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++类模板实例化条件

&#xff08;我不想了解这个&#xff0c;可是考试要考。。。。 并不是每次使用模板类都会实例化一个类 声明一个类模板的指针和引用不会引起类模板的实例化如果检查这个指针或引用的成员时时&#xff0c;类模板会实例化定义一个对象的时候需要有类的定义&#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…

C++函数默认参数

声明是用户可以看到的部分&#xff0c;客户非常信任地使用这个特性&#xff0c;希望得到一定的结果&#xff0c;但是你在实现里使用了不同的缺省值&#xff0c;那么将是灾难性的。因此编译器禁止声明和定义时同时定义缺省参数值。 类的成员函数的参数表在声明时默认参数位于参…

C语言链表各类操作详解

http://blog.csdn.net/pf4919501/article/details/38818335链表概述   链表是一种常见的重要的数据结构。它是动态地进行存储分配的一种结构。它可以根据需要开辟内存单元。链表有一个“头指针”变量&#xff0c;以head表示&#xff0c;它存放一个地址。该地址指向一个元素。…

Java笔试复习

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

数据结构之自建算法库——链栈

http://blog.csdn.net/sxhelijian/article/details/48463801本文针对数据结构基础系列网络课程(3)&#xff1a;栈和队列中第4课时栈的链式存储结构及其基本运算实现。 按照“0207将算法变程序”[视频]部分建议的方法&#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实现高并发聊天室

http://blog.csdn.net/qq_31564375/article/details/51581038项目介绍 本项目是实现一个简单的聊天室&#xff0c;聊天室分为服务端和客户端。本项目将很多复杂的功能都去掉了&#xff0c;线程池、多线程编程、超时重传、确认收包等等都不会涉及。总共300多行代码&#xff0c;让…

BZOJ2809-左偏树合并

Description 在一个忍者的帮派里&#xff0c;一些忍者们被选中派遣给顾客&#xff0c;然后依据自己的工作获取报偿。在这个帮派里&#xff0c;有一名忍者被称之为 Master。除了 Master以外&#xff0c;每名忍者都有且仅有一个上级。为保密&#xff0c;同时增强忍者们的领导力&a…

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

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

epoll详解

http://blog.csdn.net/majianfei1023/article/details/45772269欢迎转载&#xff0c;转载请注明原文地址&#xff1a;http://blog.csdn.net/majianfei1023/article/details/45772269一.基本概念&#xff1a;1.epoll是什么&#xff1a;epoll是Linux内核为处理大批量文件描述符而…

数据分割-并查集+set

小w来到百度之星的赛场上&#xff0c;准备开始实现一个程序自动分析系统。 这个程序接受一些形如xixj 或 xi≠xj 的相等/不等约束条件作为输入&#xff0c;判定是否可以通过给每个 w 赋适当的值&#xff0c;来满足这些条件。 输入包含多组数据。 然而粗心的小w不幸地把每组数据…

linux c++线程池的实现

http://blog.csdn.net/zhoubl668/article/details/8927090?t1473221020107 线程池的原理大家都知道&#xff0c;直接上代码了^_^ Thread.h [cpp] view plaincopy #ifndef __THREAD_H #define __THREAD_H #include <vector> #include <string> #inc…

树启发式合并入门

所谓启发式合并&#xff0c;就是一种符合直觉的合并方法&#xff1a;将小的子树合并在大的子树上。 这些问题一般是相似的问题背景&#xff1a;都是树上的计数问题&#xff0c;都不能直接从上往下进行暴力&#xff0c;都需要从下往上计数时对子树信息进行运算从而得到父亲节点的…

链栈基本操作

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;而不是阻…

UVa12633-Super Rooks on Chessboard-容斥+FFT

题目大意就是给你一个R*C的棋盘&#xff0c;上面有超级兵&#xff0c;这种超级兵会攻击 同一行、同一列、同一主对角线的所有元素&#xff0c;现在给你N个超级兵的坐标&#xff0c;需要你求出有多少方块是不能被攻击到的(R,C,N<50000) 遇到这种计数问题就要联想到容斥&#…