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 ...
随机推荐
- CentOS 7 安装配置 Vsftpd
https://blog.imzhengfei.com/centos-an-zhuang-pei-zhi-vsftpd/ vsftpd 是“very secure FTP daemon”的缩写,是一款 ...
- C# 判断网络是否连接
bool isconn = true; PingReply pr; Ping ping = new Ping(); pr = ping.Send("lightyiyi.cn"); ...
- 统计分析与R软件-chapter2-5
2.5 多维数组和矩阵 2.5.1 生成数组或矩阵 数组有一个特征属性叫做维数向量(dim属性),维数向量是一个元素取正整数的向量,其长度是数组的维数,比如维数向量有两个元素时数组为2维数组(矩阵). ...
- JavaScript数组去重—ES6的两种方式
说明 JavaScript数组去重这个问题,经常出现在面试题中,以前也写过一篇数组去重的文章,(JavaScript 数组去重的多种方法原理详解)但感觉代码还是有点不够简单,今天和大家再说两种方法,代 ...
- undefined reference to symbol '_ZNK11GenICam_3_016GenericException17GetSourceFileNameEv'
今天在编译DALSA二次开发的源码时,出现了如下错误: /usr/bin/ld: ./out/camera.o: undefined reference to symbol '_ZNK11GenICa ...
- pppd[15863]: Terminating on signal 15
由于广网于网上pptp服务器和client之间存在一些问题: 1)windows 客户端出现619 或800等错误 ----极有可能是服务器端未开启nat-t功能 2)ubunut 客户端没有拿到IP ...
- 请求Jenkins链接返回403
使用python请求Jenkins链接,返回403 1.使用正确的账号密码(Jenkins -> 系统设置 -> 全局安全设置),该账户拥有访问该Jenkins链接的权限 2.代码中的账号 ...
- php OpenSSL 加解密
2018-1-6 17:10:19 星期六 $data = '123456'; $openssl_method = 'AES-256-CBC'; $openssl_iv_length = openss ...
- HDU 4549
水题: 费马小定理+快速幂+矩阵快速幂 (第一次用到费马小定理) #include<bits/stdc++.h> using namespace std; typedef long lon ...
- Mysql41道练习题
1.自行创建测试数据 2.查询“生物”课程比“物理”课程成绩高的所有学生的学号.ps:针对的是自己的生物成绩比物理成绩高,再把符合条件的学生的学号查出来: # 查到 生物 和 物理的 id: sele ...