c++类——复制构造函数(含深浅拷贝)
本文最后更新于:1 年前
¶(一)引言:
对于普通类型,复制是极其简单的:
1 |
|
但对于内部含有多个数据成员的类对象,其结构复杂,复制也相对复杂。
这时就需要调用复制构造函数来完成拷贝过程。
¶(二)概念:
复制构造函数又名拷贝构造函数,是一种特殊的构造函数,它通过编译器调用完成基于同一类的其他对象的构造及初始化。
其形参是唯一的,且只能是本类的引用,一般情况下会在前面加const引用来保证被复制的类对象在复制过程中不被修改。
-
如果形参是值传递,将会发生无穷递归,即被复制的类对象需要先复制出一个临时对象传入复制构造函数,然后临时对象又需要调用复制构造函数产生新的临时对象......
-
如果形参是指针传递,能够实现要求,但是会导致歧义。
在类中如果没有显式给出复制构造函数时,则编译器自动给出一个默认复制构造函数。如果有自己定义的构造函数(包括复制构造函数),则按函数重载的规律,调用合适的构造函数。
¶(三)调用时机:
-
对象以值传递的方式传入函数参数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39#include <iostream>
using namespace std;
class Time
{
private:
int hour;
int minute;
int sec;
public:
Time() { hour = minute = sec = 0; }
Time(int i1, int i2, int i3) :hour(i1), minute(i2), sec(i3) {}
Time(const Time& t)
{
hour = t.hour;
minute = t.minute;
sec = t.sec;
cout << "复制构造函数" << endl;
}//复制构造函数
void set_time()
{
cin >> hour >> minute >> sec;
return;
}
void show_time()
{
cout << hour << ":" << minute << ":" << sec << endl;
}
};
void test(Time t)
{
return;
}
int main()
{
Time t1(4, 5, 6);
test(t1);//发生复制
t1.show_time();
return 0;
}调用test()时,会进行以下步骤:
-
Time对象t1传入test形参时,产生临时对象temp。
-
调用复制构造函数将t1的值赋值给temp。即有Time temp(t1)发生。
-
在test函数本身执行完毕后,将temp析构掉
-
temp本质上是test的局部变量,生命周期和test相同。
-
-
对象以值传递的方式从函数返回。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40#include <iostream>
using namespace std;
class Time
{
private:
int hour;
int minute;
int sec;
public:
Time() { hour = minute = sec = 0; }
Time(int i1, int i2, int i3) :hour(i1), minute(i2), sec(i3) {}
Time(const Time& t)
{
hour = t.hour;
minute = t.minute;
sec = t.sec;
cout << "复制构造函数" << endl;
}//复制构造函数
void set_time()
{
cin >> hour >> minute >> sec;
return;
}
void show_time()
{
cout << hour << ":" << minute << ":" << sec << endl;
}
};
Time test(int i1,int i2,int i3)
{
Time t(i1, i2, i3);
return t;//发生复制
}
int main()
{
Time t1(4, 5, 6);
t1 = test(10, 9, 8);
t1.show_time();
return 0;
}调用test至return时,会进行以下步骤:
-
在return处产生临时对象temp。
-
调用复制构造函数将test内创建的局部变量t的值赋值给temp。即有Time temp(t)发生。
-
在test函数本身执行到最后,将t析构掉
-
在test()执行完毕后(此时赋给t1已经结束了),析构temp(因其为临时对象)。
-
-
对象需要通过另外一个对象进行初始化。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35#include <iostream>
using namespace std;
class Time
{
private:
int hour;
int minute;
int sec;
public:
Time() { hour = minute = sec = 0; }
Time(int i1, int i2, int i3) :hour(i1), minute(i2), sec(i3) {}
Time(const Time& t)
{
hour = t.hour;
minute = t.minute;
sec = t.sec;
cout << "复制构造函数" << endl;
}//复制构造函数
void set_time()
{
cin >> hour >> minute >> sec;
return;
}
void show_time()
{
cout << hour << ":" << minute << ":" << sec << endl;
}
};
int main()
{
Time t1(4, 5, 6);
Time t2 = t1;//发生复制
t1.show_time();
return 0;
} -
当成员变量为类类型时。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37#include <iostream>
using namespace std;
class year
{
private:
int years;
public:
year() { years = 0; }
year(const year& y) { cout << "year复制构造函数" << endl; }//year的复制构造函数
};
class Time
{
private:
int hour;
int minute;
int sec;
year y;
public:
Time() { hour = minute = sec = 0; }
Time(int i1, int i2, int i3) :hour(i1), minute(i2), sec(i3) {}
void set_time()
{
cin >> hour >> minute >> sec;
return;
}
void show_time()
{
cout << hour << ":" << minute << ":" << sec << endl;
}
};
int main()
{
Time t1(4, 5, 6);
Time t2 = t1;//发生复制,且调用的是成员类的复制构造函数。
t1.show_time();
return 0;
}因为Time的复制构造函数不存在,且其成员类year中存在复制构造函数,所以,此时编译器合成一个Time类的复制构造函数,但是这个合成的复制构造函数的目的是向其中插入能够去调用类year的复制构造函数的代码。
如需要两个被同时调用,需要自己构造Time的复制构造函数,并且对成员类进行显式赋值。
代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44#include <iostream>
using namespace std;
class year
{
private:
int years;
public:
year() { years = 0; }
year(const year& y) {cout << "year复制构造函数" << endl;}//year复制构造函数
};
class Time
{
private:
int hour;
int minute;
int sec;
year y;
public:
Time() { hour = minute = sec = 0; }
Time(int i1, int i2, int i3) :hour(i1), minute(i2), sec(i3) {}
Time(const Time& t):y(t.y)
{
hour = t.hour;
minute = t.minute;
sec = t.sec;
cout << "复制构造函数" << endl;
}//复制构造函数
void set_time()
{
cin >> hour >> minute >> sec;
return;
}
void show_time()
{
cout << hour << ":" << minute << ":" << sec << endl;
}
};
int main()
{
Time t1(4, 5, 6);
Time t2 = t1;//发生复制,且调用了各自的复制构造函数
t1.show_time();
return 0;
}
¶(四)浅拷贝和深拷贝:
-
默认拷贝构造函数:
当我们自己未定义任何复制构造函数时,编译器会为我们自动生成一个默认拷贝构造函数,以保证需要复制对象时不会产生问题。
但是此函数是使用被复制对象的值对复制对象的数据成员进行赋值,不会复制静态数据成员(实际上不会进行任何操作),需要手动添加。
-
浅拷贝:
浅拷贝是指对象复制只对对象中的数据成员进行简单赋值(默认构造函数即为浅拷贝)。
浅拷贝的对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址 。如果其中一个对象改变了这个地址,就会影响到另一个对象。
浅拷贝中,一旦存在动态成员,将会产生大问题:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33#include <iostream>
using namespace std;
class Time
{
private:
int hour;
int minute;
int sec;
int* p;
public:
Time() { hour = minute = sec = 0; p = new int(10); }
Time(int i1, int i2, int i3) :hour(i1), minute(i2), sec(i3) { p = new int(5); }
~Time()
{
delete p;
}
void set_time()
{
cin >> hour >> minute >> sec;
return;
}
void show_time()
{
cout << hour << ":" << minute << ":" << sec << endl;
}
};
int main()
{
Time t1(4, 5, 6);
Time t2(t1);
t2.show_time();
return 0;
}运行此代码将会发生p指针指向的动态数组发生了多次释放。
分析此过程:
因为浅拷贝是将原对象或原数组的引用直接赋给新对象,新数组,新对象/数组只是原对象的一个引用。所以,两个不同对象的动态数组指针p地址值其实是相同的,它们指向的是同一个空间。
在销毁对象时,两个对象的析构函数对同一空间进行了两次释放,进而产生了指针悬挂错误。
我们所需要的是两个动态指针p指向两个不同空间,但是两个空间具有相同的值,而不是两个动态指针p指向同一空间。
此时需要进行深拷贝。
-
深拷贝:
深拷贝是指将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象,且修改新对象不会影响原对象。
深拷贝创建一个新的对象和数组,重新动态分配空间,将原对象的各项属性的“值”(数组的所有元素)拷贝过来,拷贝的是“值”而不是“引用”。
代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45#include <iostream>
using namespace std;
class Time
{
private:
int hour;
int minute;
int sec;
int* p;
public:
Time() { hour = minute = sec = 0; p = new int(10); }
Time(int i1, int i2, int i3) :hour(i1), minute(i2), sec(i3) { p = new int(10); }
Time(const Time& t)
{
hour = t.hour;
minute = t.minute;
sec = t.sec;
p=new int(*t.p);
cout << "复制构造函数" << endl;
}//复制构造函数
~Time()
{
if (p != NULL) {
cout << "析构"<<&p << endl;
delete p;
p = NULL;
}
}
void set_time()
{
cin >> hour >> minute >> sec;
return;
}
void show_time()
{
cout << hour << ":" << minute << ":" << sec << endl;
}
};
int main()
{
Time t1(4, 5, 6);
Time t2(t1);//发生复制
t2.show_time();
return 0;
}可以看到两次析构函数析构前的动态指针p指向地址不同,说明两个动态指针p各自指向不同的内存空间,且指向的内容相同,此时即有深拷贝。
-
防止浅拷贝发生:
声明一个私有复制构造函数,可以不定义,此时如果按值传递或或函数返回该类对象,不会发生浅拷贝错误,可以避免问题。