C++构造函数 +初始化列表| 析构函数 | 拷贝构造函数保姆级入门

一.默认成员函数

1.1.空类

在进入正文前,先引入一个概念--默认成员函数,先上图:

注:空类或结构体的大小为1是因为C++标准规定“任何不同的对象不能拥有相同的内存地址”如果空类大小为0,当声明一个这个类的对象数组时,数组中的每个对象都拥有相同的地址,这违背了标准。为了确保每个实例在内存中都有一个独一无二的地址,编译器往往会给一个空类隐含的加一个字节

对于一个空类,类中并非什么都没有,其中带有了6个默认成员函数。如图:

那么这些函数到底有什么作用,在此介绍构造函数,析构函数,拷贝构造函数。

1.2全缺省和无参函数的二义性问题

字面意思,如图当我们调用两个函数时,看似参数不同,实则编译器编译时无法区分这两个函数的区别。

二.构造函数

2.1.为什么要使用构造函数

直接上图来看比较清楚:如图每次使用a时都需要给它手动初始化,比较麻烦。

于是C++中便定义了一个函数叫做构造函数,也可以理解为专门用来初始化对象的函数。

2.2.构造函数的特性

构造函数是特殊的成员函数

1.函数名与类名相同(A类的构造函数的名字就是A())

2.无返回值(无返回类型)

3.对象实例化时自动调用构造函数(如:A a;时编译器自动调用)

4.构造函数可以重载(可以写多个,定义多种初始化方式,要注意无参函数和全缺省函数只可存在一个)

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

6.C++将类型分为内置类型和自定义类型,内置类型就是语言提供的数据类型如:int/char/float...自定义类型就是我们自己定义的class/struct/union/等类型,在调用默认生成的构造函数时,对于内置类型编译器不做处理,即这些内置类型中存的是随机值,而对自定义类型则会调用它的默认成员函数。(若是在该自定义类型中依然未写构造函数,则调用它的默认构造函数,调用规则同上)

7.无参的构造函数,全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。

注意:无参构造函数,全缺省构造函数,我们未写编译器自动生成的函数,都为默认构造函数。

8.当内置类型被默认处理为随机值时,我们可以选择在private中声明内置类型时给缺省值,即当调用构造函数时,默认将其定义为我们给定的缺省值,如图:

总结:

         1.一般情况下我们需要显时定义构造函数,一般为全缺省构造函数。

         2.只有少数情况下不需要写,使用编译器自动生成的构造函数。如用两个栈实现队列Myquene.

2.3.构造函数应用实例--日期类


2.4函数调用时间

• 构造函数调用时间

• 对象被建立时

三.初始化列表

3.1为什么要使用初始化列表

初始化列表:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个"成员变量"后面跟一个放在括 号中的初始值或表达式.

初始化列表的本质:可以理解为类对象中每个成员被定义的地方

有三种情况,无法使用在构造函数函数体内对对象中的某些内容进行初始化

1.const类型变量

2.引用类型变量

3.类中的自定义类型成员变量没有默认构造函数(即无参构造函数全缺省构造函数编译器默认生成的构造函数,注:当有了前两者中的其一,编译器不会再生成默认构造函数

4,对于其他正常情况,也可以使用初始化列表对其进行初始化

3.2实例体现与理解为什么要使用初始化列表:Myquene类

让我们先完成一个简单的堆栈类:

那么请重点关注此类的构造函数这块:

可以看到,在参数列表中给定了缺省值,这里属于全缺省默认构造函数,我们再看Myquene类:

这时当我们创建一个Myquene类的q变量时,可以看到,自定义类型被调用默认构造函数初始化,内置类型被编译器进行处理(不同编译器有不同处理)

但当我们去除Stack类的默认构造函数时,Myquene也不具备相应默认构造函数,就会出现如下情形:

因此,我们就需要对Myquene的构造函数用初始化列表进行显示化,如图

若我们在Myquene类中添加const类,和引用类,则也必须在初始化列表中进行初始化,否则会报错,大家可以自行验证,如图:

3.3初始化列表的一些细节与特性

3.3.1初始化列表的隐式调用

现在,经过前面的介绍,相信大家已经了解了初始化列表的用法,现在介绍一些它的细节和特性。首先请大家回到Myquene的默认构造函数上:

其实对于该默认构造函数,无论你写不写初始化列表,编译器都会自动走一遍类中的每个成员变量,对于内置类型不做处理(具体看编译器),对于自定义类型则调用它的默认构造函数。相当于:

因此,在上节介绍构造函数时,我们已经讲过,对于不做处理的内置类型可以使用给缺省值的方式来进行赋值,但其实本质就是将给定的缺省值用于初始化列表中,即

等效于:

3.3.2初始化列表的特性

1.初始化列表只能初始化一次,多次初始化会报错。

错例:

2.编译器也允许构造函数赋初值和初始化列表初始化混用:

顺序为先初始化列表再函数体。

但混用时初始化列表初始化还是要遵循只能初始化一次成员变量的原则。

实践中:尽可能使用初始化列表进行初始化,不方便再使用函数体。

3.成员变量在类中声明的先后顺序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关。

如图:

3.4总结

1.实践中:尽可能使用初始化列表进行初始化,不方便再使用函数体。

2.对于所提到的三种特例一定要使用初始化列表,但若自定义类型中有默认构造函数也可以不写,因为存在初始化类表的隐式调用。

四.析构函数

4.1.为什么要使用析构函数

析构函数:与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作。

4.2.析构函数的特性

1.析构函数的名字时类名前加~(如A类,则函数~A())

2.无参数无返回值

3.一个类只能有一个析构函数。若未显示定义,系统会生成默认的析构函数。

注意:析构函数不能重载

4.对象生命周期结束时,自动调用析构函数。

5.跟构造函数类似,对于内置类型不做处理,由编译器处理,对于自定义类型去调用它的析构函数。

总结:

1.有资源需要显式清理,就需要写析构,如:stack,list...

2.有两种场景不需要写析构

其一:没有资源需要清理,如Date类,其中都是内置类型

其二:内置类型没有资源需要清理,其余都是自定义类型。

4.3.析构函数应用实例-Myqueen

4.4函数调用时间

• 析构函数调用时间

• 对象被销毁时(delete a)

• 函数执行完毕时(仅适用于函数内建立的对象)

• 整个程序结束运行时

• 不包括动态分配的对象

五.拷贝构造函数

5.1.为什么要使用拷贝构造函数

在创建一个对象时,能否方便快速的创建一个与已存在对象一摸一样的对象呢?

拷贝构造函数:只有单个形参,该形参是对本类类对象的引用(一般用const修饰,防止调用时将需要拷贝的对象的值进行修改),在用已存在的类类型对象创建新对象时由编译器自动调用。

5.2.拷贝构造函数的特性

1.拷贝构造函数是构造函数的一个重载形式。

2.拷贝构造函数调用的时间

• 建立对象时设定对象类型参数时

例如:

• 函数参数为类类型时 • 实参给形参传值的过程本质上是以对象生成对象

例如:(d和a1的地址不同)

• 函数返回值为类类型时 • 局部对象先建立接收返回值的对象 再销毁自己

例如:

3.拷贝构造函数的参数有且仅有一个且必须是类类型对象的引用,使用传值方式编译器会报错,因为会引发无穷递归调用。(当传值时会调用拷贝构造函数,而调用该函数首先便需要传参,那么又会去调用拷贝构造函数,而新的调用的函数首先仍需要传参,那么便引发了无穷递归问题。)

4.若未显式定义,编译器会自动生成默认的拷贝构造函数。默认的拷贝构造函数对象按内存存储按字节序进行拷贝,而对于引用指针类,引用类则会出现这么一个问题,如原对象中有一个arr数组指针,新对象中的arr数组指针的地址会与原对象中的arr地址一致,当调用析构函数时,会连续清理资源两次引发报错。

• 浅拷贝构造函数 • 上述系统自动生成的拷贝构造函数

• 缺点:难以应对指针和引用参数

• 深拷贝构造函数 • 需要重载构造函数

• 例:为Date类编写深拷贝构造函数 

总结:

1.如果没有管理资源,一般不需要写拷贝构造,默认生成的拷贝构造函数即可,如:Date

2.如果都是自定义类型,内置类型成员没有指向资源,默认的拷贝构造即可,如Myquene

3.一般情况下,不需要写析构函数就不用写拷贝构造

4.如果内部有指针或者一些值指向资源,需要显示写析构释放,通常就需要写拷贝构造函数完成深拷贝。如:Stack,Quene,List

5.3拷贝构造函数使用的实例--日期类

  • 23
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值