简单实现string类以及深浅拷贝问题

对象之间可以进行复制操作,包括采用拷贝构造函数的方式用一个对象去构造另一个对象(用一个对象的值初始化一个新的构造的对象),如同指针的复制一样,对象复制也分为浅复制和深复制

对象浅拷贝:

两个对象之间进行复制时,若复制完成后,他们还共同使用着某些资源(内存空间),其中一个对象的销毁会影响另一个对象(动态顺序表)

如果没有显式提供拷贝构造函数与赋值运算符重载,编译器会生成一个默认的拷贝构造函数和运算符重载(默认为位的拷贝,将一个对象中的内容原封不动的拷贝到到另一个对象中。如果类中涉及到资源管理,则会使得多个对象在底层共用同一块资源,在销毁对象时,就会导致一份资源释放多次引起程序崩溃)

如果一个类中涉及到资源,该类必须显式提供拷贝构造含糊,赋值运算符重载函数,析构函数

//类似系统生成的默认拷贝构造函数的方式

​ //值的拷贝方式—–内存的拷贝

​ //后果:多个对象共用同一份资源,在销毁时同一份资源被释放多次而引起程序的崩溃

1
2
3
4
5
String(const String& s)

:str(s.str) //当前对象的指针和s里的字符串共用同一段空间

{}

浅拷贝

对象深拷贝:

当两个对象之间进行复制时,若复制完成后,它们不会共享任何资源(内存空间),其中一个对象的销毁不会影响另一个对象

深拷贝

1
2
3
4
5
6
7
8
9
String(const String& s)

:str(new char[strlen(s.str) + 1]) //先分配一段空间

{

strcpy(str,s.str);

}

此时查看监视,发现s1与s2地址空间并不一样,不会产生内存泄露问题,也可以正常析构销毁

浅拷贝问题的String类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
class String
{
public:
String(const char* str = "") //创建空的字符串
{
//assert(str); //断言检测是否为空
if(nullptr == str)
str = ""; //如果为空那么就当作空字符串

_str = new char[strlen(str) + 1];
strcpy(_str,str);
/*
if(nullptr == str)
{
//_str = new char //分配一个字节的空间,但在下边析构时需要与delete匹配起来使用,为了方便,将其设为以下形式
_str = new[1] char;
*_str = "\0"
}
else
{
_str = new char[strlen(str) + 1];
strcpy(_str,str);
}
*/
}

//类似系统生成的默认拷贝构造函数的方式
//值的拷贝方式-----内存的拷贝
//后果:多个对象共用同一份资源,在销毁时同一份资源被释放多次而引起程序的崩溃
String(const String& s)
:_str(s._str) //当前对象的指针和s里的字符串共用同一段空间
{}

//类似系统生成的默认的赋值运算符重载的方式
//问题:1.内存泄露
// 2.与拷贝构造函数类似
String& operator=(const String& s)
{
if(this != &s)
{
_str = s._str;
return *this;
}
}

~String()
{
if(_str) //判断是否有空间
{
delete[] _str;
_str = nullptr;
}
}

private:
char* _str;
};

void TestString()
{
String s1("hello");
String s2(s1); //用s1拷贝构造s2,因为没有自己给出拷贝构造函数,系统会默认使用类生成的拷贝构造函数进行值的拷贝(浅拷贝),销毁期间会对一段资源销毁两次产生程序而崩溃
String s2 = s1; //此时会看到s2本身有一个地址空间,但是在赋值时完全将s1中的东西拷贝,使得s2本来的空间找不到了,产生内存泄漏
}

使用深拷贝进行处理

传统方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
class String
{
public:
String(const char* str = "") //创建空的字符串
{
//assert(str); //断言检测是否为空
if(nullptr == str)
str = ""; //如果为空那么就当作空字符串

_str = new char[strlen(str) + 1];
strcpy(_str,str);
/*
if(nullptr == str)
{
//_str = new char //分配一个字节的空间,但在下边析构时需要与delete匹配起来使用,为了方便,将其设为以下形式
_str = new[1] char;
*_str = "\0"
}
else
{
_str = new char[strlen(str) + 1];
strcpy(_str,str);
}
*/
}

String(const String& s)
:_str(new char[strlen(s._str) + 1]) //先分配一段空间
{
strcpy(_str,s._str);
}

String& operator=(const String& s)
{
if(this != &s)
{
/*
释放旧空间,开辟新空间,再进行字符串拷贝
delete[] _str; //因为先释放了原来空间,如果开辟新空间失败了,那么会造成影响
_str = new char[strlen(s._str) + 1];
strcpy(_str,s._str);
*/
char* pStr = new char[strlen(s._str) + 1];
strcpy(_str,s._str);
delete[] _str; //释放掉旧空间
_str = pStr;
}
return *this;
}

~String()
{
if(_str) //判断是否有空间
{
delete[] _str;
_str = nullptr;
}
}

private:
char* _str;
};


void Test()
{
String s1("hello");
String s2(s1);
}

int main()
{
Test();
return 0;
}

现代写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
//代码较简洁
class String
{
public:
String(const char* str = "") //创建空的字符串
{
if(nullptr == str)
str = ""; //如果为空那么就当作空字符串

_str = new char[strlen(str) + 1];
strcpy(_str,str);
}

String(const String& s)//注意!本编译器下此时_str没有进行初始化,放的是一个随机值,所以在释放strTemp时出错,所以需要给一个初始值
:_str(nullptr)
{
String strTemp(s._str);
swap(_str,strTemp._str);
}

/*
String& operator=(const String& s)
{
if(this != &s)
{
String strTemp(s);
swap(_str,strTemp._str);
}
return *this; //当前对象用的是临时对象的空间,出了作用域销毁临时对象,实际是将当前对象的地址空间释放了
}
*/

String& operator=(String s)
{
swap(_str,s._str);
return *this;
}

~String()
{
if(_str) //判断是否有空间
{
delete[] _str;
_str = nullptr;
}
}

private:
char* _str;
};

void Test()
{
String s1("hello");
String s2(s1);
String s3;
s3 = s2; //此时实际是临时对象给s3赋值的
}

int main()
{
Test();
return 0;
}

写时拷贝

1.在对象中定义一个成员变量来计数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class String
{
public:
String(const char* str = "")
{
if(nullptr == str)
str = "";

_str = new char[strlen(str) + 1];
strcpy(_str,str);
_count = 1;
}

String(String& s)
:_str(s._str)
,_count(++s._count)
{}

~String()
{
if(0 == --_count && _str)
{
delete[] _str;
_str = nullptr;
}
}

private:
char* _str;
int _count; //每个对象中均有一份,一个对象修改了其他对象不知道
};

2.使用static修饰成员变量,但是所有对象共享的,而资源有可能会有多分,每调用一次构造就将_count置为1了,不能针对多份资源

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
//static也不可以,是类中所有对象共享的
class String
{
public:
String(const char* str = "")
{
if(nullptr == str)
str = "";

_str = new char[strlen(str) + 1];
strcpy(_str,str);
_count = 1;
}

String(String& s)
:_str(s._str)
{
++_count;
}

~String()
{
if(0 == --_count && _str)
{
delete[] _str;
_str = nullptr;
}
}

private:
char* _str;
static int _count; //所有对象共享的,但资源有可能会有多分,每调用一次构造就将_count置为1了,不能针对多份资源,如 String s3;
};

int String::_count = 0;

void Test()
{
String s1("hello");
String s2(s1);
String s3; //此时会出现问题,到这里时_count重新被置为1,导致只能将s3释放而无法释放s1和s2
}

int main()
{
Test();
return 0;
}

3.写时拷贝(COW copy on write):浅拷贝+引用计数+在向对象写内容时,是否需要给当前对象独立空间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97

class String
{
public:
String(const char* str = "")
:_pCount(new int(1))
{
if(nullptr == str)
str = "";

_str = new char[strlen(str) + 1];
strcpy(_str,str);
}

String(String& s)
:_str(s._str)
,_pCount(s._pCount)
{
++*(_pCount);
}

String& operator=(const String& s)
{
if(this != &s)
{
if(0 == --(*_pCount) && _str) //检测拷贝以后自己的资源需不需要释放
{
delete[] _str;
_str = nullptr;

delete _pCount;
_pCount = nullptr;
}

//与被拷贝的资源共享资源
_str = s._str;
_pCount = s._pCount;

//新资源计数+1
++(*_pCount);
}
return *this;
}

char& operator[](size_t index) //返回引用是因为有可能返回后作为左值
{
if(*_pCount > 1)
{
String str(_str);
this->Swap(str);
}
return _str[index];
}

const char& operator[](size_t index)const
{
return _str[index];
}

~String()
{
if(0 == --(*_pCount) && _str)
{
delete[] _str;
_str = nullptr;

delete _pCount;
_pCount = nullptr;
}
}

void Swap(String& s)
{
swap(_str,s._str);
swap(_pCount,s._pCount);
}
private:
char* _str;
int* _pCount;
};

void Test()
{
String s1("hello");

String s2(s1);

String s3;
//s3 = s1;
s1 = s3;
s1[0] = 'H'; //此时一改变会全改变,s1,s2,s3共用一份资源
}

int main()
{
return 0;
}
0%