在C++中,3种对象需要拷贝,此时拷贝构造函数将会被调用。
1、一个对象以值传递的方式传入函数体
2、一个对象以值传递的方式从函数返回
3、一个对象需要通过另一个对象进行初始化
如下例:
输出结构为:
再如下例:
分析:
test类中buf是一个字符指针,带参数的构造函数中为它分配了一块堆内存来存放字符串,然后析构函数中又将堆内存释放。
main函数中,首先先构造一个对象,调用带参数的构造函数,因此t1.buf指向一块堆内存(记作A),然后,又用t1去构造t2,即对象复制,由于是使用默认拷贝构造函数,那就是简单的把两个对象的指针作赋值运算,即直接复制过去的,那么这个时候,t2.buf也指向了堆内存A。
程序崩溃会发生在main()函数退出对象析构时,两个对象那么久要产生两次析构,然而两个对象中的buf指针都指向了同一块内存,同一块堆内存被释放两次,那么导致了崩溃。
程序执行结果为:
(t1.buf==t2.buf)?yes
程序崩溃
要解决这个问题,可以在Test类中再增加一个拷贝构造函数:
增加了该拷贝构造函数后,在main()函数中Test t2=t1,即用对象t1去构造对象t2时,就不会调用默认拷贝构造函数了,而是使用用户重定义的拷贝构造函数,此时,函数中又为t2.buf分配了一块堆内存来存储字符串,那么此时,t1.buf和t2.buf分别指向了两块堆内存,析构时就不会发生程序崩溃了。
执行结果为:
写一个继承类的拷贝函数
分析:Derived类继承自Base类,因此在Derived类中不能使用obj.i或者Base::i的方式访问Base的私有成员i。
Derived类的拷贝构造函数只能使用Base(obj)的方式来 调用其基类的拷贝构造函数 来给基类的私有成员i初始化。
编写类String的构造函数、析构函数和赋值函数
运行结果:
之所以建立一个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++类各成员函数间关系
执行结果为:
C++类的临时对象
分析:
调用play()函数时,有两种情况:1、传入参数为整型数时,2、传入参数为类对象时
1、传入参数为整型数时,会先调用test类中的带参数的构造函数来产生一个临时对象。
这里有一点需要特别注意的是,play()函数中有return b,即返回一个test类对象。那么在返回前(return代码执行时)会调用test类的拷贝构造函数生成一个临时对象,然后在return代码执行完毕后,再调用test类的析构函数进行析构。
2、传入参数为类对象时,会先调用test类中的拷贝构造函数来产生一个临时对象,剩下的过程与1相同。
程序执行结果为:
分析结果:
调用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()同理
拷贝构造函数和析构函数
程序执行结果为:
分析:
这个程序需要注意的仍旧是fun()函数中执行return时生成临时对象,执行完return后,析构临时对象
C++静态成员和临时对象
执行结果为:
分析:
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
临时对象
执行结果为:
分析:
第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析构。