C&C++   发布时间:2022-04-01  发布网站:大佬教程  code.js-code.com
大佬教程收集整理的这篇文章主要介绍了拷贝构造器(深拷贝与浅拷贝)大佬教程大佬觉得挺不错的,现在分享给大家,也给大家做个参考。

一. 概述

复习巩固学习过的知识C++拷贝构造器。

环境:Centos7 64位,g++ 4.8.5

 

二. 代码与验证

1. 构造与拷贝构造

拷贝构造器(copy constructor)的地位与构造器(constructor)的地位是一样的,都是由无到有的创建过程。拷贝构造器,是由同类对象创建新对象的过程。

通过下面的代码验证几种情况。类A中自实现了构造器,拷贝构造器,析构器。

第28行、第32行代码调用 了构造函数,第29行代码调用了拷贝构造函数,这3行代码比较好理解。

第30行,调用了拷贝构造函数,一时有点不好理解,感觉有点像是调用了赋值运算符函数。但是通过运行结果,可以看到它确实是调用了拷贝构造函数。

可以再回顾一下上面的这句话“由同类对象创建新对象”,可能会更好地帮助理解。

 1 #include <iostream>
 2 
 3 using namespace std;
 4 
 5 class A
 6 {
 7     public:
 8         A()
 9         {
10             cout<<"constructor A()"<<endl;
11         }
12 
13         A(const A &another)
14         {
15             cout<<"A(const A &another)"<<endl;
16         }
17 
18         ~A()
19         {
20             cout<<"~A()"<<endl;
21         }
22     protected:
23         int m_a;
24 };
25 
26 int main()
27 {
28     A a1;      // constructor  构造
29     A a2(a1);  // copy constructor  拷贝构造
30     A a3 = a1; // copy constructor  拷贝构造
31 
32     A a4;     // constructor  构造
33     a4 = a1;  // assign
34 
35     return 0;
36 }

运行结果如下:

拷贝构造器(深拷贝与浅拷贝)

 再结合下面代码以进一步理解一下

1 int a = 0;  // 初始化
2 a = 10;     // 赋值
3 int b = a;  // 初始化

 

2. 验证,不自实现拷贝构造器时会发生什么

注释掉类A中的自实现的拷贝构造函数第15行--第18行代码。

通过运行结果,可看到对象a1与a2调用dis()方法,两者打印结果一致,说明第40行代码的确是执行了拷贝构造。

注:此时是有系统提供的默认的拷贝构造器。

 1 #include <iostream>
 2 
 3 using namespace std;
 4 
 5 class A
 6 {
 7     public:
 8         A(int x = 10)
 9             :m_a(X)
10         {
11             cout<<"constructor A()"<<endl;
12         }
13 
14         /*
15         A(const A &another)
16         {
17             cout<<"A(const A &another)"<<endl;
18         }
19         */
20 
21         ~A()
22         {
23             cout<<"~A()"<<endl;
24         }
25 
26         void dis()
27         {
28             cout<<"@H_601_68@m_a: "<<m_a<<endl;
29         }
30     protected:
31         int m_a;
32 };
33 
34 int main()
35 {
36     A a1(42);
37     a1.dis();
38 
39     cout<<"----------"<<endl;
40     A a2(a1);
41     a2.dis();
42 
43     return 0;
44 }

运行结果如下:

拷贝构造器(深拷贝与浅拷贝)

 

3. 说明

1)系统提供了默认的拷贝构造器,拷贝的格式比较固定,一经自实现,默认的将不复存在;

2)此拷贝构造器不是空的,而是提供了一个等位拷贝机制。等位拷贝不包含成员函数;

3)系统提供的拷贝构造函数,是一种浅拷贝,shallow copy;

4)深拷贝,deep copy。如果对象中不含有堆上的空间(指针指向的堆上的空间),此时浅拷贝可以满足需求,不需要自实现。但如果对象中含有堆上的空间,此时浅拷贝不能满足需求,就需要自实现了(申请内存空间后再进行拷贝)。因为浅拷贝会带来重析构(double freE)的问题。

 

拷贝构造格式,如下,another可以写成自己习惯的名称。

注:同类对象方法间,进行传参,可以访问其私有成员,其它则不行(同类间无私处,异类间有友元----老司机总结的结论)。

1 A(const A &another)
2 {
3     m_a = another.m_a;
4 }

 

4. 关于重析构double free的验证

上面第4)点,通过以下代码验证一下。类A中自实现了构造器和析构器。拷贝构造函数保持系统默认。构造函数中,给成员变量m_a申请了内存空间,并向其拷贝了字符串。类型最好转换下,不过这样也通过了编译。先不改了。

从运行结果可以看到double free的报错,报错的其它内容都看不太懂,先不管。

 1 #include <iostream>
 2 #include <cString>
 3 
 4 using namespace std;
 5 
 6 class A
 7 {
 8     public:
 9         A()
10         {
11             m_a = new char[100];
12             strcpy(m_a, "C++ is thE intersTing language.");
13             cout<<"constructor A()"<<endl;
14         }
15 
16         ~A()
17         {
18             delete []m_a;
19         }
20 
21         void dis()
22         {
23             cout<<"@H_601_68@m_a: "<<m_a<<endl;
24         }
25     protected:
26         char *@H_302_38@m_a;
27 };
28 
29 int main()
30 {
31     A a1;
32     a1.dis();
33 
34     cout<<"----------"<<endl;
35     A a2(a1);
36     a2.dis();
37 
38     return 0;
39 }

运行结果如下:

拷贝构造器(深拷贝与浅拷贝)

  说明

对象a1、a2中的m_a指向了同一块堆空间,对象销毁,析构的时候析构了两次,所以报错double free。下面通过代码再验证一下。

 

5. 再次验证重析构

类A中增加setStr()方法,25-28行代码。第47行代码,对象a1调用setStr()方法修改对象a1中的成员变量m_a。第48行代码,a2调用dis()方法打印m_a。第50行增加了一个死循环,暂不让析构函数执行。

 1 class A
 2 {
 3     public:
 4         A()
 5         {
 6             m_a = new char[100];
 7             strcpy(m_a, "C++ is thE intersTing language.");
 8             cout<<"constructor A()"<<endl;
 9         }
10 
11         /*
12         A(const A &another)
13         {
14             m_a = new char[strlen(another.m_a)+1];
15             strcpy(m_a, another.m_a);
16             cout<<"A(const A &another)"<<endl;
17         }
18         */
19 
20         ~A()
21         {
22             delete []m_a;
23         }
24 
25         void setStr()
26         {
27             strcpy(m_a, "php is a intersTing language.");
28         }
29 
30         void dis()
31         {
32             cout<<"@H_601_68@m_a: "<<m_a<<endl;
33         }
34     protected:
35         char *@H_302_38@m_a;
36 };
37 
38 int main()
39 {
40     A a1;
41     a1.dis();
42 
43     cout<<"----------"<<endl;
44     A a2(a1);
45     a2.dis();
46 
47     a1.setStr();  // modify m_a
48     a2.dis();
49 
50     while(1);
51 
52     return 0;
53 }

 运行结果如下:

拷贝构造器(深拷贝与浅拷贝)

 对象 a2中的m_a居然也被修改了。佐证了"对象a1、a2中的m_a指向了同一块堆空间"。拷贝构造,只是将a1中m_a的地址拷贝给了a2中的m_a,但两者均指向同一块堆空间。

 

6. 深拷贝。对于含有堆上的空间,自实现拷贝构造

将上面代码块的第12到17行代码去掉注释,

1 A(const A &another)
2 {
3     m_a = new char[strlen(another.m_a)+1];
4     strcpy(m_a, another.m_a);
5     cout<<"A(const A &another)"<<endl;
6 }

执行结果如下

拷贝构造器(深拷贝与浅拷贝)

 a2.dis(),打印是对象a2的成员变量m_a指向的重新申请的内存空间的内容。

此时,上面代码块注释掉第50行死循环的代码,运行也不会报错了。

现在,对象a1与a2中的m_a是不同的地址,并且它们指向不同的堆空间,但是它们的内容是一样的。调用析构函数时,分别free不同的m_a指向的各自的堆空间,也就不会有double free的问题了。

下面这张图可以帮助理解。这里的m_a其实就是图中的_str。

拷贝构造器(深拷贝与浅拷贝)

 

7. 拷贝构造器的适用场景

主要是用于传参和返回。

以下简单地说一下传参的问题

在上面代码块中,添加一个全局函数

1 void foo(A aa)
2 {
3     cout<<"void foo(A aa)"<<endl;
4 }

在main函数中,调用此全局函数时

 1 int main()
 2 {
 3     A a1;
 4     a1.dis();
 5 
 6     cout<<"----------"<<endl;
 7     foo(a1);
 8 
 9     return 0;
10 }

运行结果如下:

拷贝构造器(深拷贝与浅拷贝)

 从结果可看到,在传参时,调用了拷贝构造函数。也就是,第7行代码,在调用foo(a1)函数时,将对象a1拷贝给了函数foo形参aa。这种传参方式,调用了拷贝构造函数。

如果我们换成引用试试,即将全局函数的形参修改为A &aa,即为void foo(A &aa)。运行结果如下:

拷贝构造器(深拷贝与浅拷贝)

 此时,没有调用拷贝构造函数。传引用,其实也就是在传递对象本身,也不会涉及到拷贝的问题了。在这种情形下,传引用比传对象开销要相对小点。

 

暂时就啰嗦这么多了。

 

 参材料:

《C++基础与提高》  王桂林

 

大佬总结

以上是大佬教程为你收集整理的拷贝构造器(深拷贝与浅拷贝)全部内容,希望文章能够帮你解决拷贝构造器(深拷贝与浅拷贝)所遇到的程序开发问题。

如果觉得大佬教程网站内容还不错,欢迎将大佬教程推荐给程序员好友。

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
如您有任何意见或建议可联系处理。小编QQ:384754419,请注明来意。
标签: