- 1.浅拷贝问题解决
- 1.1浅拷贝存在的问题
- 1.2通过深拷贝的方式解决浅拷贝问题
- 传统版解决方式
- 现代版解决方式
- 1.3通过写时拷贝解决
- 理解写时拷贝方法
- 1.4验证不同平台的string类是通过什么方式解决浅拷贝的
- 2、string类实现
对于浅拷贝,主要存在于拷贝构造和赋值运算符重载的过程中,下面给出一段代码结合分析
#pragma warning(disable:4996)
#include
using namespace std;
class String
{
public:
String(const char* str = "")
{
if (nullptr == str)
{
str = " ";
}
_str = new char[strlen(str) + 1];
strcpy(_str, str);
}
~String()
{
if (_str)
{
delete[] _str;
_str = nullptr;
}
}
private:
char* _str;
};
void StringTest()
{
String s1("Hello");
String s2(s1);
}
int main()
{
StringTest();
return 0;
}
执行时程序直接崩溃
下面分析一下原因:
上述的崩溃时由于浅拷贝导致的多次释放问题
再来看下面的代码快:
分析:
本次报错是因为执行s2 = s1时,导致内存泄漏以及多次释放的问题,具体看下面的分析
总结一下:
浅拷贝会导致①内存泄露②多次释放同一块空间
这些错误都是极其严重的,我们务必要避免!
接下来就来探讨一下如何解决这些问题:
本质:让每一个对象都拥有一份独立的资源
传统版解决方式- 解决拷贝构造
- 解决赋值运算符重载
下面给出解决的完整代码
#pragma warning(disable:4996)
#include
using namespace std;
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(new char[strlen(s._str)+1])
{
strcpy(_str, s._str);
}
String& operator=(const String& s)
{
if (this != &s)
{
char* temp = new char[strlen(s._str) + 1];
strcpy(temp, s._str);
delete[] _str;
_str = temp;
}
return *this;
}
~String()
{
if (_str)
{
delete[] _str;
_str = nullptr;
}
}
private:
char* _str;
};
void StringTest()
{
String s1("Hello");
String s2("World");
s2 = s1;
//String s2(s1);
}
int main()
{
StringTest();
return 0;
}
现代版解决方式
仔细观察上面的代码,我们发现其实重复的 *** 作很多,比如每次申请新空间,拷贝元素。
现代版的方式就是采用巧妙地代码复用,将繁琐的 *** 作简洁化
具体修改在拷贝构造和赋值运算符重载处
下面我们一一解决
- 拷贝构造的优化
- 赋值运算符重载的优化
以上便是对之前代码的一些优化!
写时拷贝就是一种拖延症,是在浅拷贝的基础之上增加了引用计数的方式来实现的。
引用计数:用来记录资源使用者的个数。
在构造时,将资源的计数给成1,每增加一个对象使用该资源,就给计数增加1,
当某个对象被销毁时,先给该计数减1,然后再检查是否需要释放资源,
如果计数为1,说明该对象时资源的最后一个使用者,将该资源释放;
否则就不能释放,因为还有其他对象在使用该资源。
下面使用图解的方式解释一下:
OK,看了上面的解法,有没有感觉有点问题呢?
上面的过程并没有涉及到对象内容修改,假设我现在想将上面s2对象的内容修改为“World”;由于s1 s2共用一块内存空间,所以修改s2的同时s1的内容也被修改了
但是很明显,这不是我的本意,也不符合规矩。
那如何解决这个棘手问题呢?
其实上面的这个过程还没有涉及到我们的写时拷贝。
所谓的写时拷贝是在上面的基础上解决修改(写)内容的时候发生的问题
具体步骤如下:
假设我们现在已经处于上面图示的场景,即s1和s2共用同一块内存空间,计数器此时为2
现在我想要将s2的内容改为“World”,需要执行以下步骤:
(1)为s2对象开辟新的空间
(2)将原来的空间的计数器值减减
(3)将s1对象的内容拷贝至为s2新开辟的空间中,并将s2中计数器值设置为1
(4)s2在新空间内进行修改 *** 作
下面通过图示的方式再次演示该过程:
以上便是写时拷贝的粗略思路,具体细节内容等到后续总结,目前处于扫盲状态。
验证思路:
(1)实例化一个对象s1,长度大于15;
注意:长度必须大于15,否则无法测试出正确的结果。
因为在Windows下,string类中维护着一个空间为16的字符数组,因此小于等于15的字符串直接被存储在数组内,并不会申请空间,也就无法验证。
(2)通过拷贝构造实例化对象s2
(3)打印s1和s2对象的地址,观察地址值是否一样
如果一样 -----> 写时拷贝
如果不一样 ----> 深拷贝
验证代码如下:
void TestCopy()
{
string s1(20, 'A');
string s2(s1);
printf("&s1 is %p\n",s1.c_str());
printf("&s2 is %p\n", s2.c_str());
}
int main()
{
TestCopy();
return 0;
}
1、在Windows平台下的VS2013环境中
结论:vs2013中string是按照深拷贝实现的
2、在Linux平台下
结论:Linux中string是按照写时拷贝实现的
有了前面的知识做铺垫,这里直接给出代码,模拟实现只是对主要的方法进行模拟,并不是完全实现一个string容器
//mystring.h
#pragma once
#include
namespace gyj
{
class string
{
friend std::ostream& operator<<(std::ostream& _cout, const gyj::string& s);
public:
typedef char* iterator;
public:
/构造和析构///
string(const char* s = "");
string(const string& str);
string& operator=(const string& str);
~string();
///迭代器相关/
iterator begin();
iterator end();
///容量相关//
size_t size()const;
size_t capacity()const;
bool empty()const;
void resize(size_t n, char c = ')';void
reserve ()size_t n;///元素访问相关//
char
&operator []()size_t index;const
char &operator []()size_t indexconst;///修改相关的///
void
push_back (char) c;&
stringoperator +=(char) c;void
append (constchar *) str;&
stringoperator +=(constchar *) str;void
clear ();void
swap (&string) s;const
char *c_str ()const;/其他//
bool
operator <(const& string) s;bool
operator <=(const& string) s;bool
operator (>const& string) s;bool
operator (>=const& string) s;bool
operator ==(const& string) s;bool
operator !=(const& string) s;// 返回c在string中第一次出现的位置
find
size_t (char, c= size_t pos 0 )const ;// 返回子串s在string中第一次出现的位置
find
size_t (constchar *, s= size_t pos 0 )const ;// 在pos位置上插入字符c/字符串str,并返回该字符的位置
&
stringinsert (,size_t poschar ) c;&
stringinsert (,size_t posconst char *) str;// 删除pos位置上的元素,并返回该元素的下一个位置
&
stringerase (,size_t pos) size_t len;private
:char
*; _str;
size_t _size;
size_t _capacity}
;}
extern
void stringTest1 ();extern
void stringTest2 ();#
接下来是mystring.cpp
pragmawarning (:disable4996)#
include#
include"mystring.h"#
include::
gyj::stringstring(constchar *) sif
{
( nullptr== ) sassert
{
(0);}
=
_size strlen ()s;=
_capacity ; _size=
_str new char [+_capacity1 ];strcpy
(,_str) s;}
::
gyj::stringstring(const& string) str:
_str(nullptr),
_size (0),
_capacity (0)temp
{
string (.str)_str;::
stdswap(,_str. temp)_str;=
_size . str;_size=
_capacity . str;_capacity}
::
gyj&string:: gyj::stringoperator=(const& string) strif
{
( this!= & )strtemp
{
string (.str)_str;::
stdswap(,_str. temp)_str;=
_size . str;_size=
_capacity . str;_capacity}
return
* this;}
::
gyjstring::~string()if
{
( )_strdelete
{
[]; _str=
_str nullptr ;}
}
///迭代器相关/
::
gyj::string::iterator gyj::stringbegin()return
{
; _str}
::
gyj::string::iterator gyj::stringend()return
{
+ _str ; _size}
///容量相关//
::
size_t gyj::stringsize()constreturn
{
; _size}
::
size_t gyj::stringcapacity()constreturn
{
; _capacity}
bool
:: gyj::stringempty()constif
{
( 0== ) _sizereturn
{
true ;}
return
false ;}
void
:: gyj::stringresize(,size_t nchar ) cif
{
( )n > _sizeif
{
( )n > _capacityreserve
{
()n;}
memset
(+_str , _size, c- n ) _size;}
=
_size ; n[
_str]_size= ';' }void
::
:: gyjreservestring()ifsize_t n(
{
) charn > _capacity*
{
=new temp char [ +1n ] ;strcpy(
,)temp; _strdelete[
];= _str;
_str = temp;
_capacity } n}
///元素访问相关//
char
&
:::: gyjoperatorstring[]()assertsize_t index(
{
<)index ; _sizereturn[
] _str;index}const
char
& :::: gyjoperatorstring[]()constsize_t indexassert(
{
<)index ; _sizereturn[
] _str;index}///修改相关的///
void
::
:: gyjpush_backstring(char)if c(
{
== )_size //扩容 _capacityreserve
{
(
*2_capacity ) ;}[
++
_str]_size=; [ c]
_str=_size';' } ::&
::
gyj::stringoperator gyj+=string(char)push_back( c)
{
;returnc*this
; }void::
::
append gyj(stringconstchar*) if( str+
{
strlen (_size))//扩容str= >= _capacity*
{
2
size_t newcapacity + _capacity strlen ( > _size ) ?*str2 : _capacity + strlen ( _size ) +1str; reserve ()
;}newcapacitystrcat(
,
);_str+= strstrlen(
_size ) ;}str::&
::
gyj::stringoperator gyj+=string(constchar*) append( str)
{
;returnstr*this
; }void::
::
clear gyj(string)[0]
{
_str=';'= 0 ;}
_size void ::::
swap
( gyj&string)::swapstring( s,
{
std.);_str:: sswap_str(,
std.);_size:: sswap_size(,
std.);_capacity} sconst_capacitychar*
::
:: c_str( gyj)stringconstreturn;}// <<重载
{
:: _str&
::
operator
std<<ostream( gyj::&,conststd::ostream& _cout) << gyj.string; sreturn
{
_cout ; s}_str/其他//
bool _cout::
::
operator
< gyj(stringconst&)int= stringstrcmp s(
{
, ret . );_strif s(_strret<
0 )return true ;}
{
return false;
}
bool ::::
operator
<= gyj(stringconst&)return! string( s*
{
this );}bool>s::::
operator
( gyjconststring&)>int= stringstrcmp s(
{
, ret . );_strif s(_str0)
return trueret > ;}
{
return false;
}
bool ::::
operator
( gyjconststring&)>=return! string( s*
{
this <);} bool s::::
operator
== gyj(stringconst&)int= stringstrcmp s(
{
, ret . );_strif s(_str==0
) returnret true ;}
{
return false;
}
bool ::::
operator
!= gyj(stringconst&)return! string( s*
{
this ==);} // 返回c在string中第一次出现的位置 s::::
find
(
size_t gyjcharstring,)constfor c( size_t pos= ;
{
< ;size_t i ++ pos) i if _size( i==[
{
] )c return _str;i}}
{
return i-
1
;
} // 返回子串s在string中第一次出现的位置::::
find
(
size_t gyjconststringchar*,) constassert s( size_t pos) ;
{
assert(s<)
;constpos char _size*=
+ ;while src ( _str * pos)
const char*src=
{
; constchar match * s=
; while( cur * src&&
* ==*match ) ++match ; ++cur;
{
}matchif
(cur*
==
')' return-match ; }else
{
++ src ; _str}
}
return
{
-src1
;
}
// 在pos位置上插入字符c/字符串str,并返回该字符的位置 ::&::
::
insert
gyj(string, gyjcharstring)assert(size_t pos<= ) c;
{
if(pos == _size)//扩容
char *_size = _capacitynew
{
char
[ *newstr 2 + 1]_capacity ; strcpy ( ,);
delete[newstr] _str;=
;*=2 _str;
_str } newstr//移数据
_capacity for (int
=
;
( int) i ; _size-- i >= )[+pos1 ]i=
{
_str[i ] ;} [ _str]i=;
++
_str;posreturn * cthis
_size;}
:: &::::
insert
gyj(string, gyjconststringchar*)size_t pos= strlen () str;
{
size_t len if (+str)//扩容
//扩容 char_size * len > _capacity=new
{
char
[ *newstr 2 + 1]_capacity ; strcpy ( ,);
delete[newstr] _str;=
;*=2 _str;
_str } newstr//后移数据
_capacity for (int
=
;
( int) i ; _size-- i >= )[+pos] =i[
{
_str]len ; i} //拷贝字符串 _strwhilei(*
!=
')'
[ ++]str = *++
{
_str;pos}+= ; returnstr*this
;
_size } len// 删除pos位置上的元素,并返回该元素的下一个位置
:: &::::
erase
(
gyj,string) gyjassertstring(<)size_t pos; size_t lenif
{
(+pos ) _size//pos位置之后全为0[
] =pos ';' len >= _size=;
{
_str}poselse strcpy (+
_size , pos+
+
)
{
;-=_str ; pos} _str return pos * lenthis;
_size } lenvoid
stringTest1
( )::s1
(
"hello" );::
{
gyjs2string ();::;
gyj=string ;::s1<<.
gyjsizestring s3(
s3 ) s2<<
std::cout ;s1::<<.capacity ( std)endl<<
std::cout ; s1::<<.empty ( std)endl<<
std::cout ; s1.resize(3 ) std;endl.
s1resize(10);.
s2resize(15,'A')
s3;.reserve(4 );.
s1reserve(20);.
s2reserve(100);::
s3<<[0]<<::
std;cout :: s1<<[3 ] std<<endl::
std;cout const s2::s4( "aaaaaa" std)endl;
char gyj=string [3];//_CrtDumpMemoryLeaks();
} ch void s4stringTest2()::
s1
(
"Hello" );.
{
gyjpush_backstring (' ');.
s1append("World");+=
s1'!';+="~~~~";::
s1 << .c_str
s1 ( )<<
std::cout ; s1::s2("12345" ) std;endl.
gyjswapstring ();.clear
s1();s2_CrtDumpMemoryLeaks(
s1);}
具体代可以参考我的码云
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)