C++ 写时拷贝 2

什么情况下会用到c++中的拷贝构造函数】:

 1)用已经存在的同类的对象去构造出另一个新的对象

  2)当函数的形参是类的对象时,这时调用此函数,使用的是值的拷贝,也会调用拷贝构造函数
  3)当函数的返回值是类的对象时,这时当函数调用完后,会将函数的对象拷贝构造出一个临时的对象并传给函数的返回处

【浅拷贝】:(位拷贝(值拷贝))

1、概念:所谓的浅拷贝就是当在进行对象的复制时,只是进行对象的数据成员的拷贝,其中默认的拷贝构造函数也是浅拷贝。大多数情况下,使用浅拷贝是没有问题的,但是当出现动态成员,就会出现问题。

2、关于浅拷贝的使用举例:

[plain] view plain copy
  1. #include<iostream>  
  2. using namespace std;  
  3. class Test  
  4. {  
  5. public:  
  6.     //构造函数  
  7.     Test(int a)  
  8.         :_a(a)  
  9.     {}  
  10.     //拷贝构造函数  
  11.     Test(const Test& x)  
  12.     {  
  13.         _a = x._a;  
  14.     }  
  15.   
  16. private:  
  17.     int _a;  
  18. };  
  19. int main()  
  20. {  
  21.     Test b(10);  
  22.     Test c(b);  
  23.     return 0;  
  24. }  

3、浅拷贝的缺陷:

      浅拷贝对于指针成员不可行。多个对象共用同一块空间,同一内存地址,但是在调用析构函数释放空间的时候,多次调用析构函数,这块空间被释放了多次,此时程序就会崩溃。

【引用计数的拷贝】:

1、(怎么引入的)概念:因为浅拷贝的缺陷,所以在这个时候我们就引入了引用计数的拷贝。

     【说明】:引用计数的拷贝是用来解决浅拷贝存在的问题的,所以它也是一种浅拷贝

2、如何实现:我们为每个内存的字符数组添加一个引用计数pcount,即就是在构造函数申请空间的时候多申请出来4个字节。表示有多少个对象使用这块内存,有多少个对象使用,就让pcount值加1,当对象被析构的时候,让pcount值减1,当pcount值为0的时候,将这块内存释放掉。当然pcount也要实现内存共享,所以它也是一个堆中的数据,每个对象都有一个指向它的指针。

3、【说明】:但在此时,pcount的类型的选取,就会要有所考虑?

    1)如果选取int类型:(不采取)

[cpp] view plain copy
  1. #include<iostream>  
  2. using namespace std;  
  3. class String  
  4. {  
  5. public:  
  6.     //构造函数  
  7.     String(const char* ptr = "")  
  8.     {  
  9.         if(ptr == NULL)  
  10.         {  
  11.             _ptr = new char[1];  
  12.             _pcount = 1;  
  13.             *_ptr = '\0';  
  14.         }  
  15.         else  
  16.         {  
  17.             _pcount = 1;  
  18.             _ptr = new char[strlen(ptr)+1];  
  19.             strcpy(_ptr,ptr);  
  20.         }  
  21.     }  
  22.     //拷贝构造函数  
  23.     String(String& s)  
  24.         :_ptr(s._ptr)  
  25.         ,_pcount(s._pcount)  
  26.     {  
  27.         _pcount++;  
  28.     }  
  29.     //赋值运算符重载  
  30.     String& operator=(const String& s)  
  31.     {  
  32.         if(this != &s)  
  33.         {  
  34.             if(--_pcount == 0)  
  35.             {  
  36.                 delete[] _ptr;  
  37.                 //delete _pcount;  
  38.             }  
  39.             else  
  40.             {  
  41.                 _ptr = s._ptr;  
  42.                 _pcount = s._pcount;  
  43.                 (_pcount)++;  
  44.             }  
  45.         }  
  46.         return *this;  
  47.     }  
  48.     //析构函数  
  49.     ~String()  
  50.     {  
  51.         if((0 == --_pcount) && _ptr!= NULL)  
  52.         {  
  53.             delete[]_ptr;  
  54.             //delete _pcount;  
  55.             _ptr = NULL;  
  56.         }  
  57.           
  58.     }  
  59.     //重载[]  
  60.     char& operator[](size_t size)  
  61.     {  
  62.         if(--_pcount >1)  
  63.         {  
  64.             char* ptemp = new char[strlen(_ptr)+1];  
  65.             int pcount = 1;  
  66.             strcpy(ptemp,_ptr);  
  67.             _pcount--;  
  68.             _ptr = ptemp;  
  69.             _pcount = pcount;   
  70.         }  
  71.         return _ptr[size];  
  72.     }  
  73. private:  
  74.     char*_ptr;  
  75.     int _pcount;  
  76. };  
  77.   
  78. void FunTest()  
  79. {  
  80.     String s1("hello");  
  81.     String s2(s1);  
  82.     String s3(s2);  
  83.     s3 = s2;  
  84. }  
  85. int main()  
  86. {  
  87.     FunTest();  
  88.     return 0;  
  89. }  
调试:



(注意这里我将断点就走到s2,意在说明问题):本来增加s2的时候,两个对象的计数应该是一样的,但是现在一个是1,一个是2,不同步,我们了解到这两个对象的计数变量的地址是不一样的。说明此pcount是公共的,可以被多个对象同时访问。

    2)如果选取的是static类型的:(不采取)  

[cpp] view plain copy
  1. #include<iostream>  
  2. using namespace std;  
  3. class String  
  4. {  
  5. public:  
  6.     //构造函数  
  7.     String(const char* ptr = "")  
  8.     {  
  9.         if(ptr == NULL)  
  10.         {  
  11.             _ptr = new char[1];  
  12.             _pcount = 1;  
  13.             *_ptr = '\0';  
  14.         }  
  15.         else  
  16.         {  
  17.             _pcount = 1;  
  18.             _ptr = new char[strlen(ptr)+1];  
  19.             strcpy(_ptr,ptr);  
  20.         }  
  21.     }  
  22.     //拷贝构造函数  
  23.     String(String& s)  
  24.         :_ptr(s._ptr)  
  25.     {  
  26.         _pcount++;  //因为是静态的,所以直接进行计数的增值就可以了  
  27.     }  
  28.     //赋值运算符重载  
  29.     String& operator=(const String& s)  
  30.     {  
  31.         if(this != &s)  
  32.         {  
  33.             if(--_pcount == 0)  
  34.             {  
  35.                 delete[] _ptr;  
  36.                 //delete _pcount;  
  37.             }  
  38.             else  
  39.             {  
  40.                 _ptr = s._ptr;  
  41.                 _pcount = s._pcount;  
  42.                 (_pcount)++;  
  43.             }  
  44.         }  
  45.         return *this;  
  46.     }  
  47.     //析构函数  
  48.     ~String()  
  49.     {  
  50.         if((0 == --_pcount) && _ptr!= NULL)  
  51.         {  
  52.             delete[]_ptr;  
  53.             //delete _pcount;  
  54.             _ptr = NULL;  
  55.         }  
  56.           
  57.     }  
  58.     //重载[]  
  59.     char& operator[](size_t size)  
  60.     {  
  61.         if(--_pcount >1)  
  62.         {  
  63.             char* ptemp = new char[strlen(_ptr)+1];  
  64.             int pcount = 1;  
  65.             strcpy(ptemp,_ptr);  
  66.             _pcount--;  
  67.             _ptr = ptemp;  
  68.             _pcount = pcount;   
  69.         }  
  70.         return _ptr[size];  
  71.     }  
  72. private:  
  73.     char*_ptr;  
  74.     static int _pcount;  
  75. };  
  76. int String::_pcount = 0;  
  77. void FunTest()  
  78. {  
  79.     String s1("hello");  
  80.     String s2(s1);  
  81.     String s3(s2);  
  82.     s3 = s2;  
  83.     String s4("world");  
  84.     String s5(s4);  
  85. }  
  86. int main()  
  87. {  
  88.     FunTest();  
  89.     return 0;  
  90. }  
调试

  先走到s3,然后走到s4,用s4来构造s5,结果就不对了,走到s4的时候,计数器又变成了1,说明这5个对象公用一个pcount,不能实现引用计数

    3)那么我们这样想:如果一个对象第一次开辟空间存放字符串再开辟一块新的空间存放新的额引用计数,当它拷贝构造其它对象时让其它对象的引用计数都指向存放引用计数的同一块空间,pcount设置成int*就可以啦,但是这种方式有缺陷。(读者可以自己实现下)

       缺陷一:每次new两块空间,创建多个对象的时候效率比较低

       缺陷二:它多次分配小块空间,容易造成内存碎片化,导致分配不出来大块内存

4、代码实现:(所以我将优化版的代码贴出来,其实就是仿照new的底层实现,开辟一块空间,但是它的头几个字节用于计数)

[cpp] view plain copy
  1. <span style="font-size:18px;">#include<iostream>  
  2. using namespace std;  
  3. class String  
  4. {  
  5. public:  
  6.     String(char *ptr = "")  
  7.     {  
  8.         if(ptr == NULL)  
  9.         {  
  10.             _ptr = new char[strlen(ptr)+5];  
  11.             _ptr = new char[5];  
  12.             *(_ptr+4) = '\0';  
  13.         }  
  14.         else  
  15.         {  
  16.             _ptr = new char[strlen(ptr)+5];  
  17.             *((int*)_ptr) = 1;  
  18.             _ptr += 4;  
  19.             strcpy(_ptr,ptr);  
  20.         }  
  21.     }  
  22.     String(const String& s)  
  23.         :_ptr(s._ptr)  
  24.     {  
  25.         (*((int*)(_ptr-4)))++;  
  26.     }  
  27.     String& operator=(const String& s)  
  28.     {  
  29.         if(this != &s)  
  30.         {  
  31.             if(--(*((int*)(_ptr-4))) == 0)  
  32.             {  
  33.                 delete[]_ptr;  
  34.             }  
  35.             else  
  36.             {  
  37.                 _ptr = s._ptr;  
  38.                 ++(*(int*)(_ptr-4));  
  39.             }  
  40.         }  
  41.         return *this;  
  42.     }  
  43.     ~String()  
  44.     {  
  45.         if((_ptr != NULL) && ((--(*((int*)(_ptr-4)))) == 0))  
  46.         {  
  47.             delete[]_ptr;  
  48.             _ptr = NULL;  
  49.         }  
  50.     }  
  51. private:  
  52.     char* _ptr;  
  53. };  
  54. void Funtest()  
  55. {  
  56.     String s1("hello");  
  57.     String s2(s1);  
  58.     String s3(s2);   
  59.     s3 = s2;  
  60. }  
  61. int main()  
  62. {  
  63.     Funtest();  
  64.     return 0;  
  65. }</span>  

【深拷贝】:(址拷贝)

1、概念:采取在堆中申请新的空间来存取数据,这样数据之间相互独立。址拷贝。

2、举例:(string类中的拷贝构造函数)

[cpp] view plain copy
  1. <span style="font-size:18px;">String(const String& s)  
  2.     {  
  3.         _ptr = new char[strlen(s._ptr)+1];  
  4.         strcpy(_ptr,s._ptr);  
  5.     }</span>  

【写时拷贝】:

1、(如何引入的)概念:但是当其中一个对象改变它的值时,其他对象的值就会随之改变,所以此时我们采取这样一种做法,就是写时拷贝。

2、核心思想:

(写入时拷贝)如果有多个调用者同时要求相同资源(如内存或磁盘上的数据存储),它们会共同获取相同的指针指向的资源,直到某个调用者试图修改资源的内容时,系统才会真正复制一份专用副本给该调用者,而其他调用者所见到的最初的资源仍然保持不变。这过程中对其他调用者都是透明的。做法的优点:如果调用者没有修改该资源,就不会有副本被创建,因此多个调用者只是读取操作时可以共享同一份资源

(写时拷贝)指用浅拷贝的方法拷贝其他对象,多个指针指向同一块空间,只有当对其中一个对象修改时,才会开辟一个新的空间给这个对象,和它原来指向同一空间的对象不会收到影响。

3、做法:给要改变值的那个对象重新new出一块内存,然后先把之前的引用的字符数据复制到新的字符数组中,这就是写时拷贝。注意,同时还要把之前指向的内存的引用计数减1(因为它指向了新的堆中的字符数组),并在堆中重新new一个块内存,用于保存新的引用计数,同时把新的字符数组的引用计数置为1。因为此时只有一个对象(就是改变值的对象)在使用这个内存。

4、代码实现:

[cpp] view plain copy
  1. <span style="font-size:18px;">#include<iostream>  
  2. using namespace std;  
  3. class String  
  4. {  
  5. public:  
  6.     //构造函数  
  7.     String(const char* ptr = "")  
  8.     {  
  9.         if(ptr == NULL)  
  10.         {  
  11.             _ptr = new char[1];  
  12.             _pcount = new int(1);  
  13.             *_ptr = '\0';  
  14.         }  
  15.         else  
  16.         {  
  17.             _pcount = new int(1);  
  18.             _ptr = new char[strlen(ptr)+1];  
  19.             strcpy(_ptr,ptr);  
  20.         }  
  21.     }  
  22.     //拷贝构造函数  
  23.     String(String& s)  
  24.         :_ptr(s._ptr)  
  25.         ,_pcount(s._pcount)  
  26.     {  
  27.         (*_pcount)++;  
  28.     }  
  29.     //赋值运算符重载  
  30.     String& operator=(const String& s)  
  31.     {  
  32.         if(this != &s)  
  33.         {  
  34.             if(--(*_pcount) == 0)  
  35.             {  
  36.                 delete[] _ptr;  
  37.                 delete _pcount;  
  38.             }  
  39.             else  
  40.             {  
  41.                 _ptr = s._ptr;  
  42.                 _pcount = s._pcount;  
  43.                 (*_pcount)++;  
  44.             }  
  45.         }  
  46.         return *this;  
  47.     }  
  48.     //析构函数  
  49.     ~String()  
  50.     {  
  51.         if(0 == --(*_pcount) && _ptr!= NULL)  
  52.         {  
  53.             delete[]_ptr;  
  54.             delete _pcount;  
  55.             _ptr = NULL;  
  56.         }  
  57.           
  58.     }  
  59.     //重载[]  
  60.     char& operator[](size_t size)  
  61.     {  
  62.         if(--(*_pcount) >1)  
  63.         {  
  64.             char* ptemp = new char[strlen(_ptr)+1];  
  65.             int* pcount = new int(1);  
  66.             strcpy(ptemp,_ptr);  
  67.             (*_pcount)--;  
  68.             _ptr = ptemp;  
  69.             _pcount = pcount;   
  70.         }  
  71.         return _ptr[size];  
  72.     }  
  73. private:  
  74.     char*_ptr;  
  75.     int* _pcount;  
  76. };  
  77.   
  78. void FunTest()  
  79. {  
  80.     String s1("hello");  
  81.     String s2(s1);  
  82.     String s3(s2);  
  83.     s3 = s2;  
  84.     String s4(s1);  
  85.     s1[0] = 'y';  
  86. }  
  87. int main()  
  88. {  
  89.     FunTest();  
  90.     return 0;  
  91. }</span>  

经调试,结果如下:


【注意】:因为不止一个对象使用这一块内存,当修改自己的时,也等于修改了他人的。在向这块存储单元写之前,应该确信没有其他人使用它。如果引用计数大于1,在写之前必须拷贝这块存储单元,这样就不会影响他人了。


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/383780.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Java学习笔记十一】图形用户界面

图形用户界面或图形用户接口(Graphical User Interface&#xff0c;GUI)是指采用图形方式,借助菜单、按钮等标准界面元素&#xff0c;用户可以通过鼠标等外设向计算机系统发出指令、启动操作&#xff0c;并将系统运行的结果同样以图形方式显示给用户的技术。 GUI是事件驱动的&…

C++ 写时拷贝 3

http://blog.csdn.net/ljianhui/article/details/22895505 字符串一种在程序中经常要使用到的数据结构&#xff0c;然而在C中却没有字符串这种类型。在C中&#xff0c;为了方便字符串的使用&#xff0c;在STL中提供了一个string类。该类维护一个char指针&#xff0c;并封装和提…

C++ String类写时拷贝 4

http://blog.51cto.com/zgw285763054/1839752 维基百科&#xff1a; 写入时复制&#xff08;英语&#xff1a;Copy-on-write&#xff0c;简称COW&#xff09;是一种计算机程序设计领域的优化策略。其核心思想是&#xff0c;如果有多个调用者&#xff08;callers&#xff09;同时…

C++笔试复习

基础知识点 C中对象数组在定义的时候全部进行实例化&#xff08;与Java不同&#xff0c;Java相当于只是定义了一个指针数组&#xff0c;没有进行实例化&#xff09; 程序的三种基本控制结构是&#xff1a;顺序结构、循环结构、选择结构 一个C程序开发步骤通常包括&#xff1a…

Java笔试复习

Java程序运行 Java程序的执行必须经过编辑、编译和运行三个步骤 编辑指编写代码&#xff0c;最终形成后缀名为.java的Java源文件编译指使用Java编译器&#xff08;javac指令&#xff09;将源文件翻译为二进制代码&#xff0c;编译后生成后缀名为.class的字节码文件&#xff0c…

Java类名与包名不区分大小写

刚才写了一个简单的Java程序&#xff0c;经过测试得到一个令人震惊的结论&#xff1a;Java类名和包名是不区分大小写的 可以看一下这个例子&#xff1a; package Test;class aBcdEfG {}class AbCdefg {}public class TTT {public static void main(String[] args){AbCdefg tm…

处理大并发之一 对epoll的理解,epoll客户端服务端代码

http://blog.csdn.net/zhuxiaoping54532/article/details/56494313处理大并发之一对epoll的理解&#xff0c;epoll客户端服务端代码序言&#xff1a;该博客是一系列的博客&#xff0c;首先从最基础的epoll说起&#xff0c;然后研究libevent源码及使用方法&#xff0c;最后研究n…

链栈基本操作

http://blog.csdn.net/jwentao01/article/details/46765517###;栈基本概念&#xff1a; 栈&#xff08;stack&#xff09;是限定在表尾进行插入和删除操作的线性表&#xff08;或单链表&#xff09;。 //只能在一段进行插入和删除&#xff0c;因此不存在&#xff0c;在中间进行…

Linux网络编程---I/O复用模型之select

https://blog.csdn.net/men_wen/article/details/53456435Linux网络编程—I/O复用模型之select 1. IO复用模型 IO复用能够预先告知内核&#xff0c;一旦发现进程指定的一个或者多个IO条件就绪&#xff0c;它就通知进程。IO复用阻塞在select或poll系统调用上&#xff0c;而不是阻…

Linux网络编程---I/O复用模型之poll

https://blog.csdn.net/men_wen/article/details/53456474Linux网络编程—I/O复用模型之poll 1.函数poll poll系统调用和select类似&#xff0c;也是在指定时间内轮询一定数量的文件描述符&#xff0c;以测试其中是否有就绪者。 #include <poll.h>int poll(struct pollfd…

Linux网络编程---I/O复用模型之epoll

https://blog.csdn.net/men_wen/article/details/53456474 Linux网络编程—I/O复用模型之epoll 1. epoll模型简介 epoll是Linux多路服用IO接口select/poll的加强版&#xff0c;e对应的英文单词就是enhancement&#xff0c;中文翻译为增强&#xff0c;加强&#xff0c;提高&…

POJ 1741tree-点分治入门

学习了一下点分治&#xff0c;如果理解有误还请不吝赐教。 为了快速求得树上任意两点之间距离满足某种关系的点对数&#xff0c;我们需要用到这种算法。 点分治是树上的一种分治算法&#xff0c;依靠树和子树之间的关系进行分治从而降低复杂度。 和其他树上的算法有一些区别…

基于单链表的生产者消费者问题

『生产者与消费者问题分析』「原理」生产者生产产品&#xff0c;消费者消费产品。产品如果被消费者消费完了&#xff0c;同时生产者又没有生产出产品&#xff0c;消费者 就必须等待。同样的&#xff0c;如果生产者生产了产品&#xff0c;而消费者没有去消费&#x…

C++智能指针(二)模拟实现三种智能指针

https://blog.csdn.net/nou_camp/article/details/70186721在上一篇博客中提到了Auto_ptr(C智能指针&#xff08;一&#xff09;)&#xff0c;下面进行模拟实现Auto_ptr 采用类模板实现 #include<iostream> using namespace std; template<class T> class Autoptr …

C++智能指针(三)总结

https://blog.csdn.net/nou_camp/article/details/70195795 在上一篇博客中&#xff08;C智能指针&#xff08;二&#xff09;&#xff09;模拟实现了三种智能指针。 其中最好的就是shared_ptr,但是这并不代表它就是最完美的&#xff0c;它也有问题&#xff0c;这个问题就是循环…

c++11 你需要知道这些就够了

https://blog.csdn.net/tangliguantou/article/details/50549751c11新特性举着火把寻找电灯今天我就权当抛砖引玉&#xff0c;如有不解大家一起探讨。有部分内容是引用自互联网上的内容&#xff0c;如有问题请联系我。T&& 右值引用 std::move 右值引用出现之前我们只能…

c++仿函数 functor

https://www.cnblogs.com/decade-dnbc66/p/5347088.html内容整理自国外C教材先考虑一个简单的例子&#xff1a;假设有一个vector<string>&#xff0c;你的任务是统计长度小于5的string的个数&#xff0c;如果使用count_if函数的话&#xff0c;你的代码可能长成这样&#…

Ubuntu软件更新失败

刚安装好Ubuntu以后需要将系统的软件都更新一下&#xff0c;但是遇到一个问题就是下载仓库信息失败&#xff0c;大概是这个样子的错误&#xff1a; 经国遇到这样的问题可以试一下下面这个命令&#xff1a; sudo rm -rf /var/lib/apt/lists/* sudo apt-get update参考网址&…

getsockname函数与getpeername函数的使用

https://www.tuicool.com/articles/V3Aveygetsockname和getpeername函数 getsockname函数用于获取与某个套接字关联的本地协议地址 getpeername函数用于获取与某个套接字关联的外地协议地址 定义如下&#xff1a;[cpp] view plaincopy#include<sys/socket.h> int gets…

Linux命令【一】基本命令

shell命令和bash命令相同&#xff0c;指的是命令解析器 快捷键 history 所有的历史命令ctrl P 向上滚动命令 ctrl N 向下滚动命令 ctrlB将光标向前移动 ctrlF将光标向后移动 ctrlA移动到命令行头部 ctrlE移动到命令行尾部 光标删除操作&#xff1a;删除光标前面字符ctrlh或…