1.   浅拷贝 : 也称位拷贝 , 编译器只是直接将指针的值拷贝过来, 结果多个对象共用 同 一块内存, 当一个对象将这块内 存释放掉之后, 另 一些对象不知道该块空间已经还给了系统, 以为还有效, 所以在对这段内存进行操作的时候, 发生了违规访问。

先上代码

 class String
{
public:
/* 浅拷贝---下列代码相当于系统合成的
String()
{
_pStr = new char;
*_pStr = '\0';
}*/
String(const char *pStr = "")
{
if (pStr == NULL)
{
_pStr = new char[];
*_pStr = '\0';
}
else
{
_pStr = new char[strlen(pStr) + ];
strcpy(_pStr, pStr);
}
}
String(const String& s)
:_pStr(s._pStr)
{}
~String()
{
if (_pStr)
delete[] _pStr;
_pStr = NULL;
}
String& operator=(const String& s)
{
if (this != &s)
{
_pStr = s._pStr;
}
return *this;
}
private:
char *_pStr;
};/*存在问题:1.同一对象析构多次,程序崩溃;2.内存泄漏*/

int main()
  {
  String s1;
  String s2 = "hello word";
  String s3(s2);
  s1[0] = '5';
  String s4;
  s4 = s2;
  }

编译时可以轻松通过,但是这段代码是有问题的,运行时程序崩溃

这是String类的一个经典反例,下面来具体分析一下这段代码存在的问题:

   当类里面有指针对象时, 拷贝构造和赋值运算符重载只进行值拷贝(浅拷贝) , 两个对象指向同一块内 存, 对象销毁时该空间被释放了 两次, 因此程序崩溃!下面的 s1[0] = '5';  String s4;  s3 = s4; 也存在类似问题

  

存在问题:1.同一对象析构多次,程序崩溃;2.内存泄漏

那么该如何解决出现的问题呐?这需要我们自己重新定义相关类的成员函数,下面将介绍多种深拷贝方法以解决此问题

我们已经知道了浅拷贝存在的问题,即多次析构同一空间。这个问题是类的成员函数引起的,就是前面浅拷贝里模拟实现的编译器自动合成的函数,确切的说,浅拷贝里的问题是由隐式拷贝构造函数和隐士赋值运算符引起的。

拷贝构造函数用于将一个对象拷贝到新创建的对象中。也就是说,他用于初始化过程中,最常见的是将新对象显式地初始化为现有的对象。每当程序生成了副本对象时,编译器也将使用拷贝构造函数。默认的拷贝构造函数逐个的拷贝非静态成员(即浅拷贝),拷贝的是成员的值。(由于按值传递对象将调用拷贝构造函数,因此应该按引用传递对象。这样可以节省调用构造函数的时间以及存储新对象的空间。)

默认的赋值运算符是通过自动为类重载赋值运算符实现的。它的原型是:Class_name & Class_name ::operator=(const Class_name &);它接受并返回一个指向类对象的引用。将已有的对象赋给另一个对象时,将使用重载的赋值运算符(初始化对象时不一定会使用)。如:String s1=s2;也可能分两步来处理这条语句:使用拷贝构造函数创建一个临时对像,然后通过赋值将临时对象的值复制到新对象中。即初始化总会调用拷贝构造函数,而使用“=“时也可能调用赋值运算符。

2.解决方法:深度复制(deep copy)。定义一个显式拷贝构造函数,拷贝字符串并将副本的地址赋给str成员,而不仅仅是拷贝字符串地址。这样每个对象都有自己的字符串,而不是引用另一个对象的字付串。则调用析构函数都将释放不同的字符串而不是去释放已经释放的字符串。代码如下:

1     String(const String& s)
2 :_pStr(new char[strlen(s._pStr)] + 1)
3 {
4 if (this != &s)
5 {
6 strcpy(_pStr, s._pStr);
7 }
8 }

必须定义拷贝构造函数的原因在于,一些类成员是使用new初始化的、指向数据的指针,而不是数据本身。

总结:如果类中包含了使用new初始化的指针成员,应当定义一个拷贝构造函数,以拷贝指向的数据,而不是指针;浅拷贝仅浅浅的拷贝指针信息,而不会深入”挖掘“以拷贝指针引用的结构。

默认赋值运算符存在的问题与默认拷贝构造函数相同:数据受损。试图删除已经删除的数据导致的结果是不正确的,因此可能改变内存中的内容,导致程序异常终止。解决方法是提供深度拷贝的赋值运算符定义。

 1     String& operator=(const String& s)
2 {
3 if (this != &s)
4 {
5 char *temp = new char[strlen(s._pStr) + 1];
6 strcpy(temp, s._pStr);
7 delete[] _pStr;
8 _pStr = temp;
9 }
10 return *this;
11 }

注意:

•由于目标对象可能引用了以前分配的数据,所以函数应使用delete[]来释放这些数据。

•函数应该避免将对象赋值给本身;否则,给对象重新赋值前,释放内存操作可能删除对象的内容,

•函数返回一个指向调用对象的引用。

按照之前讨论的方法重新定义赋值运算符和拷贝构造函数,还有更简洁的方法如下:

 1     String(const String& s)
2 :_pStr(NULL) //
3 {
4 String strtemp(s._pStr);
5 std::swap(_pStr, strtemp._pStr);
6 }
7
8 String& operator=(const String& s)
9 {
10 if (this != &s)
11 {
12 String strtemp(s);
13 std::swap(_pStr, strtemp._pStr);
14 }
15 return *this;
16 }
17
18 String& operator=(String s)
19 {
20 std::swap(_pStr, s._pStr);
21 return *this;
22 }

此外,还有一些深度拷贝的不同方法将在下一篇介绍。

String 类的实现(1)浅拷贝存在的问题的更多相关文章

  1. String 类的实现(1)浅拷贝存在的问题以及深拷贝实现

    1.   浅拷贝 : 也称位拷贝 , 编译器只是直接将指针的值拷贝过来, 结果多个对象共用 同 一块内存, 当一个对象将这块内 存释放掉之后, 另 一些对象不知道该块空间已经还给了系统, 以为还有效, ...

  2. 自己实现简单的string类

    1.前言 最近看了下<C++Primer>,觉得受益匪浅.不过纸上得来终觉浅,觉知此事须躬行.今天看了类类型,书中简单实现了String类,自己以前也学过C++,不过说来惭愧,以前都是用C ...

  3. String类的写时拷贝

    #include<iostream>using namespace std; class String;ostream& operator<<(ostream & ...

  4. String 类 Copy-On-Write 技术以及使用时存在的风险

    先来看一下string 面试时的简易写法(使用的是深拷贝): class String { String() :str(]) { str[] = '\0'; } String(char* p, siz ...

  5. String 类的实现(3)引用计数实现String类

    我们知道在C++中动态开辟空间时是用字符new和delete的.其中使用new test[N]方式开辟空间时实际上是开辟了(N*sizeof(test)+4)字节的空间.如图示其中保存N的值主要用于析 ...

  6. String类的实现(4)写时拷贝浅析

    由于释放内存空间,开辟内存空间时花费时间,因此,在我们在不需要写,只是读的时候就可以不用新开辟内存空间,就用浅拷贝的方式创建对象,当我们需要写的时候才去新开辟内存空间.这种方法就是写时拷贝.这也是一种 ...

  7. String 类的实现(2)引用计数与写时拷贝

    1.引用计数 我们知道在C++中动态开辟空间时是用字符new和delete的.其中使用new test[N]方式开辟空间时实际上是开辟了(N*sizeof(test)+4)字节的空间.如图示其中保存N ...

  8. String 类实现 以及>> <<流插入/流提取运算符重载

    简单版的String类,旨在说明>> <<重载 #include <iostream> //#include <cstring>//包含char*的字符 ...

  9. STL库中string类内存布局的探究

    在STL中有着一个类就是string类,他的内存布局和存储机制究竟是怎么样的呢? 这就是建立好的string 可以看出,图中用黄色框框标注的部分就是主要区域 我们用来给string对象进行初始化的字符 ...

随机推荐

  1. Bootstrap框架的要点--栅格系统

    不同的公司要求使用框架有所不同,而Bootstrap框架在工作中使用频次较高,其中栅格系统在这一框架中的地位不容小觑,下面我们开始聊聊它吧. 简单介绍: Bootstrap提供了一套响应式.移动设备优 ...

  2. php文件上传分类

    <?php/** * 文件上传类 * @author lijiamin * @time 2017-02-17 * @email 1195989301@qq.com */class Upload{ ...

  3. ArcGIS许可启动问题

    前段时间,由于360常常删除重要文件终于发生在我身上.不得已换了电脑管家,清理后再次打开License Server Administrator时,发现启动项怎么也点不动了.而打开服务管理器,却发现A ...

  4. Android项目实战(三十一):异步下载apk文件并安装(非静默安装)

    前言: 实现异步下载apk文件 并 安装.(进度条对话框显示下载进度的展现方式) 涉及技术点: 1.ProgressDialog   进度条对话框  用于显示下载进度 2.AsyncTask     ...

  5. CLAHE的实现和研究

    CLAHE算法对于医学图像,特别是医学红外图像的增强效果非常明显. CLAHE  https://en.wikipedia.org/wiki/Adaptive_histogram_equalizati ...

  6. JavaScript 基本类型值-String类型

    ▓▓▓▓▓▓ 大致介绍 String类型用于表示由零或多个16位Unicode字符组成的字符序列,即字符串.在JavaScript中没有单个的字符型,都是字符串.字符型就相当于只包含一个字符的字符串. ...

  7. React Native与原生项目连接与发布

    前面的各种环境配置按照官方文档一步一步来,挺详细,宝宝在这里就不多说废话了. 其次,前面的配置,我参照的这个博主的文章React Native 集成到iOS原生项目 下面是宝宝掉过的坑(半径15M): ...

  8. ios 相机调用之读取相册

    UIIamgePickerControllerr可以从照片库中读取一张图片到咱们应用程序中来   步骤:   //创建图片判断图片库是否可以使用   if([UIImagePickerControll ...

  9. 数据库读写分离Amoeba

    1.理解读写分离的原理 Amoeba(变形虫)项目,该开源框架于2008发布一款Amoeba for mysql软件,该软件致力于mysql的分布式数据库前端代理层,主要的作用是应用服务访问mysql ...

  10. ACM 阶乘的0

    阶乘的0 时间限制:3000 ms  |  内存限制:65535 KB 难度:3   描述 计算n!的十进制表示最后有多少个0   输入 第一行输入一个整数N表示测试数据的组数(1<=N< ...