1. 类的默认成员函数:
类的默认成员函数就是用户没有显示实现,编译器会自动生成的成员函数称为默认成员函数。一个类,我们不写的情况下编译器会默认生成6个默认成员函数(构造函数,析构函数,拷贝构造函数,赋值重载函数,取地址重载(普通对象,const引用对象)),最后面两个取地址重载不重要,稍微了解一下即可。其次就是C++11以后还会增加两个默认成员函数,移动构造和移动赋值。默认长远函数很重要,我们从两个方面去学习:
- 第一:我们默认写时,编译器默认生成的函数行为是什么,是否满足我们的需求。
- 第二:黏一起默认生成的函数不符合我们的需求,我们需自己实现,如何实现?
2. 构造函数
构造函数是特殊的成员函数,需要注意的是,构造函数虽名称叫构造,但是构造函数的主要任务并不是开空间创造对象,而是对象实例化时初始化对象。构造函数的本质是为了替代我们之前写的Stack 和 Date 类中的 Init 函数功能,构造函数自动调用的特点完美的替代了 Init 函数。
构造函数特点:
- 函数名与类名相同
- 无返回值
- 对象实例化系统会自动调用对应的构造函数
- 构造函数可以重载
- 如果类中没有显示定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显示实现定义编译器将不再生成
- 无参构造函数、全缺省构造函数、我们不写编译器默认生成的构造函数,都叫做默认构造函数。但是三个函数有且只有一个能有效存在,不能同时存在。无参构造函数和全缺省构造函数虽然构成函数重载,但是调用时会存在歧义。要注意很多同学会认为默认构造函数函数是编译器默认生成呢个叫默认构造,实际上无参构造函数,全缺省构造函数也是默认构造,总结一下就是不传实参就可以调用的构造就叫默认构造。
- 我们不写,编译器默认生成的构造,对内置类型成员变量的初始化没有要求,也就是说是否初始化是不确定的,看编译器,对于自定义类型成员变量,要求调用调用这个成员变量的默认构造函数初始化。如果这个成员变量的默认构造函数初始化。如果这个成员变量,没有默认构造函数。那么就会报错,我们要初始化这个成员变量,需要用初始化列表才能解决,我们下个章节再讲。
注:C++把类型分为内置类型(基本类型)和自定义类型,内置类型就是语言提供的原生数据类型,如:Int/char/double/指针等,自定义类型就是我们使用 class/struct等关键字自定义的类型。
3. 析构函数
析构函数与构造函数功能相反,析构函数不是完成对对象本身的销毁,比如局部对象是存在栈帧,函数栈帧销毁, 他就释放了,不需要我们管C++规定在对象销毁时自动调用析构函数,完成对象中资源的清理释放工作。析构函数的功能类比我们之前 Stack 实现的 Destroy 功能,而像 Date 没 有 Destroy 其实就是没有资源释放,严格来说 Date 是不需要析构函数的。
析构函数的特点:
- 析构函数的函数名是在类名前加上符号 ‘~’
- 无参数无返回值(这里跟构造函数类似,也不需要加 void )
- 一个类只能有一个析构函数,若显示未定义,编译器会自动生成默认的析构函数
- 对象生命周期结束后,系统会自动调用析构函数
- 跟构造函数类似,我们不写编译器自动生成的析构函数对内置类型成员不做处理,自定类型成员会调用它的析构函数
- 我们显示写析构函数,对自定义类型成员也会调用它的析构,也就是说自定义类型成员无论什么情况都会调用析构函数
- 如果类中没有生成资源时,析构函数可以不写,直接使用编译器生成的默认析构函数,如 Date ;如果默认生成的就可以用,也就不需要显示写析构,如MyQueue;但是有资源申请时,一定要自己写析构,否则会造成内存泄露,如Stack。
- 一个局部域的多个对象,C++规定后定义的先析构
4. 拷贝构造函数
如果构造函数的参数是第一个类型的引用,且任何额外的参数都有默认值,则此构造函数也叫做拷贝构造函数,也就是说拷贝构造函数是一个特殊的构造函数。
拷贝构造的特点:
- 拷贝构造函数是一个构造函数的重载
- 拷贝构造函数的第一个参数必须是类类型对象的引用,使用传值方式编译器直接报错,因为语法逻辑上会引发无穷递归调用。拷贝构造函数也可以多个参数但是第一个参数必须是类类型对象的引用,后面的参数必须有缺省值
- C++类型规定自定义类型的拷贝必须要调用拷贝构造,所以这里自定义类型传值传参和传值返回都必须调用拷贝构造完成
- 若未显示定义拷贝构造,编译器会自动生成拷贝构造函数。自动生成的的拷贝构造对内置类型的成员变量会完成值拷贝/浅拷贝(一个字节一个字节的拷贝),对自定义类型的成员变量会调用它的拷贝构造。
- 像 Date 这样的类成员变量全是内置类型且没有指向什么资源,编译器自动生成的拷贝构造就可以完成我们需要的拷贝,所以不需要我们显示实现拷贝构造。像 Stack 这样的类,虽然也都是内置类型,但是_a指向了资源(也就是开辟了内存),编译器自动生成的浅拷贝/值拷贝不符合我们的需求,所以需要我们自己实现深拷贝(对指向的资源也进行拷贝)。这里有一个小技巧,如果一个类显示实现了析构并释放资源,那么他就要显示写拷贝构造,否则就不需要。
- 传值返回会创建一个临时对象调用拷贝构造,传值引用返回返回的是引用对象的别名(引用),没有产生拷贝。但是如果返回的对象是一个当前函数局部域的局部对象,函数结束就销毁了,那么使用引用返回是有问题的,这时的引用相当于野引用,类似于野指针一样。传引用返回可以减少拷贝,但是一定要确保返回对象,在当前函数结束后还在,才能引用返回。