深浅拷贝

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

对象浅拷贝:

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

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

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

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

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

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

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

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

{}

img

对象深拷贝:

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

img

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地址空间并不一样,不会产生内存泄露问题,也可以正常析构销毁

img

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
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
#include<assert.h>
#include<iostream>
using namespace std;


#if 0
浅拷贝问题的String类
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本来的空间找不到了,产生内存泄漏
}
#endif


#if 0
使用深拷贝进行处理
传统方式
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;
}
#endif

#if 0
现代版写法,代码较简洁
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;
}
#endif

#if 0
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; //每个对象中均有一份,一个对象修改了其他对象不知道
};
#endif

/*
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;
}*/

#if 0
//写时拷贝(COW copy on write):浅拷贝+引用计数+在向对象写内容时,是否需要给当前对象独立空间
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;
}
#endif
0%