本文主要是对拷贝构造函数和赋值函数已经深拷贝、浅拷贝的总结归纳。
拷贝构造函数
如果构造函数第一个参数为自身类类型的引用,且任何额外参数都具有默认值,则此构造函数为拷贝构造函数。
class Foo
{
public:
Foo(); //默认构造函数
Foo(const Foo&) //拷贝构造函数
};
拷贝构造函数的第一个参数必须为一个引用。默认情况下如果没有显式定义拷贝构造函数,编译器将生成默认拷贝构造函数,这一点和构造函数相似。
我们可以使用delete删除拷贝构造函数。这样对象不能通过值传递,也不能进行赋值。需要注意析构函数不能够使用delete删除
class Person
{
public:
Person(const Person& p) = delete;
Person& operator=(const Person& p) = delete;
private:
int age;
string name;
};
拷贝构造函数的调用场景:
- 将一个对象作为函数参数
- 函数返回值为一个非引用型对象
- 使用一个对象初始化另一个对象。
拷贝构造函数与赋值函数
相似之处都是将一个对象给另一个对象赋值,区别在于拷贝构造函数是将对象赋值给一个新的实例,而赋值函数是赋值给一个已存在的实例。
可以结合下面代码理解。
代码如下:
class Person
{
public:
Person(){}
Person(const Person& p)
{
cout << "Copy Constructor" << endl;
}
Person& operator=(const Person& p)
{
cout << "Assign" << endl;
return *this;
}
private:
int age;
string name;
};
void f(Person p)
{
return;
}
Person f1()
{
Person p;
return p;
}
int main()
{
Person p;
Person p1 = p; // 1
Person p2;
p2 = p; // 2
f(p2); // 3
p2 = f1(); // 4
Person p3 = f1(); // 5
return 0;
}
对于第5个暂时有点懵。。按照上面规则分析,应该是会调用拷贝构造函数。但是只出现了四个结果,而且都是对应前四个编号。利用gcc编译器测试发现将对象作为函数值返回并没有调用拷贝构造函数。
深拷贝浅拷贝
浅拷贝
对于没有显式声明拷贝构造函数的类,编译器会生成一个默认拷贝构造函数,这个默认拷贝构造函数会对拷贝对象进行简单拷贝。在大多数情况下这样问题不大,当存在动态变量(例如指针)时。这就是浅拷贝的问题。
看下面一段代码
class Rect
{
public:
Rect() // 构造函数,p指向堆中分配的一空间
{
p = new int(100);
}
~Rect() // 析构函数,释放动态分配的空间
{
if(p != NULL)
{
delete p;
}
}
private:
int width;
int height;
int *p; // 一指针成员
};
int main()
{
Rect rect1;
Rect rect2(rect1); // 复制对象
return 0;
}
在析构的时候会出现问题,因为并没有为rect2的指针,单独分配空间,但是析构的时候却删除了两遍,所以会出现问题。并且如果你期中一个对象的值改变了,另外一个对象的值也随着一起改变。
如下图所示。
深拷贝
需要单独为动态成员分配空间,因此需要将复制构造函数改为
Rect(const Rect &r)
{
width=r.width;
height = r.height;
p= new int[100]
}
为每一个动态变量单独分配空间
总结
拷贝构造函数和赋值运算符的行为比较相似,却产生不同的结果;拷贝构造函数使用已有的对象创建一个新的对象,赋值运算符是将一个对象的值复制给另一个已存在的对象。区分是调用拷贝构造函数还是赋值运算符,主要是否有新的对象产生。
关于深拷贝和浅拷贝。当类有指针成员或有动态分配空间,都应实现自定义的拷贝构造函数。提供了拷贝构造函数,最后也实现赋值运算符。-