#include <QCoreApplication>
#include "stdio.h"
#include "string.h"
class PERSON
{
public:
int age;
char* name;
PERSON()
{
printf("construct with 0 para\n");
age = 1;
name = new char[7];//7=strlen(""NoName)+1;
strcpy(this->name, "NoName");
}
PERSON(int age_tmp, const char* name_tmp)
{
printf("construct with 2 para\n");
age = age_tmp;
int name_tmp_len = strlen(name_tmp)+1;//strlen不计\0
name = new char[name_tmp_len];
strcpy(this->name, name_tmp);//应当加入name_tmp的字符串合法性检查,这里略了
}
~PERSON()
{
printf("dis_construct\n");
delete [] name;//删除数组内存堆
}
PERSON(PERSON &other)
{
printf("copy construct\n");
this->age = other.age;
int other_len = strlen(other)+1;//strlen不计\0
this->name = new char[other_len];
strcpy(this->name, other.name);
}
PERSON& operator =(const PERSON & other)//赋值运算符
{
printf("operator =\n");
this->age = other.age;
strcpy(this->name, other.name);
return *this;
}
};
void person_age_add(PERSON pers)
{
pers.age++;
}
PERSON get_person_age(void)
{
printf("creat local var\n");
PERSON a(10,"lucy");
printf("creat local person completed\n");
return a;
}
int main()
{
printf("start\n");
printf("part a:\n");
PERSON a(20,"tom");
printf("\npart b:\n");
PERSON b=a;
printf("\npart c:\n");
PERSON c(a);
printf("\npart d-1:\n");
PERSON d;
printf("part d-2:\n");
d = a;
printf("\npart transfer parameter:\n");
person_age_add(a);
printf("\npart transfer ret_val:\n");
a = get_person_age();
printf("\n end\n");
return 0;
}
以上程序在QT5.6中可以编译运行,
结果如下:
**************************************************************************************************************
问题:什么时候拷贝构造函数会被调用?
(1)一个对象以值传递的方式传入函数体
(2)一个对象以值传递的方式从函数返回
(3)一个对象需要通过另一个对象进行初始化
当然,情况(1)(2)本质上都属于情况(3),都是情况(3)的具体应用。以把实参传入形参的过程为例,从汇编侧面上看,形参位于内存栈中,在调用这类函数时,第一步就是:把实参“深拷贝”到内存栈中,所谓深拷贝,就是把对象中的指针成员指向的内容一块拷贝到栈中(同时该形参的指针成员的指向也被修改为指向栈中的内容),这样才能保证函数内收到的形参和实参是一模一样的(除了指针成员本身的值以外);如果实参到形参的传递过程使用了浅拷贝,后果是:形参的指针成员的值和实参的指针成员的值是一模一样的,也即,形参的指针成员竟然指向了实参指针成员指向的位置,那么函数体内如果对形参指针成员指向的内容进行了修改操作,那么等函数返回以后,实参指针成员指向的内容也就变了!这种恶劣的情形同时还伴随着另外两种恶劣情形:①被赋值的指针成员的值被修改掉了,那么它原先指向的内存将无法被释放,内存泄漏,同时这也导致了情况②,②赋值和被赋值的两个类指针成员的值指向了同一块内存,两个类析构时会释放同一块内存,同一块内存释放两次将导致不可预知的结果。
传递返回值和传递实参,情形是一样的。
由上述分析过程可见,对于带有指针成员的对象,作为实参传入函数时必须使用深拷贝,然而,实参指针成员指向的区域有多大,拷贝多少内容入栈,这些东西编译器是不知道的,这时必须靠程序员编写的深拷贝函数来明确,这个深拷贝函数就是拷贝构造函数,例如上面PERSON(PERSON &other)。
结论:
1、值传递时:实参传入形参,或者传回返回值的过程,这两种传递过程就是拷贝构造函数的执行过程,如果程序员没有人为地定义拷贝构造函数,编译器会默认生成一个浅拷贝的拷贝构造函数。因此,对于有指针成员的类,程序员必须手工创建深拷贝的拷贝构造函数,否则void func(PERSON per); fun(a);,对形参per的指针成员指向内容的操作,会影响实参a。
2、编译器默认生成的拷贝构造函数,功能与默认生成的重载等号运算符完全一样,都是浅拷贝。因此,对于有指针成员的类,程序员必须手工重载深拷贝的等号运算符,否则使用了a=b之后,对a的指针成员指向的内容的操作会影响b。
3、手工创建的重载等号运算符函数、拷贝构造函数,除了要手动拷贝指针成员指向的内容以外,普通的成员变量也得一一传值。