一、拷贝构造函数的性质
定义:
拷贝构造函数是一个成员函数,它用同一个类的一个对象初始化另一个对象。
语法:
classname (const classname &obj) {
// body of constructor
}
note: 当类中没有定义拷贝构造函数时,编译器默认提供一个拷贝构造函数,简单的进行成员变量的值赋值。
拷贝构造函数的作用:
1.从同一类型的另一个对象初始化一个对象。 |
2.将对象拷贝作为一个函数的参数。 |
3.拷贝一个对象以返回它的函数。 |
拷贝构造函数的意义:
浅拷贝 | 拷贝后对象的物理状态相同 |
深拷贝 | 拷贝后对象的逻辑状态相同 |
note:编译器提供的拷贝构造函数只进行浅拷贝。
什么时候调用拷贝构造函数:
1 .当类对象返回一个值; |
2.当类的对象被作为参数通过值传递(到函数)时; |
3.当一个类的对象是基于同一个类的另一个对象构造时; |
4.当编译器生成一个临时对象时 |
Example1:
#include <iostream>
using namespace std;
class Line {
public:
int getLength(void);
Line(int len); // 普通构造函数
Line(const Line &obj); // 拷贝构造函数
~Line(); // 析构函数
private:
int *ptr;
};
// 成员函数定义,包括构造函数
Line::Line(int len) {
cout << "普通构造函数分配指针ptr" << endl;
// 给指针ptr分配内存;
ptr = new int;
*ptr = len;
}
Line::Line(const Line &obj) { //obj是用于初始化另一个对象的对象的引用。
cout << "拷贝构造函数分配指针 ptr." << endl;
ptr = new int;
*ptr = *obj.ptr; // 拷贝值
}
Line::~Line(void) {
cout << "释放内存!" << endl;
delete ptr;
}
int Line::getLength(void) {
return *ptr;
}
void display(Line obj) {
cout << "line 的长度 : " << obj.getLength() << endl;
}
// Main 函数实现
int main() {
Line line(10); //调用普通构造函数 Line(int len);
display(line); //调用拷贝构造函数 Line(const Line &obj);
getchar();
return 0;
}
编译运行,结果如下:
调用拷贝构造函数的另一种方式 Line line2 = line1; 如下:
int main() {
Line line1(10);
Line line2 = line1; // 这也是调用拷贝构造函数
display(line1);
display(line2);
getchar();
return 0;
}
二、深拷贝与浅拷贝
Example2:
#include <stdio.h>
class Test
{
private:
int i;
int j;
int* p;
public:
int getI()
{
return i;
}
int getJ()
{
return j;
}
int* getP()
{
return p;
}
Test(const Test& t) //拷贝构造函数
{
i = t.i;
j = t.j;
p = new int;
*p = *t.p;
}
Test(int v)
{
i = 1;
j = 2;
p = new int;
*p = v;
}
void free()
{
delete p;
}
};
int main()
{
Test t1(3); //调用构造函数Test(int v)
Test t2(t1); //调用拷贝构造函数Test(const Test& t)
printf("t1.i = %d, t1.j = %d, *t1.p = %d\n", t1.getI(), t1.getJ(), t1.getP()); //*t1.p = 4887464
printf("t2.i = %d, t2.j = %d, *t2.p = %d\n", t2.getI(), t2.getJ(), t2.getP()); //*t2.p = 4887512
t1.free();
t2.free();
getchar();
return 0;
}
编译运行,
可见,t1 和 t2所指的地址空间是不同的 ,因为t2进行深拷贝。
我们不妨把注释掉下列代码:
Test(const Test& t) //拷贝构造函数
{
i = t.i;
j = t.j;
p = new int;
*p = *t.p;
}
然后编译运行:
可见,指针 t1 和 t2的地址相同,说明了t1 和t2 都指向了同一片内存空间。
问题分析:
①浅拷贝:默认构造函数只做浅拷贝。
t1 和t2 都指向了同一片内存空间:
当释放内存地址时,t1 和 t2释放的是同一块内存地址,此时会发生编译错误:
②深拷贝:只能与用户定义的复制构造函数。在用户定义的复制构造函数中,我们确保复制对象指向新的内存位置的指针(或引用)。
可见,Bag2 深拷贝 Bag1,Bag2指向了一片新的内存地址。
Question: 什么时候需要进行深拷贝?
当对象中有成员指代了系统中的资源:
1.成员指向了动态内存空间 |
2.成员打开了外存中的文件 |
3.成员使用了系统中的网络端口 |
一般性原则:
自定义拷贝构造函数,必然需要实现深拷贝!
三、拷贝构造函数 VS 赋值操作符
下面的两个语句哪个调用拷贝构造函数,哪个调用赋值操作符?
MyClass t1, t2;
MyClass t3 = t1; // ----> (1)
t2 = t1; // -----> (2)
答:
当一个已经初始化的对象从另一个现有对象分配一个新值时,调用赋值操作符:
t2 = t1;
//调用赋值操作符, 相当于 "t2.operator=(t1);"
当新对象从现有对象创建时,就会调用拷贝构造函数,作为现有对象的拷贝:
Test t3 = t1;
// 调用拷贝构造函数, 相当于"Test t3(t1);"
Test:
#include<iostream>
#include<stdio.h>
using namespace std;
class Test
{
public:
Test() {}
Test(const Test &t)
{
cout << "调用拷贝构造函数 " << endl;
}
Test& operator = (const Test &t)
{
cout << "调用赋值操作符 " << endl;
}
};
int main()
{
Test t1, t2;
t2 = t1; //调用赋值操作符
Test t3 = t1; //调用赋值操作符
getchar();
return 0;
}
编译运行,结果如下:
编译运行,结果如下:
<本文完>
Reference: