干货长文预警
这一篇,我们来解决对象的赋值与拷贝问题。赋值与拷贝,是程序设计过程中不可或缺的部分,对于基础类型,这两个概念相信大家早已熟知,这里不多赘述。
但是对于自定义类型的对象,拷贝与赋值就有这不同的含义:
拷贝发生在一个对象诞生的时候,基于另一个同类对象进行构造
而赋值则是将一个已存在的对象的内容传递给另一个已存在的对象。
虽然编译器提供了默认的拷贝与赋值函数,又由于对象的数据成员在被赋值或是拷贝时会有着诸如指针指向新内存这些默认函数不能实现的不同行为需求,因此赋值与拷贝操作有时需要重新定义其行为,以满足程序的具体要求。这也就引出了本篇的第二个关键问题:深拷贝与浅拷贝
本文思维导图:
拷贝构造函数
根据名称,不难知道拷贝构造函数是一种特殊的构造函数,它的任务就是用来拷贝对象。
默认拷贝构造函数
拷贝构造函数享有编译器赋予默认添加的vip特权。当程序中没有拷贝构造函数时,编译器会自动提供一个缺省的拷贝构造函数。这个拷贝构造函数的访问级别是公有的。
在进行拷贝时,默认的拷贝构造函数的行为是固定的,它有两种行为:
- 普通成员(内置类型,指针等)按位拷贝。
- 对象成员 执行其拷贝构造函数
对于普通成员,程序将按位进行复制进行拷贝,执行“浅复制”
对于对象成员,程序将调用它的拷贝构造函数进行复制,这个成员的复制的具体行为由其拷贝构造函数决定。
对于对象成员拷贝的解读,来看个粟子:
class A{
public:
A(){
}
A(const A& a){
cout << "A类拷贝构造函数" << endl << endl;}
};
class B
{
public:
B(){
}
A a; //对象成员,在默认拷贝构造函数中将被调用其拷贝构造函数
};
int main()
{
B b;
B c = b; //产生拷贝情况
}
运行结果:
表示在拷贝B类型对象过程中调用到了A类型拷贝构造函数
另外,我们还漏掉了一类情况:引用类型!!
将上述代码稍作修改,用来观察引用类型成员在默认拷贝构造函数中的行为:
class A{
public:
A(){
}
A(const A& a){
cout << "A类拷贝构造函数" << endl << endl;}
};
class B
{
public:
B():aa(*new A()){
} //引用类型必须进行初始化!!!
A a; //对象成员,在默认拷贝构造函数中将被调用其拷贝构造函数
A& aa;
};
int main()
{
B b;
B c = b; //产生拷贝情况
cout << "对象成员 " << &b.a << " " << &c.a << endl;
cout << "引用成员 " << &b.aa << " " << &c.aa << endl;
}
在主函数中打印两个对象的成员地址,用以区分其是否为同一对象。同时,该代码保留了上一份代码对A类拷贝构造函数的提示功能。其运行结果如下:
不难发现的是,对于引用型的成员,初始化拷贝时直接引用了被拷贝对象的引用,并没有创建新的引用。
考虑到引用在底层是使用指针实现的,相当于一个 A * const
类型指针,所以引用成员的拷贝行为就也可以看作是按位复制的结果,仍然是一种“浅拷贝”。
自定义拷贝构造函数
当默认的拷贝构造函数不能满足程序设计需求时,就需要程序员自己来实现拷贝构造函数了。
实现自定义的拷贝构造函数的第一步,就理解拷贝构造函数的书写形式。
拷贝构造函数的形式
重点放在前面
拷贝构造函数声明形式如下:
类名(cosnt 类名& );
实现形式如下:
类名 (const 类名& 引用对象名):初始化列表{构造函数方法体}
作为一种特殊