1. C++拷贝构造函数(复制构造函数)
拷贝和复制是一个意思。
对于计算机来说,拷贝是指用一份原有的、已经存在的数据创建出一份新的数据,最终的结果是多了一份相同的数据。
在C++中,拷贝是指用已经存在的对象创建出一个新的对象,从本质上讲,对象也是一份数据,因为它会占用内存。
拷贝是在对象的初始化阶段进行的,也就是用其他对象的数据初始化新对象的内存。
什么是初始化,与赋值有什么不同?
在定义的同时进行赋值叫做初始化,定义完成以后再赋值(不管在定义的时候有没有赋值)就叫做赋值。初始化就是首次对内存赋值,初始化只能有一次,赋值可以有多次。
拷贝构造函数
拷贝构造函数只有一个参数,而且必须是当前类的引用,可以是const引用,也可以是非const引用,一般都是用const引用,含义更加明确,并且添加const限制后,可以将const或非const对象传递给形参,因为非const类型可以转换为const类型,但是const类型不能转换为非const类型。
拷贝构造函数的形参为什么必须是引用类型?
在调用拷贝构造函数时,会将另外一个对象直接传递给形参,这本身就是一次拷贝,会再次调用拷贝构造函数,然后又将一个对象直接传递给了形参,……这个过程会一直持续下去,陷入死循环。
默认拷贝构造函数
如果类不持有数据指针、动态分配内存、打开文件、网络连接等资源,默认拷贝构造函数就够用了,没有必要再显示定义一个。
以拷贝的方式初始化一个对象时会调用拷贝构造函数。
string 类对象的初始化都是用的拷贝方式。例如:
string s1 = "http://c.biancheng.net";
string s2 = s1;
string s3(s1);
string s4 = s1 + s2;
上面的s1、s2、s3、s4都是使用拷贝方式来初始化的。对于s1表面上看起来是将一个字符串直接赋值给了s1,实际上在内部进行了类型转换,将const char *
类型转换为string类型后才赋值。
2. 什么时候会调用拷贝构造函数
- 用类的一个对象去初始化另一个对象时;
- 当函数的形参是类的对象时(也就是值传递),引用传递不会调用拷贝构造函数;
- 当函数返回值是类的对象时。
3. C++深拷贝和浅拷贝(深复制和浅复制)
浅拷贝
对于基本的数据类型和简单对象,他们之间的拷贝非常简单,就是按位复制内存,这种默认的拷贝行为就是浅拷贝,这和memcpy()函数的调用效果非常类似。
int a=10;
int b=a;
深拷贝
将对象所持有的其他资源一并拷贝的行为叫做深拷贝,必须显示的定义拷贝构造函数才能达到深拷贝的目的。深拷贝会将原有对象的所有成员变量拷贝给新对象,还会为新对象再分配一块内存,并将原有对象所持有的内存也拷贝过来,这样能保证原有对象和新对象所持有的动态内存都是相互独立的,更改一个对象的数据不会影响另一个对象。
深拷贝的例子比比皆是,标准模板库中的string、vector、stack、map等都是必须使用深拷贝的。
怎么判断是深拷贝还是浅拷贝
- 当一个类拥有指针类型的成员变量时,那么绝大部份情况下就需要深拷贝。因为只有这样才能将指针指向的内容再复制一份出来,让原有对象和新对象相互独立,彼此不影响。
- 如果创建对象时需要进行一些预处理工作,比如统计创建过的对象数目、记录对象创建的时间等,这时就必须使用深拷贝。
4. C++重载=(赋值运算符)
当以拷贝的方式初始化一个对象时,会调用拷贝构造函数;当给一个对象赋值时,会调用重载过的赋值运算符。
即使我们没有显式的重载赋值运算符,编译器也会以默认地方式重载它。默认重载的赋值运算符功能很简单,就是将原有对象的所有成员变量一一赋值给新对象,这和默认拷贝构造函数的功能类似。
当类中有指针变量、动态内存分配等,需要显示重载赋值运算符。
深拷贝实验
记住深拷贝必须显示定义拷贝构造函数才能实现。
#include<iostream>
using namespace std;
//自定义Array类,实现变长数组
class Array{
public:
Array(int len); //普通构造函数
Array(Array &arr); //拷贝构造函数
~Array(); //析构函数
public:
int operator[](int i) const {return m_p[i];} //获取元素(读操作)
int &operator[](int i) {return m_p[i];} //写入元素
int length() const {return m_len;}
Array &operator=(const Array &arr); //重载赋值运算符,注意返回类型和形参类型
private:
int m_len;
int *m_p;
};
Array::Array(int len):m_len(len)
{
m_p=(int*)malloc(sizeof(int)*m_len);
}
/*拷贝构造函数(复制构造函数)
作用:
1. 将原有对象的所有成员拷贝给新对象;
2. 为新对象分配新的内存空间;
3. 将原有对象所持有的内存拷贝给新对象。
这样做能保证新对象与原有对象持有的动态内存相互独立,修改一个
对象的数据不会影响另一个对象。
注意拷贝构造函数的形参必须是当前类的引用
*/
Array::Array(Array &arr)
{
this->m_len=arr.m_len;
this->m_p=(int*)malloc(sizeof(int)*(this->m_len));
memcpy(this->m_p,arr.m_p,m_len*sizeof(int));
}
Array::~Array()
{
free(m_p);
}
//重载赋值运算符
/*如果没有显示的重载赋值运算符,编译器也会以默认的方式重载它。
默认重载的赋值运算符很简单,就是将原有对象的成员变量一一赋值给新对象。
这类似于默认的拷贝构造函数,同理,当类持有其他类似动态内存、数据指针等资源,
必须要显示重载赋值运算符,这样才能将原有对象的所有数据赋值给新对象。
1)operator=()的返回类型是Array &,即当前类的引用,这样可以避免返回
数据时调用拷贝构造函数,还能达到连续赋值的目的
2)operator=()的形参类型是const Array &,这样能避免在传参时调用拷贝
构造函数,还能够同时接受const类型和非const类型的实参;
*/
Array &Array::operator=(const Array &arr)
{
if(this!=&arr){ //判断是否给同一个对象赋值
this->m_len=arr.m_len;
free(m_p);
this->m_p=(int*)malloc(sizeof(int)*this->m_len);
memcpy(this->m_p,arr.m_p,m_len*sizeof(int));
}
return *this; //返回当前对象,即新对象
}
void display(const Array &arr)
{
int len=arr.length();
for(int i=0;i<len;++i){
if(i==len-1){
cout<<arr[i]<<endl;
}
else{
cout<<arr[i]<<" ";
}
}
}
int main(int argc,char **argv)
{
Array arr1(10);
for(int i=0;i<10;++i){
arr1[i]=i;
}
//定义的同时赋值叫做初始化
//这里会调用拷贝构造函数
Array arr2=arr1;
//如果不使用深拷贝(也就是不显示定义拷贝构造函数,而使用默认拷贝构造函数),对新对象数据的修改也会影响原有对象
arr2[5]=100;
arr2[3]=32;
display(arr1); //输出0 1 2 3 4 5 6 7 8 9
display(arr2); //输出0 1 2 32 4 100 6 7 8 9
Array arr3(5);
for(int i=0;i<5;++i){
arr3[i]=i;
}
display(arr3); //输出0 1 2 3 4
//定义完成后的赋值行为叫做赋值,不是初始化。
//这里会调用重载赋值运算符
arr3=arr2;
display(arr3); //输出0 1 2 32 4 100 6 7 8 9
arr3[2]=10; //修改arr3的数据不会影响arr2的数据
arr3[4]=200;
display(arr2); //输出0 1 2 32 4 100 6 7 8 9
display(arr3); //输出0 1 10 32 200 100 6 7 8 9
return 0;
}
![](https://img.haomeiwen.com/i423313/898d2e94aedb0a08.png)
上述对象之间的初始化与赋值,如果不使用深拷贝,那么对新对象数据的修改也会影响原有对象。
网友评论