一、构造函数
构造函数是类的同名成员函数,没有返回值,当实例化对象时它会自动执行,一般负责对类进行初始化、分配资源
class 类名
{
int* p;
public:
类名(参数)
{
p = new int;
}
};
1、构造函数必须是public 否则无法实例化对象
2、构造函数可以重载,可以有多个版本
3、带参数的构造函数的调用方法:
类名 对象名(实参);
类名* 对象指针 = new 类名(实参);
4、默认情况下编译器会自动生成一个无参构造函数,该函数什么都不做,一旦显示地实现了构造函数,则编译器不生成该函数
类名 对象名; //调用无参构造,如果没有无参构造则报错
5、也可以通过设置构造函数的默认形参达到无参构造的效果
6、构造函数没有返回值
7、不要使用malloc为类实例化对象分配内存,因为malloc不会调用构造函数
二、、析构函数
析构函数负责对类对象进行收尾工作,例如:释放类中的资源、保存数据等,当类对象销毁时会自动调用执行
class 类名
{
int* p;
public:
类名(参数)
{
p = new int;
}
~类名()
{
delete p;
}
};
1、析构函数也必须是 public
2、没有返回值、参数、不能重载
3、当类对象生命周期完结、或者使用delete销毁对象时会自动调用析构函数
4、如果没有显示地实现析构函数,编译器也会自动生成一个什么都不干的析构函数
5、构造函数肯定会执行,但是析构函数不一定执行
6、不要使用free来销毁对象,不会执行析构函数
初始化列表
初始化列表是构造函数的一种特殊语法,只能在构造函数中使用
class 类名
{
public:
类名(参数):成员名1(初始化数据),成员名2(初始化数据)...
{
//构造函数
}
};
1、类的成员变量在老编译标准中不可以设置初始值的,而且在构造函数之前成员变量都已经定义完毕,因此const属性的成员
变量无法在构造函数内正常赋值初始化(新标准中可以直接设置初始值,但是也只能给常量,功能有限)
2、初始化列表先于构造函数执行的,初始化列表执行时类对象还没有构造完成,因此它是唯一(老标准)一种能给const属性成员变量赋值的方法
3、当参数名与成员名相同,初始化列表可以自动分解成员名和参数名
4、当类中有类类型成员时,该成员的有参构造函数也可以在初始化列表中被执行
三、拷贝构造
拷贝构造是一种特殊的构造函数,格式为
类名(const 类名& that) //const不是必写的,加了更好
{
}
什么时候会调用拷贝构造:当使用旧对象给新对象进行初始化时,会自动调用拷贝构造
Test t;
Test t1 = t;
拷贝构造的任务:
顾名思义拷贝构造负责把旧对象中的成员变量拷贝给新对象,且编译器会默认生成具备这样功能一个隐藏的拷贝构造函数
什么时候应该显示地实现拷贝构造:
普通情况下编译器自动生成的拷贝构造完全够用,但是当类中的成员有指针且为指针分配了堆内存,默认的拷贝构造
只会对指针的值进行拷贝,这样就导致两个对象的指针成员都指向同一块堆内存,在执行析构函数时会造成重复
释放堆内存崩溃,此时就应该显式地实现拷贝构造
浅拷贝和深拷贝:
当类中有成员是指针类型且分配了堆内存,浅拷贝只会拷贝指针变量的值;深拷贝不拷贝指针变量的值,而是申请新的内存,
拷贝内存中的内容到新内存中
四、赋值操作函数:
所谓的赋值操作,就是一个对象给另一个对象赋值(俩对象都已经创建完毕),在C++中会把运算符当作函数处理,使用运算符
相当于调用运算符函数
void operator=(const 类名& that)
{
}
Test t1, t2;
t1 = t2; //调用赋值操作
赋值运算符函数的任务:
它与拷贝构造的任务基本一致,默认下编译器也会自动生成具备浅拷贝功能的赋值操作函数,但是当需要进行深拷贝时不仅需要
显式实现拷贝构造,同时也需要显式地实现赋值运算符函数
实现赋值运算符函数需要注意的问题:
赋值运算符函数与拷贝构造函数任务虽然接近,但是实现过程有所不同:
问题1: 两个对象的指针都已经分配好内存
a、先释放被赋值者的指针变量所指向的原内存
b、再给被赋值者的指针变量重新申请内存
c、把赋值者指针变量所指向内存的内容拷贝给赋值者新申请的内存中
问题2: 有可能对象自己给自己赋值
需要判断this和赋值者的地址是否相同,如果相同立即结束,不相同才进行赋值操作
问题3: 允许 n1 = n2 = n3 连续赋值操作
因此赋值运算符函数的返回值要返回类对象的引用