【C++】String中的深浅拷贝问题;深拷贝的两种写法(传统写法、现代写法)
一、浅拷贝问题
浅拷贝
首先定义一个Sting类,它包含一个成员变量,一个char*的指针。
namespace CPP
{
class String
{
public:
private:
char* _str;
};
}
对于String类的拷贝构造函数及operator=函数来说,当用一个String对象拷贝构造或赋值给另一个String对象时,就是将这个对象里的指针的值赋值给另一个对象里的指针。将一个指针值赋值给另一个指针,就会使得两个指针指向同一块空间,这就产生了浅拷贝(值拷贝)。
namespace CPP
{
class String
{
public:
//拷贝构造---浅拷贝
String(const String& s)
:_str(s._str)
{}
//赋值
String& operator=(const String& s)
{
if (this != &s)
{
_str = s._str;
}
return *this;
}
//析构函数
~String()
{
if (_str)
{
delete[] _str;
}
}
private:
char* _str;
};
void TestString1()
{
String s1("hello");
String s2(s1);//未写拷贝构造函数时,使用默认的,为浅拷贝(值拷贝)
cout << s2.c_str() << endl;
String s3("world");
s2 = s3;
cout << s2.c_str() << endl;
}
}
这时其实程序已经崩溃了。 问题在于以下两个方面:
① 两个(或两个以上)指针指向同一块空间,这个内存就会被释放多次;(例如下面定义了一个String对象s1,以浅拷贝的方式拷贝构造了一个String对象s2,则s1和s2里面的指针_str就会指向同一块空间;当出了作用域,s2先调用析构函数,而上面代码中析构函数里面进行了空间的释放,也就是这个空间就会被s2释放,接下来会调用s1的析构函数,也会去释放这块空间,释放一块已经不属于我的空间就会出错)
② 另一方面,当两个指针指向同一块空间时,一旦一个指针修改了这块空间的值,另一个指针指向的空间的值也会被修改。
二、深拷贝及其两种写法(传统、现代)
1.传统写法
若用一个s2对象拷贝构造给s3对象,s3(s2)以及s4赋值给s3,s3=s4,当涉及到浅拷贝的问题时:
- 对于拷贝构造函数来说,s3先开一块和s2一样大的空间;然后把s2的内容原封不动的拷贝下来,再让s3的_str指向这段新开的空间。出了作用域,各自释放各自的。
- 而对于赋值运算符重载函数来说s3已经存在,则必须先释放s3的空间然后才让s3开与s4一样大的空间(否则就会导致s3里面的指针没有释放),然后再把s4拷贝过去。
优点:直观。
//深拷贝--传统写法
//s3(s2) s3是this指针,s2是s
String(const String& s)
{
_str = new char[strlen(s._str) + 1];//_str是this指针,新开一个s2大小的空间
strcpy(_str, s._str);//把s2中的各个字节拷贝给this(s3)
}
//赋值 s3=s4
String& operator=(const String& s)
{
if (this != &s)//判断不要自己给自己赋值
{
delete[] _str;//因为不知道空间大小,为避免内存泄漏,先释放掉s3原来的空间
_str = new char[strlen(s._str) + 1];
strcpy(_str, s._str);
}
return *this;
}
void TestString1()
{
String s2("hello");
String s3(s2);//未写拷贝构造函数,使用默认的,即为浅拷贝(值拷贝)
cout << s3.c_str() << endl;
String s4("world");
s3 = s4;
cout << s3.c_str() << endl;
}
2.现代写法(推荐)
调用其他的函数来实现自己的功能
- 本质:让别人去开空间,去拷数据,而我将你的空间与我交换就可以。
- 实现:例如用s2拷贝构造一个s3对象s3(s2),可以通过构造函数将s2里的指针_str构造一个临时对象tmp(构造函数不仅会开空间还会将数据拷贝至tmp),此时tmp就是我们想要的哪个对象,然后将新tmp的指针_ptr与自己的指针进行交换。
- 至于赋值,就可以直接调用swap()交换~
对于构造函数来说,因为String有一个带参数的构造函数,则用现代写法写拷贝构造时可以调用构造函数,而对于没有无参的构造函数的类只能采用传统写法(开空间然后拷数据)。
//深拷贝--现代写法
//s3(s2) tmp开辟一个和s2相同的空间,拷过来,s2是s
String(const String& s)
:_str(nullptr)
{
String tmp(s._str);//调构造
swap(_str, tmp._str);
}
////赋值 s3=s2
//String& operator=(const String& s)
//{
// if (this != &s)//判断不要自己给自己赋值
// {
// String tmp(s._str);
// strcpy(_str, tmp._str);
// }
// return *this;
//}
//更简单的赋值
String& operator=(String s)
{
swap(_str, s._str);
return *this;
}
void TestString1()
{
String s2("hello");
String s3(s2);//未写拷贝构造函数,使用默认的,即为浅拷贝(值拷贝)
cout << s3.c_str() << endl;
String s4("world");
s3 = s4;
cout << s3.c_str() << endl;
}
优点:简洁+复用
最后本文涉及代码如下:
String.h
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
#pragma once
namespace CPP
{
class String
{
public:
////无参的构造函数
//String()
// //:_str(nullptr)//不可取,会有空指针问题
// :_str(new char[1])
//{
// _str[0] = '\0';
//}
////带参的构造函数
//String(const char* str)
// :_str(new char[strlen(str)+1])
//{
// strcpy(_str, str);// while (*dst++ = *src++)'\0'已经拷贝过去了
//}
//推荐使用全缺省的带参的构造函数初始化
String(const char* str = "")//不能给nullptr,因为strlen会崩溃
:_str(new char[strlen(str) + 1])
{
strcpy(_str, str);// while (*dst++ = *src++)'\0'已经拷贝过去了
}
//拷贝构造---浅拷贝
String(const String& s)
:_str(s._str)
{}
//赋值
String& operator=(const String& s)
{
if (this != &s)
{
_str = s._str;
}
return *this;
}
//深拷贝-传统写法
//s3(s2) s3是this指针,s2是s
String(const String& s)
{
_str = new char[strlen(s._str) + 1];//_str是this指针,新开一个s2大小的空间
strcpy(_str, s._str);//把s2中的各个字节拷贝给this(s3)
}
//赋值 s3=s2
String& operator=(const String& s)
{
if (this != &s)//判断不要自己给自己赋值
{
delete[] _str;//因为不知道空间大小,为避免内存泄漏,先释放掉s3原来的空间
_str = new char[strlen(s._str) + 1];
strcpy(_str, s._str);
}
return *this;
}
//深拷贝--现代写法
//s3(s2) tmp开辟一个和s2相同的空间,拷过来,s2是s
String(const String& s)
:_str(nullptr)
{
String tmp(s._str);//调构造
swap(_str, tmp._str);
}
////赋值 s3=s2
//String& operator=(const String& s)
//{
// if (this != &s)//判断不要自己给自己赋值
// {
// String tmp(s._str);
// strcpy(_str, tmp._str);
// }
// return *this;
//}
//更简单的赋值
String& operator=(String s)
{
swap(_str, s._str);
return *this;
}
//析构函数
~String()
{
if (_str)
{
delete[] _str;
}
}
//输出c形式的字符串
char* c_str()
{
return _str;
}
//计算大小size
size_t Size()
{
return strlen(_str);
}
char& operator[](size_t pos)//返回别名,除了可读还可写
{
return _str[pos];
}
private:
char* _str;
};
void TestString1()
{
String s1;
String s2("hello");
cout << s1.c_str() << endl;//s1是一个空对象,它的_str是一个空指针
cout << s2.c_str() << endl;
for (size_t i = 0; i < s2.Size(); ++i)
{
cout << s2[i] << " ";
}
cout << endl;
String s3(s2);//未写拷贝构造函数,使用默认的,即为浅拷贝(值拷贝)
cout << s3.c_str() << endl;
String s4("world");
s3 = s4;
cout << s3.c_str() << endl;
}
}
test.cpp
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
#include "String.h"
int main()
{
CPP::TestString1();
system("pause");
return 0;
}