目录
一、构造函数
1.1声明和定义构造函数
1.2成员名和参数名
1.3构造函数的使用
1.4初始化列表
二、析构函数
2.1析构函数的概念
2.2析构函数的性质
三、拷贝构造函数
四、赋值运算符重载
4.1运算符重载
4.2赋值运算符重载
一、构造函数
我们知道,C++中类的数据部分的访问状态是私有的,这就意味着程序不能直接访问数据成员,而只能通过成员函数来访问数据成员。为了让创建的实例化对象一开始都有一个合适的初始值(如果需要额外调用函数赋初值未免太过麻烦),C++提供了一个特殊的成员函数——类构造函数,C++提供了函数的名称和语法,而程序员需要提供方法定义。名称和类名相同。
构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,以保证每个数据成员都有 一个合适的初始值,并且在对象整个生命周期内只调用一次。
1.1声明和定义构造函数
已知我们的构造函数名和类名相同,现在我们来定义一个Date类的构造函数:
1.2成员名和参数名
如果我们不熟悉构造函数,我们可能会试图将类成员名称用作构造函数的参数名,如下所示:
其实这样是不对的。构造函数的参数表示的不是类成员,而是赋给类成员的值。因此,二者不能相同,否则代码就会出现上图的情况,如"year = year",虽然系统会默认传一个 this 指针,但是我们也应该避免这种混淆,以下给出两种解决方案:
我比较不听从他们的建议,我选择了在数据成员中使用 _ 前缀,其实大家也可以按照自己的喜好随意命名,这里只是提供一种思路。
1.3构造函数的使用
这里有两种使用构造函数的语法,而且提供了一个错误案例:
d3的报错也说明了系统不允许创建未初始化的对象,但是当我们没有人为提供构造函数时,系统会自动生成默认构造函数,无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个:
注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为
是默认构造函数
此时我们也可以调用 Print() 查看系统的默认构造函数:另外,C++11 中针对内置类型成员不初始化的缺陷,又打了补丁,即:内置类型成员变量在类中声明时可以给默认值(但这只能成为赋初值,而不能称作初始化。因为初始化只能初始化一次,而构造函数体内可以多次赋值):
1.4初始化列表
除了上述的声明和定义构造函数,还有一种初始化方式就是初始化列表,初始化列表是指以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个"成员变量"后面跟一个放在括号中的初始值或表达式。
为什么学习完之前的构造函数还要再学这种呢?这并非画蛇添足,而是在一些情境下,必须使用初始化列表,下面列出几种情况:
1.引用成员变量
2.const成员变量
3.自定义类型成员(且该类没有默认构造函数时)
下面我们来介绍一下初始化列表的一些性质:
1.初始化列表有一个走后门之处:不管你是否使用初始化列表,对于自定义类型成员变量,一定会先使用初始化列表初始化,如果没有,再进入函数体内部寻找。
2.成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关
二、析构函数
2.1析构函数的概念
学指针时,我们创建一个指针后还要把它销毁,那我们能创建一个对象,如何销毁一个对象呢?
析构函数:与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作。
2.2析构函数的性质
析构函数是特殊的成员函数,其特征如下:
1. 析构函数名是在类名前加上字符 ~。
2. 无参数无返回值类型。析构函数必须是 ~类名()
3. 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构函数不能重载。
4. 对象生命周期结束时,C++编译系统系统自动调用析构函数。
我们在这里创建的Date类的析构函数不承担任何重要的工作,因此可以将它编写为不执行任何操作的函数,当然,为了我们能明显地看出析构函数何时被调用,可以这样编写代码:
另外,我们还要知道的是,越早创建的对象,析构函数的调用越靠后:
最后,我们想谈的就是不同区域的函数最终按什么顺序完成析构?
局部对象(先定义后析构)-> 静态区域 -> 全局对象(先定义后析构)
三、拷贝构造函数
拷贝构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用。
拷贝构造函数可以说是构造函数的重载形式,所以它的语法和构造函数类似:
和前面学过的两个函数类似,拷贝构造函数若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数是传值拷贝,也就是浅拷贝。
注意:类中如果没有涉及资源申请时,拷贝构造函数是否自定义都可以;一旦涉及到资源申请时,则拷贝构造函数是一定要写的,否则就是浅拷贝。
四、赋值运算符重载
4.1运算符重载
运算符重载是C++多态的一种体现。运算符重载将重载的概念扩展到运算符上,允许赋予C++运算符多种含义,实际上,我们已经见过很多运算符重载的例子。如 * ,在指针运用上,它充当解引用的作用,而在两数相乘时,它有充当乘法的作用。
C++运算符重载语法如下:
函数名字为:关键字operator后面接需要重载的运算符符号。
函数原型:返回值类型 operator操作符(参数列表)
1.如果我们要定义运算符重载,可能要访问类内private数据,此时我们可以直接将其定义为类的成员函数。
注意:这里的函数参数默认已经有了this指针,我们输入的_year其实是this->_year
所以我们只需要再传入我们需要比较的另一个参数即可
2.如果我们要把函数定义在类外,那么我们要提前用到后面会学的友元函数(简单来说,被friend关键字修饰的函数可以访问类内的私有成员)/
4.2赋值运算符重载
我们想要类似拷贝构造函数一样的运算符重载,应该怎么办呢?下面假设我们的类 class Type
首先,我们要明确函数返回值类型应该是Type&,如下图:
然后我们现在先设计一个大概,有了之前this指针的经验,我们轻松就可以在类内设计出来:
现在我们看一下还缺点什么?返回值!那么应该返回什么呢?环顾了四周,怎么什么都不能返回啊?非也非也,不要忘记了this指针!我们传参的时候可是默认传了个this指针呢!这个指针是谁?不就是我们要被赋值的对象吗?但要注意,我们返回的应该是解引用的this。
光说了类内,但是类外呢?我们运算符重载时可是学了两种方法啊,我记得在类外是用友元吧,那现在呢?赋值运算符重载在类外怎么实现呢?
当我们像运算符重载一样使用友元后:
哎呦呵,我才刚在类内声明呢,我类外还没实现呢,您怎么就给我报错了?
报错原因显而易见,下面我们来探讨一下为什么赋值运算符只能是成员函数呢。
答:赋值运算符如果不显式实现,编译器会生成一个默认的。此时用户再在类外自己实现一个全局的赋值运算符重载,就和编译器在类中生成的默认赋值运算符重载冲突了,故赋值运算符重载只能是类的成员函数。
而其他的如值拷贝的内容、什么时候必须手写赋值运算符重载,就和拷贝构造函数的内容一致了,大家自行查阅: