拷贝构造
何时使用
用一个最简单的例子来说明:
class People{
private:
int age;
char *name;
};
使用情况1: 对象作为值传递作为函数传入参数, 如:
bool is_adult(People zy){
return zy.get_age()>=18;
}
但当对象的引用作为函数传入参数,如:
bool is_adult(People &zy){
return zy.get_age()>=18;
}
就不用调用拷贝构造,这是很简单的逻辑,引用变量是对象的一个别名,也就是说,它是某个已存在变量的另一个名字,此时不必重新再创建一个对象。
使用情况2: 对象作为值传递作为函数返回值,如:
People older(People zy,People cmh){
if(zy.get_age()>cmh.get_age())
return zy;
return cmh;
}
此时返回cmh时作为对象值返回而不是引用返回,需要重新创建一个对象。
使用情况3: 在用一个已存在的对象来初始化一个未创建的对象时:
People cmh(zy);
People cmh=zy;
不管是用 = = =或者用 ( ) () ()此时均是调用拷贝构造函数而不是 = = =赋值重载符,因为此时 c m h cmh cmh对象未创建。因此最好用第一种括号写法而不是第二种等于号写法,避免混淆。
使用方法
参数: const & 对象
const 是因为要求传进来的对象不可改变。
使用&是因为当我们使用值传递的时候,深拷贝的第一种使用情况:“对象作为值传递作为函数参数”,拷贝构造函数也是一个函数,若传入参数是值传递而不是引用传递,则会再次调用一个拷贝构造函数,新的拷贝构造函数会调用一个新的拷贝构造函数…无限调用就陷入死循环了。
返回: 无返回值,此时为构造函数,构造函数不需要返回值。
简单模板:
People(const People &p){//const 引用 类型
//对非动态开辟的成员进行赋值
age=p.age;
//对指针类型的成员开辟空间再赋值
name=new char[strlen(p.name)];
strncpy(name,p.name,strlen(p.name));
}
深拷贝与浅拷贝
如果在类内无指针类型的成员,则深拷贝和浅拷贝没有区别直接进行值拷贝即可:
struct Patient{
int age;
int id;
char gender;
}
但是若像
P
e
o
p
l
e
People
People有名字这个字符指针的类,深拷贝和浅拷贝就有区别。
若我们自己不写一个深拷贝函数,则程序会自动执行浅拷贝构造函数,如在以上定义的
P
e
o
p
l
e
People
People类中,程序默认的浅拷贝构造函数为:
People(const People &p){
age=p.age;
name=p.name;
}
若采用浅拷贝构造,当主函数中内容为
People cmh(zy);
内存中会发生如下情况,cmh的name指针和zy的name指针均指向同一个地址。这样会发生的结果是:当zy析构时会将释放name指向的内存,相当于
0
x
0000
0x0000
0x0000地址存放的内容被销毁,此时zy.name仍然指向
0
x
0000
0x0000
0x0000,只是此处没有内容了。接下来当cmh析构时会尝试再次cmh.name释放,但是此时cmh.name指向的地址已经没有内容可供销毁,因此会发生报错。
总之动态开辟和销毁是配对的,new一次就需要delete一次, 浅拷贝会造成new一次而delete两次的错误,因此我们需要自己重写深拷贝,让cmh.name指向别的空间,而不指向zy.name指向的空间。
赋值重载
何时使用
对象已经存在,如下
c
a
s
e
1
case 1
case1的
=
=
=即调用了赋值重载函数。
而当对象在创建时,如下
c
a
s
e
2
case2
case2即使用了
=
=
=,也不会调用赋值重载函数,而是调用拷贝构造函数。
//case 1:
People cmh;
cmh=zy;
//case 2:
People cmh(zy);
使用方法
参数: const & 对象。
返回值: 对象引用
重载赋值函数的返回值和传入值都需要使用对象引用,因为它也是一个函数,若采用值传递会在赋值函数中再调用一次拷贝构造函数,浪费时间和空间。
简单模板:
People & operator=(const People& p){
age=p.age;
name=new char[strlen(p.name)];
strncpy(name,p.name,strlen(p.name));
return *this;
}