在C++中,3种对象需要拷贝,此时拷贝构造函数将会被调用。

1、一个对象以值传递的方式传入函数体

2、一个对象以值传递的方式从函数返回

3、一个对象需要通过另一个对象进行初始化

如下例:

拷贝构造函数、拷贝函数、析构函数_拷贝构造函数 赋值函数 析构函数

拷贝构造函数、拷贝函数、析构函数_拷贝构造函数 赋值函数 析构函数_02

拷贝构造函数、拷贝函数、析构函数_拷贝构造函数 赋值函数 析构函数_03

输出结构为:

拷贝构造函数、拷贝函数、析构函数_拷贝构造函数 赋值函数 析构函数_04


再如下例:

拷贝构造函数、拷贝函数、析构函数_拷贝构造函数 赋值函数 析构函数_05

拷贝构造函数、拷贝函数、析构函数_拷贝构造函数 赋值函数 析构函数_06

拷贝构造函数、拷贝函数、析构函数_拷贝构造函数 赋值函数 析构函数_07

分析:

test类中buf是一个字符指针,带参数的构造函数中为它分配了一块堆内存来存放字符串,然后析构函数中又将堆内存释放。

main函数中,首先先构造一个对象,调用带参数的构造函数,因此t1.buf指向一块堆内存(记作A),然后,又用t1去构造t2,即对象复制,由于是使用默认拷贝构造函数,那就是简单的把两个对象的指针作赋值运算,即直接复制过去的,那么这个时候,t2.buf也指向了堆内存A。

程序崩溃会发生在main()函数退出对象析构时,两个对象那么久要产生两次析构,然而两个对象中的buf指针都指向了同一块内存,同一块堆内存被释放两次,那么导致了崩溃。

程序执行结果为:

(t1.buf==t2.buf)?yes

程序崩溃


要解决这个问题,可以在Test类中再增加一个拷贝构造函数:

拷贝构造函数、拷贝函数、析构函数_拷贝构造函数 赋值函数 析构函数_08

增加了该拷贝构造函数后,在main()函数中Test t2=t1,即用对象t1去构造对象t2时,就不会调用默认拷贝构造函数了,而是使用用户重定义的拷贝构造函数,此时,函数中又为t2.buf分配了一块堆内存来存储字符串,那么此时,t1.buf和t2.buf分别指向了两块堆内存,析构时就不会发生程序崩溃了。

执行结果为:

拷贝构造函数、拷贝函数、析构函数_拷贝构造函数 赋值函数 析构函数_09


写一个继承类的拷贝函数

拷贝构造函数、拷贝函数、析构函数_拷贝构造函数 赋值函数 析构函数_10

拷贝构造函数、拷贝函数、析构函数_拷贝构造函数 赋值函数 析构函数_11

拷贝构造函数、拷贝函数、析构函数_拷贝构造函数 赋值函数 析构函数_12

运行结果为:
拷贝构造函数、拷贝函数、析构函数_拷贝构造函数 赋值函数 析构函数_13

分析:Derived类继承自Base类,因此在Derived类中不能使用obj.i或者Base::i的方式访问Base的私有成员i。

Derived类的拷贝构造函数只能使用Base(obj)的方式来 调用其基类的拷贝构造函数 来给基类的私有成员i初始化。


编写类String的构造函数、析构函数和赋值函数

拷贝构造函数、拷贝函数、析构函数_拷贝构造函数 赋值函数 析构函数_14

拷贝构造函数、拷贝函数、析构函数_拷贝构造函数 赋值函数 析构函数_15

拷贝构造函数、拷贝函数、析构函数_拷贝构造函数 赋值函数 析构函数_16

拷贝构造函数、拷贝函数、析构函数_拷贝构造函数 赋值函数 析构函数_17

拷贝构造函数、拷贝函数、析构函数_拷贝构造函数 赋值函数 析构函数_18

运行结果:

拷贝构造函数、拷贝函数、析构函数_拷贝构造函数 赋值函数 析构函数_19

之所以建立一个test()函数,而不在main()函数中直接生成对象,是为了能在函数退出时,调用析构函数。而main()函数中有system("pause"),不会退出main函数。

默认赋值函数为:A& operator = (const A &),即程序中的String& operator=(Const String &other)

在类外的实现为:String& String::operator=(Const String &other)

在test()函数中c=b时,调用默认赋值函数。

这里强调的是拷贝构造函数 和 赋值函数的区别,总结一下就是:

拷贝构造函数只有在定义一个新对象并且用已有的对象进行初始化时调用。

而赋值函数,则是对现有的已经初始化的对象进行操作。

即,前者是生成对象的过程,后者是给已有对象赋值的过程。


C++类各成员函数间关系

拷贝构造函数、拷贝函数、析构函数_拷贝构造函数 赋值函数 析构函数_20

拷贝构造函数、拷贝函数、析构函数_拷贝构造函数 赋值函数 析构函数_21

拷贝构造函数、拷贝函数、析构函数_拷贝构造函数 赋值函数 析构函数_22

执行结果为:

拷贝构造函数、拷贝函数、析构函数_拷贝构造函数 赋值函数 析构函数_23


C++类的临时对象

拷贝构造函数、拷贝函数、析构函数_拷贝构造函数 赋值函数 析构函数_24

拷贝构造函数、拷贝函数、析构函数_拷贝构造函数 赋值函数 析构函数_25

拷贝构造函数、拷贝函数、析构函数_拷贝构造函数 赋值函数 析构函数_26

分析:

调用play()函数时,有两种情况:1、传入参数为整型数时,2、传入参数为类对象时

1、传入参数为整型数时,会先调用test类中的带参数的构造函数来产生一个临时对象。

这里有一点需要特别注意的是,play()函数中有return b,即返回一个test类对象。那么在返回前(return代码执行时)会调用test类的拷贝构造函数生成一个临时对象,然后在return代码执行完毕后,再调用test类的析构函数进行析构。

2、传入参数为类对象时,会先调用test类中的拷贝构造函数来产生一个临时对象,剩下的过程与1相同。

程序执行结果为:

拷贝构造函数、拷贝函数、析构函数_拷贝构造函数 赋值函数 析构函数_27

分析结果:

调用main()函数中调用fun1()

t1 start

constructed by parameter 5(在play()函数形参声明处调用带参数的构造函数,生成临时对象)

play() start

copy constructor(play()函数中return执行时调用拷贝构造函数生成临时对象,并将其复制给t1)

destructed(play函数执行完return语句后,临时对象析构,即5传入时生成的临时对象析构)

t1 end

t2 start

copy constructor(在play)函数形参声明处调用拷贝构造函数,生成临时对象

play() start

copy constructor(play()函数中return执行时调用拷贝构造函数生成临时对象,并将其复制给t2

destructed(play函数执行完return语句后,临时对象析构,即5传入时生成的临时对象析构)

t2 end

destructed(t2析构)

destructed(t1析构)


调用fun2()同理


拷贝构造函数和析构函数

拷贝构造函数、拷贝函数、析构函数_拷贝构造函数 赋值函数 析构函数_28

拷贝构造函数、拷贝函数、析构函数_拷贝构造函数 赋值函数 析构函数_29

程序执行结果为:

拷贝构造函数、拷贝函数、析构函数_拷贝构造函数 赋值函数 析构函数_30

分析:

这个程序需要注意的仍旧是fun()函数中执行return时生成临时对象,执行完return后,析构临时对象


C++静态成员和临时对象

拷贝构造函数、拷贝函数、析构函数_拷贝构造函数 赋值函数 析构函数_31

拷贝构造函数、拷贝函数、析构函数_拷贝构造函数 赋值函数 析构函数_32

执行结果为:

拷贝构造函数、拷贝函数、析构函数_拷贝构造函数 赋值函数 析构函数_33

分析:

35行,human h1; 调用默认构造函数,且human_num++,此时human_num=1;

36行,h1.print(); 此时human_num还是等于1;

37行,human h2 = f1(h1); 先看f1()函数,在形参声明处,有human x 那么,首先用h1构造一个临时对象,那么需要构造拷贝构造函数,而拷贝构造函数中,并没有对human_num做任何处理,那么human_num还是等于1,因此f1()函数中

x.print()输出的human_num=1,执行return x时,调用拷贝构造函数,执行return x完毕后,调用析构函数(此时析构的是用h1构造的那个临时对象),析构函数中对human_num进行了处理,human_num--,且析构函数中还调用print()函数,则此时human_num=0。随后,返回的临时对象复制给h2,h2.print()输出的human_num=0。

退出test()函数时,要对h2、h1进行析构,先析构h2,且析构函数中有human_num--,因此human_num=-1,再析构h1,则human_num=-2


临时对象

拷贝构造函数、拷贝函数、析构函数_拷贝构造函数 赋值函数 析构函数_34

拷贝构造函数、拷贝函数、析构函数_拷贝构造函数 赋值函数 析构函数_35

执行结果为:

拷贝构造函数、拷贝函数、析构函数_拷贝构造函数 赋值函数 析构函数_36

分析:

第35行,Test t1(1); 首先调用带参数的构造函数生成一个对象t1,num=1

第36行,fun1(t1); 在fun1的形参声明中,首先调用拷贝构造函数生成一个临时对象A,此时num=1,退出fun1()函数,则需要对对象A进行析构。

第37行,fun1(2); 调用带参数的构造函数生成一个临时对象B,此时num=2,退出fun1()函数时,则需要对对象B进行析构。

第38行,t1=fun2(); fun2()中,有Test t(3),首先调用带参数的构造函数生成一个对象t,此时num=3,执行return t时,调用拷贝构造函数,生成临时对象C,执行return t完毕后,将被拷贝的对象t析构。然后执行t1=fun2();后,再将拷贝出来的对象C进行析构。最后,test()函数执行完毕时,再将t1析构。