c++ - 第3节 - 类与对象(中)

1.类的6个默认成员函数

如果一个类中什么成员都没有,简称为空类。空类中什么都没有吗?并不是的,任何一个类在我们不写的情况下,都会自动生成下面6个默认成员函数

c++ - 第3节 - 类与对象(中)_第1张图片
 
建议:
c++ - 第3节 - 类与对象(中)_第2张图片


2. 构造函数

2.1 概念

对于以下的日期类:

c++ - 第3节 - 类与对象(中)_第3张图片

对于Date类,可以通过SetDate公有的方法给对象设置内容,但是如果每次创建对象都调用该方法设置信息,未免有点麻烦,那能否在对象创建时,就将信息设置进去呢?
构造函数是一个 特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,保证每个数据成员都有一个合适的初始值,并且 在对象的生命周期内只调用一次

2.2 特性

构造函数是特殊的成员函数,需要注意的是,构造函数的虽然名称叫构造,但是需要注意的是构造函数的主要任务 并不是开空间创建对象,而是初始化对象
其特征如下:
1. 函数名与类名相同。
2. 无返回值。
3. 对象实例化时编译器自动调用对应的构造函数。
4. 构造函数可以重载。
c++ - 第3节 - 类与对象(中)_第4张图片

注:如果通过无参构造函数创建对象时,对象后面不用跟括号,否则就成了函数声明

5.无参的构造函数和全缺省的构造函数都称为默认构造函数(默认构造函数还有一个是编译器自动生成的默认构造函数,下面会讲)(默认函数简单说是不用传参就可以调用的),并且默认构造函数只能有一个

注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认成员函数。

c++ - 第3节 - 类与对象(中)_第5张图片

c++ - 第3节 - 类与对象(中)_第6张图片

注:

1.无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认成员函数。

2.上面data无参的构造函数和data全缺省的构造函数在语法上构成重载可以同时存在,编译阶段没有问题,但是在使用时即代码运行时,存在调用歧义。

3.我们一般使用全缺省构造函数,因为全缺省构造函数不传参就是默认初始化,完成无参构造函数功能,传参的话就完成带参构造函数功能,同时也可以传一部分参数,其余参数就使用默认值,用法十分灵活

6.如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成

c++ - 第3节 - 类与对象(中)_第7张图片

问题:根据打印的结果来看,自动生成的默认构造函数好像什么事也没做,那它有什么用呢?

解答:

内置类型/基本类型:int/char/double/指针...
自定义类型:class/struct去定义类型对象
默认生成构造函数对于内置类型成员变量不做处理,对于自定义类型成员变量才会处理,如下图所示

c++ - 第3节 - 类与对象(中)_第8张图片

注:上面Data类自动生成的默认构造函数要对自定义类型成员变量进行初始化,初始化的过程是再调用A类的默认构造函数对其进行初始化

那么什么时候会用自动生成的默认构造函数呢?如下图所示,利用自动生成的默认构造函数对两个栈进行初始化

c++ - 第3节 - 类与对象(中)_第9张图片

注:如果Stack里面也不手动写默认构造函数而使用自动生成的默认构造函数,那么MyQueue类里面自动生成的默认构造函数会调用Stack类里面的默认构造函数,Stack里面也是自动生成的默认构造函数,对于其里面的内置类型不会初始化,内置类型仍是随机值,因此做了无用功。

总结:如果一个类中的成员全是自定义类型,我们就可以用自动生成的默认构造函数(如上图MyQueue类)。如果有内置类型的成员,或者需要显示传参初始化,那么都要自己实现构造函数

一旦类里面有用户显式定义的构造函数(显式定义的构造函数也就是自己写的构造函数),编译器就不会再自动生成默认构造函数。而如果此时里面显式定义的构造函数不是默认构造函数,那么编译就会出错。如下图所示,MyQueue类尝试调用Stack里面的默认构造函数,而Stack里面没有默认构造函数,会报错。这种情况需要我们使用初始化列表进行显式处理,初始化列表后面会介绍

c++ - 第3节 - 类与对象(中)_第10张图片

7.针对编译器自动生成的默认构造函数无法对内置类型成员变量初始化的问题(c++早期设计的一个败笔),C++11打了一个补丁,可以给内置成员类型缺省值,仅供默认构造函数使用,如下图所示。

注意:int _size = 0不是初始化,此处是声明,是不开辟空间的

c++ - 第3节 - 类与对象(中)_第11张图片

结论:

一般情况一个c++类,都要自己写默认构造函数。一般只有少数情况可以让编译器自动生成默认构造函数:

(1)类里面成员都是自定义类型成员,并且这些成员都提供了默认构造函数

(2)如果类里面还有内置类型成员,这些内置类型成员声明时给了缺省值(如上图所示)

类似下面场景就不需要自己写,默认生成就够用了

c++ - 第3节 - 类与对象(中)_第12张图片


3.析构函数

3.1.概念

前面通过构造函数的学习,我们知道一个对象时怎么来的,那一个对象又是怎么没呢的?
析构函数:与构造函数功能相反,析构函数不是完成对象的销毁,局部对象销毁工作是由编译器完成的。而 对象在销毁时会自动调用析构函数,完成类的一些资源清理工作

3.2.特性

析构函数是特殊的成员函数。
其特征如下:
1. 析构函数名是在类名前加上字符 ~。
2. 无参数无返回值(不能构成函数重载)。
3. 对象生命周期结束时,C++编译系统系统自动调用析构函数。
有些类我们需要写析构函数,有些类我们不需要写析构函数,如下图所示。Data类不需要写析构函数,里面没有资源需要清理,stack类需要析构函数,里面malloc开辟的空间需要清理
c++ - 第3节 - 类与对象(中)_第13张图片

4.析构顺序和构造顺序是反的,构造是代码从上往下依次构造,析构是代码从下往上依次析构,如下图所示

c++ - 第3节 - 类与对象(中)_第14张图片
5. 一个类有且只有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。
注意:系统自动生成的默认析构函数与系统自动生成的默认重构函数相似,也是内置类型不做处理,自定义类型会去调用它的析构函数
c++ - 第3节 - 类与对象(中)_第15张图片


4. 拷贝构造函数

4.1.概念

在现实生活中,可能存在一个与你一样的自己,我们称其为双胞胎。

c++ - 第3节 - 类与对象(中)_第16张图片

那在创建对象时,可否创建一个与一个对象一模一样的新对象呢?
拷贝构造函数只有单个形参,该形参是对本 类型对象的引用(一般常用const修饰),在用 已存在的类型对象创建新对象时由编译器自动调用

4.2.特征

拷贝构造函数也是特殊的成员函数,其特征如下:
1. 拷贝构造函数是构造函数的一个重载形式
2. 拷贝构造函数的参数只有一个且必须使用引用传参,使用传值方式会引发无穷递归调用
为什么传值方式会引发无穷递归调用:
我们先理解一个知识,c++中自定义类型传值传参是要调用拷贝构造的,如下图所示。
注:下面的代码中,因为Date(const Date d)中d是拷贝过来的,因此前面的const可以不加
c++ - 第3节 - 类与对象(中)_第17张图片

如果我们使用引用传参,因为此时d是d1的别名,那么就不会进行拷贝构造了,如下图所示。

注:下面的代码中,因为Date(const Date& d)中d是d1的别名,而Data在类里面,private不会进行保护,因此如果修改了d的成员变量会影响d1,前面最好加上const

c++ - 第3节 - 类与对象(中)_第18张图片

这样传值传参是一个拷贝构造,而拷贝构造又要先传参,这样就无限循环下去了,如下图所示。因此拷贝构造函数传参必须使用引用传参

c++ - 第3节 - 类与对象(中)_第19张图片

3.类似栈这种类,他的拷贝构造不能进行浅拷贝(也就是对值进行拷贝),因为如果简单的对值进行拷贝,那么st1和st2就指向了同一块内存空间了,此时操作st1后对st2进行操作,有可能就会把对st1操作的结果覆盖掉,并且系统到后面会崩(因为最后析构的时候会对这同一块地址空间进行前后两次清理,第二次清理的时候系统崩掉),这种情况只能进行深拷贝(深拷贝的实现后面会讲)

c++ - 第3节 - 类与对象(中)_第20张图片

c++ - 第3节 - 类与对象(中)_第21张图片

这就是为什么c++规定,自定义类型对象,拷贝构造函数形参传值传参或对象用另一个对象赋值初始化要使用拷贝构造函数,因为如果遇见需要深拷贝的地方,例如栈,我们不调用拷贝构造函数而只是像c语言一样简单的赋值,那么最后像析构等地方会出问题

4.若未显示定义,系统生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝我们叫做浅拷贝,或者值拷贝。 如下图一所示,默认生成的拷贝构造函数进行了浅拷贝。下图二所示的情况,应该自己写函数进行深拷贝,如果不写,默认生成函数进行了浅拷贝,此时两个指针都指向了同一块内存空间的地址,此时操作st1后对st2进行操作,有可能就会把对st1操作的结果覆盖掉,并且析构的时候会对该空间析构两次,第二次进行析构的时候就会报错 

c++ - 第3节 - 类与对象(中)_第22张图片

c++ - 第3节 - 类与对象(中)_第23张图片

你可能感兴趣的:(c++,c++,开发语言)