大家好,欢迎来到IT知识分享网。
1.string的浅拷贝
如果一个string只显示的给出构造函数和析构函数,拷贝构造函数和赋值运算符重载使用系统默认的,当进行拷贝和赋值时,会出现什么结果:
#include <string.h>
class String
{
public:
String(const char* str = "");
~String();
private:
char* _str;
};
//构造函数
String::String(const char* str)
:_str( new char[strlen(str) + 1])
{
if(NULL != str)
strcpy(_str, str);
}
//析构函数
String::~String()
{
delete[] _str;
}
int main()
{
String str1("abcde");
String str2(str1);
String str3;
str3 = str1;
return 0;
}
首先试着运行一下,发现程序会崩溃,接下来进行断点调试:
1.运行完String str1("abcde")
,结果如下:
此时可以看到,str1
运行正常。
2.运行完String str2(str1)
,结果如下:
可以看到,str2
也运行正常,但是值得注意的一一点是,str2
和 str1
指向的是同一块空间。
3.运行完str3 = str1
,结果如下:
str3
也正常生成,但是同样的,str3和str1指向同样的地址空间。
4.在运行return 0
时,由于会调用三个对象的析构函数来释放空间,但是由于三个对象所指的地址空间是一样的,因此先析构str3
(后生成的先释放),释放空间,但是在释放str2
时由于他的空间已经被释放过了,因此在这里就会报错。
还有一个要注意的地方是,str3 = str1
时由于是浅拷贝,因此对象str3
的_str
的值会被str1
的_str
的值替代,造成内存泄漏。
2.关于浅拷贝的危害
在说浅拷贝的危害之前,我先来说一下什么是浅拷贝:
浅拷贝:也称位拷贝,编译器只是将对象中的值拷贝过来,如果对象中管理资源, 最后就会导致多个对象共享同一份资源,当一个对象销毁时就会将该资源释放掉,而此时另一些对象不知道该资源已经被释放,以为还有效,所以 当继续对资源进项操作时,就会发生发生了访问违规
-
因此当类里面有指针对象时,拷贝构造和赋值运算符重载只进行值拷贝,两个对象共用同一块空间,对象销毁时程序会发生内存访问违规
-
在赋值运算符重载是由于是传值,导致指针的指向有所改变,造成内存泄漏问题。
-
如果使用拷贝构造和赋值运算符重载,当修改一个对象的值是,由于指向的同一空间,因此会导致其他对象的值一起改变,因此对象的存在将毫无意义。
这里我们要杜绝浅拷贝,那么解决方法就是使用深拷贝。
3. 深拷贝解决方法
1.解决浅拷贝方式一:普通版深拷贝
//拷贝构造函数--普通版
String::String(const String& s)
:_str(new char[strlen(s._str)+1])
{
if (s._str != NULL)
strcpy(_str, s._str);
}
//赋值运算符重载--普通版
String& String::operator=(const String& s)
{
if (NULL != s._str)
{
delete[] _str;
_str = new char[strlen(s._str) + 1];
strcpy(_str, s._str);
}
return *this;
}
2.解决浅拷贝方式二:简洁版的深拷贝
void String::swap(char** a1, char** a2)
{
char *temp = *a1;
*a1 = *a2;
*a2 = temp;
}
//拷贝构造函数
String::String(const String& s)
{
String temp(s._str);
swap(&temp._str, &_str);
}
//赋值运算符重载--普通版
String& String::operator=(const String& s)
{
String temp(s._str);
swap(&temp._str, &_str);
return *this;
}
拷贝构造函数(简洁版)的调运过程图示,如下:
赋值函数(简洁版)的调运过程图示,如下:
3.解决浅拷贝方式三:引用计数实现(仍是浅拷贝)
#include<string.h>
#pragma warning (disable:4996)
class String
{
public:
String(const char* str = "");
String(const String& s);
String& operator=(const String& s);
~String();
private:
char* _str;
int *_pCount;
};
//构造函数
String::String(const char* str)
:_str(new char[strlen(str) + 1])
,_pCount(new int(1))
{
if (NULL != str)
strcpy(_str, str);
}
//拷贝构造函数
String::String(const String& s)
{
_str = s._str;
_pCount = s._pCount;
(*_pCount)++;
}
//赋值运算符重载
String& String::operator=(const String& s)
{
delete[] _str;
_str = s._str;
_pCount = s._pCount;
(*_pCount)++;
return *this;
}
//析构函数
String::~String()
{
if (--(*_pCount) == 0)
{
delete[] _str;
delete _pCount;
}
}
int main()
{
String str1("abcde");
String str2(str1);
String str3("1234");
str3 = str1;
return 0;
}
在这里要说明的是:此引用计数选用的是指针,并且在调用构造函数时,分配空间并初始化。
在选用指针之前,我尝试使用成员变量和静态成员变量来做引用计数,结果发现,成员变量和
静态成员变量都不可以用来做引用计数,理由如下:
-
成员变量做引用计数
在调用拷贝构造函数或者运算符重载时,后面的对象对引用计数进行修改,仅仅只会印象到自己的这一数据,并不会改变其他对象的数据。因为成员函数是属于单个对象的因此不能用来控制全局。
-
静态成员变量做引用计数
虽然,静态成员变量是属于类的,可以达到控制全局的作用,但是唯一的一点不好就是,一个类只有一份该变量,造成的结果是,在已有了一个对象,且该对象有许多拷贝指向该对象,即引用计数大于1,这时如果重新定义一个新的对象,那么就会在构造函数里面对引用变量进行初始化(使引用变量等于1),问题就是该引用变量只有一份,且是属于类的,那么之前引用对象的记录,也就同时被刷新了,数据也乱掉了,因此不能使用静态成员变量来做引用计数。
-
使用指针做引用变量的好处有:
使用指针做引用变量不仅能够达到要求,还会为每一为新的不同对象创建一份引用计数,不会影响其他相同对象的引用计数。
4.写时拷贝(cow:copy on write)
在上述方法三中使用引用计数解决浅拷贝的方式存在问题:如果多个String类型的对象共用同一块内存空间,改变其中一个String类对象的字符,其几个同时被修改,不科学 。
这时我们就需用了解一下写时拷贝,即当多个String类对象共用同一块空间时,如果一个有可能改变一个对象中的字符内容,就将该对象分离出来,不要和别的对象共享空间,例如operator[]。
char& String::operator[](int num)
{
String temp(_str);
swap(&_str, &temp._str);
swap(&_pCount, &temp._pCount);
return *((this->_str)+num);
}
int main()
{
String str1("abcde");
String str2(str1);
String str3("1234");
str3 = str1;
str3[0] = 'A';
return 0;
}
构建一个单独的临时对象temp,并且将temp和要操作的对象进行内容指针交换,即交换_str
指向和_pCount
的指向, 即用temp完全替代要操作对象的原位置,在函数返回结束时,会释放临时对象,即释放要操作对象原来的空间,且保持引用计数减一,达到引用计数的作用。
5.总结
在这里基本上将string类的浅拷贝和深拷贝介绍完了,因为浅拷贝的出现场景不止在string类里面,因此希望大家多多注意浅拷贝的出现,并且学会深拷贝的解决方法,我的建议是主要掌握前两种方法即可,对于第三种,算是一个优化吧,在性能上肯定比前两种要好的多,但是同样的不好理解,希望大家能够多思考加以理解。最后希望大家能指出我的错误和不足的地方,我会加以改正的。
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://yundeesoft.com/25101.html