String 类的实现(1)浅拷贝存在的问题以及深拷贝实现
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)浅拷贝存在的问题以及深拷贝实现的更多相关文章
- String 类的实现(1)浅拷贝存在的问题
浅拷贝 : 也称位拷贝 , 编译器只是直接将指针的值拷贝过来, 结果多个对象共用 同 一块内 存, 当一个对象将这块内 存释放掉之后, 另 一些对象不知道该块空间 已经还给了 系 统, 以 为还有效, ...
- 自己实现简单的string类
1.前言 最近看了下<C++Primer>,觉得受益匪浅.不过纸上得来终觉浅,觉知此事须躬行.今天看了类类型,书中简单实现了String类,自己以前也学过C++,不过说来惭愧,以前都是用C ...
- String类的写时拷贝
#include<iostream>using namespace std; class String;ostream& operator<<(ostream & ...
- String 类 Copy-On-Write 技术以及使用时存在的风险
先来看一下string 面试时的简易写法(使用的是深拷贝): class String { String() :str(]) { str[] = '\0'; } String(char* p, siz ...
- String 类的实现(3)引用计数实现String类
我们知道在C++中动态开辟空间时是用字符new和delete的.其中使用new test[N]方式开辟空间时实际上是开辟了(N*sizeof(test)+4)字节的空间.如图示其中保存N的值主要用于析 ...
- String类的实现(4)写时拷贝浅析
由于释放内存空间,开辟内存空间时花费时间,因此,在我们在不需要写,只是读的时候就可以不用新开辟内存空间,就用浅拷贝的方式创建对象,当我们需要写的时候才去新开辟内存空间.这种方法就是写时拷贝.这也是一种 ...
- String 类的实现(2)引用计数与写时拷贝
1.引用计数 我们知道在C++中动态开辟空间时是用字符new和delete的.其中使用new test[N]方式开辟空间时实际上是开辟了(N*sizeof(test)+4)字节的空间.如图示其中保存N ...
- String 类实现 以及>> <<流插入/流提取运算符重载
简单版的String类,旨在说明>> <<重载 #include <iostream> //#include <cstring>//包含char*的字符 ...
- (C++)已知String类的定义,实现其函数体
CString类的定义如下: class CMyString{ public: CMyString(const char* pData=NULL); CMyString(const CMyString ...
随机推荐
- springboot整合dubbo\zookeeper做注册中心
springboot整合dubbo发布服务,zookeeper做注册中心.前期的安装zookeeper以及启动zookeeper集群就不说了. dubbo-admin-2.5.4.war:dubbo服 ...
- Android SpannableString实现TextView的点击事件
最近项目中遇到一个问题,就是一段文字中股票可点击并跳到股票详情,只记得SpannableString可以实现富文本功能,但并不知道可实现的富文本有点击功能,就开始借助万能搜索引擎,结果不出意料,的确有 ...
- 2.Python list_常用方法总结
一.创建列表 只要把逗号分隔的不同数据项,使用方括号[],括起来即可, 下标(角标索引)从0开始,最后一个一个元素下标可以写-1 list = ['1' , '2' , '3'] list = [] ...
- VS2017打包C#桌面应用
原文地址:https://blog.csdn.net/houheshuai/article/details/78518097 在要打包项目的解决方案 右键→添加→ 新建项目 后出现如下选择 如果没有V ...
- 转-OWASP CSRFGuard使用细节
版权声明:不存在一劳永逸的技术 只存在不断学习的人.本文为博主原创文章,未经博主允许不得转载.交流联系QQ:1120121072 https://blog.csdn.net/u013474568/ar ...
- 转载:UML学习(三)-----序列图(silent)
原文:http://www.cnblogs.com/silent2012/archive/2011/09/14/2172219.html UML的模型中可分为两种,动态模型和静态模型.用例图.类图和对 ...
- Spring 邮件发送
前言:以前都是直接用Java自带的邮件工具发送邮件,现在Spring帮我们做了封装,提供了更好用更简单的发送邮件工具JavaMailSender 关于邮件服务器的设置就不在这里说了,直接去QQ邮箱 ...
- 35)django-验证码
一:验证码原理 第一次访问GET,后台: 1.创建一张图片 2.在图片中写入随机字符串 3.将图片写到制定文件 4.打开指定目录文件,读取内容 5.把生成的验证码保存在session中 6. 通过Ht ...
- Android中播放音乐的几种方式
前言 前几天一直在研究RxJava2,也写了记录了几篇博客,但因为工作任务原因,需要研究音频相关的知识,暂时放下Rxjava,本文的demo中,MediaPalyer 部分使用RxJava编写一点逻辑 ...
- swift 学习- 11 -- 属性
// '属性'将值跟特定的类, 结构体或枚举关联, 存储属性常量或变量作为实例的一部分,而计算属性计算(不是存储) 一个值, 计算属性可以用于 类, 结构体, 枚举, 存储属性只能用于 类 和 结构体 ...