不能在类声明中对数据成员初始化
如果一个类中的所有成员都是公用的,那么可以在定义对象时对数据成员进行初始化;如:
class Time
{
public:
hour;
minute;
sec;
}
Time t1={14,56,30};
如果类中有private或protected的数据成员,就不能用这种方法。
利用构造函数实现数据成员的初始化
构造函数与其他成员函数不同,不需要用户来调用它,而是在建立对象时自动执行;
构造函数必须和类名同名,不能任意命名
不具有任何类型,不具有返回值
构造函数在建立类对象时会自动调用构造函数;在建立对象时系统为对象分配存储单元,此时执行构造函数,就把指定的初值送到有关数据成员的存储单元中,每建立一个对象,就会调用一次构造函数。
构造函数不需被用户调用,也不能被用户调用;
构造函数是定义对象时由系统自动执行的,而且只能执行一次,构造函数一般声明为public
可以用一个类对象初始化另一个类对象,如:
Time t1;
Time t2=t1;
此时,把对象t1的各个数据成员复制到t2相应各成员,而不调用t2的构造函数
如果用户没有自己定义构造函数,那么C++会自动生成一个构造函数,只是这个构造函数的函数体是空的,没有参数,不执行任何操作。
带参数的构造函数
用户是不能调用构造函数的,那么对于带参数的构造函数,无法用常规的方式给出实参
实参是在定义对象时给出的,定义对象的一般形式为:
类名 对象名(实参1,实参2···);
在建立对象时把实参的值传递给构造函数相应的形参,把他们作为数据成员的初值。
带参数的构造函数中的形参,其对应的实参是在建立对象时给出的。即在建立对象时同时指定数据成员的初值;
用参数初始化表对数据成员初始化
上面是在构造函数的函数体内通过赋值语句对数据成员实现初始化
下面介绍另一种方法
参数初始表来实现对数据成员的初始化
不在函数体内对数据成员初始化,而是在函数首部实现
在原来函数首部的末尾加一个冒号,然后列出参数的初始化表,后面的花括号是空的,即函数体是空的,没有任何执行语句
用参数的初始化表法可以减少函数体的长度,使结构函数显得精炼简单。这样就可以直接在类体中定义构造函数;
带有参数初始化表的构造函数的一般形式:
类名::构造函数名([参数表])[:成员初始化表]
{
[构造函数体]
}
例如:
Box::Box(int h,int w,int len):height(h),width(w),length(len) {}
如果数据成员是数组的话,则应当在构造函数的函数体中用语句对其进行赋值而不能在参数初始化表中对其初始化,如:
class Student
{
public:
Student(int n,char s,char nam[]):num(n),sex(s)
{
strcpy(name,nam);
}
}
在定义对象的时候,可以这样用
Student stud1(10101,'m',"wang_li");
构造函数的重载
在一个类中可以定义多个构造函数
这些构造函数具有相同的名字,而参数的个数或参数的类型不相同。
这就是构造函数的重载
编译系统根据函数调用的形式去确定对应哪一个构造函数
在建立对象时不必给出实参的构造函数,称为默认构造函数,显然,无参构造函数属于默认构造函数。
一个类只能有一个默认构造函数
如果用户未定义构造函数,则系统会自动提供一个默认构造函数,但它的函数体是空的,不起初始化作用。
如果在建立对象时选用的是无参构造函数,那么:
Box box1;
这是正确的调用形式
Box box1();
这是错误的调用形式,不应该有括号,这样子的意义为声明一个普通函数box1,返回值为Box类型
构造函数是不能被用户显式调用的
尽管在一个类中可以包含多个构造函数,但是对于每一个对象来说,建立对象时只执行一个构造函数,并非每个构造函数都被执行
使用默认参数的构造函数
构造函数中参数的值可以通过实参传递,也可以指定为某些默认值,即如果用户不指定实参值,编译系统就使形参取默认值;
类中声明:Box(int h = 10, int w = 10, int len = 10);
可写为:Box(int = 10, int = 10, int 10);
类外定义:
Box::Box(int h, int w, int len)
{
height = h;
width = w;
length = len;
}
在构造函数中使用默认参数是方便而有效的,它提供了建立对象时的多种选择,它的作用相当于好几个重载的构造函数
应当在声明构造函数时指定默认值,而不能只在定义构造函数时指定默认值。因为类声明是放在头文件中的,它是类的对外接口,用户是可以看到的,而函数的定义是类的实现细节,用户往往是看不到的。在声明构造函数时指定默认参数值,使用户知道在建立对象时应该怎么使用默认参数。
在声明构造函数时,形参名可以省略;
默认构造函数
如果构造函数的全部参数都指定了默认值,那么定义对象时可以给出一个或几个实参,也可以不给出实参
由于不需要实参也可以调用构造函数,因此:
全部参数都指定了默认值的构造函数也属于默认构造函数
一个类只能有一个默认构造函数,即意义为:可以不用参数而调用的构造函数,一个类只能有一个。为了避免歧义性。
如果同时定义了下面两个构造函数
Box();
Box(int = 10, int = 10, int 10);
那么是错误的
如果建立对象时,写成Box box1;
编译系统无法识别应该调用哪一个构造函数
在一个类中定义了全部是默认参数的构造函数后,不能再定义重载构造函数
析构函数
作用与构造函数相反
名字是类名的前面加一个"~"符号
当对象的生命期结束时,会自动执行析构函数
如果在一个函数中定义了一个对象,当这个函数被调用结束时,对象应该释放,在对象释放前自动执行析构函数
静态局部对象在函数调用结束时对象并不释放,因此也不调用析构函数,只在main函数结束或调用exit函数结束程序时,才调用static局部对象的析构函数
如果定义了一个全局的对象,则在程序的流程离开其作用域时,调用该全局对象的析构函数
如果用new运算符动态建立一个对象,当用delete运算符释放该对象时,先调用该对象的析构函数
析构函数的作用不是删除函数,而是在撤销对象占用的内存之前完成一些清理工作,使这部分内存可以被程序分配给新对象使用;
析构函数不返回任何值,没有函数类型,没有函数参数
由于没有函数参数,不能被重载
一个类可以有多个构造函数,但只能有一个析构函数
析构函数的作用并不仅限于释放资源方面,还可以被用来执行"用户希望在最后一次使用对象之后所执行的任何操作"
一般类的设计者应当在声明类的同时定义析构函数
如果用户没有定义析构函数,C++编译系统就会自动生成一个析构函数,但实际上这个析构函数什么操作都不进行;
析构函数的调用顺序
在一般情况下,调用析构函数的次序正好与调用构造函数的次序相反,最先被调用的构造函数,其对应的析构函数最后被调用,而最后被调用的构造函数,其对应的析构函数最先被调用
对同一类存储类别的对象而言:先构造的后析构,后构造的先析构
如果在全局范围中定义对象(即在所有函数之外定义的对象),那么它的构造函数在本文件模块中的所有函数(包括main)执行之前调用。但如果一个程序中包含多个文件,而在不同的文件中都定义了全局对象,则这些对象的构造函数的执行顺序是不确定的。当main函数执行完毕或调用exit函数时(此时程序终止),调用析构函数
如果定义的是局部自动对象,则在建立对象时调用其构造函数。如果对象所在的函数被多次调用,则在每次建立对象时都要调用构造函数。在函数调用结束、对象释放时先调用析构函数。
如果在函数中定义静态局部对象,则只在程序第1次调用此函数定义对象时调用一次构造函数,在调用函数结束时对象不释放,因此也不调用析构函数,只在main函数结束或调用exit函数结束程序时,才调用析构函数
对象数组
如果构造函数只有一个参数,在定义数组时可以直接在等号后面的花括号内提供实参
Student stud[3] = { 60,70,80 };
如果构造函数有多个参数,则不能用定义数组时直接提供所有实参的方法,因为一个数组有多个元素,对每个元素要提供多个实参。
编译系统只为每个对象元素的构造函数传递一个实参,所以在定义数组时提供的实参个数不能超过数组元素个数;
如果构造函数有多个参数,在定义对象数组时实现初始化的使用方法:在花括号中分别写出构造函数名并在括号内指定实参
Student stud[3] =
{
Student(1001,18,87);
Student(1002, 19, 76);
Student(1003, 18, 72);
};
对象指针
指向对象的指针
类名 *对象指针名
指向对象成员的指针
- 指向对象数据成员的指针
数据类型名 *指针变量名;
如果类的数据成员是公用的数据,那么可以在类外通过指向对象数据成员的指针变量访问对象数据成员
- 指向对象成员函数的指针
定义指向对象成员函数的指针变量和定义指向普通函数的指针变量方法有所不同
先看下指向普通函数
类型名 (*指针变量名)(参数列表);
如:
void (*p)(); 定义p为指向void型函数的指针变量
p=fun; 指向fun函数
(*p)(); 调用fun函数
而对于指向对象成员函数的指针,我们要求一下规则:
函数参数的类型和参数个数
函数返回值的类型
所属的类
必须相同
不能在类外直接用成员函数名作为函数入口地址去调用成员函数。
我们应该如下:
void (Time::* p2)();
(Time::* p2)两侧的括号不能省略,因为()优先级高于*
定义指向公用成员函数的指针变量一般形式为:
数据类型名(类名::* 指针变量名)(参数表列);
可以让它指向一个公用成员函数,只需把公用成员函数的入口地址赋给一个指向公用成员函数的指针变量即可
如:
p2 = &Time::get_time;
使指针变量指向一个公用成员函数的一般形式为:
指针变量名 = & 类名::成员函数名;