个人学习笔记
写时复制的目的就是,对于n个内容相同的字符串来说,如果都进行深拷贝的话势必会浪费很多存储空间,所有对于不需要写操作的字符串来说,就只进行浅拷贝,让这些变量都指向这里,当需要更改的时候,再进行深拷贝,对深拷贝的字符串进行写操作,这就是写时复制,可以节省很多内存空间,所以实现自定义的String的时候一定要区分读写操作,如果直接对自定义String类的下标运算符重载来进行写操作的话,那么通过下标运算符读某个字符串的时候也会写操作,因为都要经过同样的操作,这样就无法区分读写操作了,所以行不通;那么能否通过赋值运算符和输出流运算符的重载来区分读写呢?重载赋值运算符进行写操作,重载输出流运算符进行读操作,我们知道运算符的重载需要至少一个自定义类型或者枚举类型,而赋值运算符(=)的两边都是char类型,所以不能重载赋值运算符,所以此时无法通过赋值运算符和输出流运算符的重载来区分写操作;又因为赋值运算符的左边与输出流运算符的右边都是通过下标访问运算符的重载之后获得的返回值,刚刚已经说过不能通过下标访问运算符的重载来区分读写操作,所以从目前的分析来看无法实现写时复制了。其实本来可以通过赋值运算符和输出流运算符的重载来区分读写操作的,只是因为赋值运算符无法重载,而导致失败,那么如果赋值运算符可以重载呢,是不是就意味着这个问题可以解决,如果赋值运算符无法重载等式2边都是内置类型的,那么如果通过下标运算符重载返回的不是一个char类型的是不是就可以重载了,只要赋值运算符可以重载,那么写时复制也就不难实现了!这时我们的思路就是通过重载下标访问运算符返回一个自定义类型,然后对该自定义类型的赋值运算符和输出流运算符进行重载,这个问题不就顺理成章的解决了!所以这里我们需要借助一个代理商(CharProxy),重载String的下标访问运算符,返回一个代理商类型,然后重载该代理商的赋值运算符和输出流运算符,这样就可以区分读写操作。因为正常情况下String类的下标访问运算符的重载返回的是一个char类型,所以String不可以重载赋值运算符,通过一个自定义的CharProxy类型,这样就可以重载赋值运算符,结合输出流运算符的重载就可以达到区分读写的目的,这个CharProxy就是帮String完成这些功能,这就是所谓的代理模式。
因为String要实现写实复制,所以在申请一块空间存储字符串的时候,除了要多申请一个字节存储’\0’以外,还要在该空间的最开始的位置划出4个字节来存储引用计数,所以默认构造函数和传参C风格字符串的构造函数对对象存储空间的申请都要加上额外的5个字节,并将指针向前偏移4个字节来操作字符串,对引用计数初始化需要往回偏移4个字节并强转成int型,才可以操作引用计数申请的4个字节的空间,此时一定要注意指针的位置;对于拷贝构造函数就只需要增加引用计数即可;赋值运算符的重载需要注意一点,需要被赋值的字符串的引用计数减1,赋值的字符串引用计数加1,在释放左操作数的时候把引用计数申请的4个字节空间一起回收掉;析构函数这里,对于指向同一字符串的对象,释放掉一个对象,引用计数减1,并不需要对每个对象都delete,只需要对引用计数为0的最后一个对象delete释放掉即可。由于CharProxy只需要为String服务,所以这里把CharProxy设为私有,再对CharProxy的赋值运算符和输出流运算符重载即可,赋值运算符的重载就是完成写时复制的操作,输出流运算符重载就是实现读的操作。最后,因为我们需要String下标访问运算符最终返回的就是一个char类型的字符,这样可以不用重载输出流运算符,所以这里我们通过类型转换函数,将自定义的CharProxy转换为char,代替输出流运算符的重载,简化操作。
#include
#include
using std::cout;
using std::endl;
class String
{
public:
String()
:_pstr(new char[5]() + 4)
{
cout << "String()" << endl;
//_pstr创建的是一个char型指针,需要转换成int型指针控制4个字节
*(int *)(_pstr - 4) = 1;
}
//String s1 = "hello";
String(const char *pstr)
:_pstr(new char[strlen(pstr) + 5]() + 4)//new char[strlen(pstr) + 5](),加了()后,数据都初始化为0了,就相当于memset,已经初始化了并设置为0了。
{
cout << "String(const char *)" << endl;
strcpy(_pstr, pstr);
*(int *)(_pstr - 4) = 1;
}
//String s2 = s1;
String(const String &rhs)
:_pstr(rhs._pstr)
{
cout << "String(const String &)" << endl;
++*(int *)(rhs._pstr - 4);
}
int getRef()
{
return *(int *)(_pstr - 4);
}
size_t size() const
{
return strlen(_pstr);
}
//String s3 = "world"
//s3 = s1;
String &operator=(const String &rhs)
{
cout << "String &operator=(const String &)" << endl;
if(this != &rhs)//1、自复制
{
/* auto ref = --*(_pstr - 4); */
--*(int *)(_pstr - 4);
if(0 == *(int *)(_pstr - 4))//2、释放左操作数
{
/* delete []_pstr; //error*/
//这里需要先把指针偏移,然后delete
delete [](_pstr - 4);
_pstr = nullptr;
}
_pstr = rhs._pstr;//3、浅拷贝
++*(_pstr - 4);
}
return *this;//4、返回this指针
}
//将String风格字符转换成C风格
const char* c_str() const
{
return _pstr;
}
~String()
{
cout << "~String()" << endl;
//析构函数这里,对于指向同一字符串的对象,释放掉一个对象,引用计数减一
//并不需要对每个对象都delete,只需要对引用计数为0的
//最后一个对象delete释放掉即可,
--*(int *)(_pstr - 4);
if(0 == *(int *)(_pstr - 4))
{
//这里需要先把指针偏移,然后delete
delete [](_pstr - 4);
/* delete []_pstr;//error */
}
/* if(nullptr == _pstr) */
/* { */
/* delete [] _pstr; */
/* _pstr = nullptr; */
/* } */
}
friend std::ostream &operator<<(std::ostream &os, const String &rhs);
private:
//设计模式之代理模式
class CharProxy
{
public:
CharProxy(String &str, size_t idx)
:_str(str)
,_idx(idx)
{
cout << "CharProxy(String &, size_t)" << endl;
}
char &operator=(const char &ch)
{
if(_idx < _str.size())
{
if(_str.getRef() > 1)
{
--*(int *)(_str._pstr - 4);
char *tmp = new char[strlen(_str._pstr)]() + 4;
strcpy(tmp, _str._pstr);
_str._pstr = tmp;
cout << "tmp.sizeof = " << strlen(tmp) << endl;
*(int *)(_str._pstr - 4) = 1;
}
_str._pstr[_idx] = ch;//真正的的赋值操作
return _str._pstr[_idx];
}
else
{
static char nullchar = '\0';
return nullchar;
}
}
//类型转换函数,将自定义的CharProxy转换为char
//代替输出流运算符的重载
operator char()
{
return _str._pstr[_idx];
}
/* friend std::ostream &operator<<(std::ostream &os, const CharProxy &rhs); */
private:
String &_str;
size_t _idx;
};
public:
//CharProxy要在String下标运算符重载之前定义
//不可以返回CharProxy类型的引用,因为return的是临时变量是个右值
CharProxy operator[](size_t idx)
{
return CharProxy(*this, idx);
}
/* friend std::ostream &operator<<(std::ostream &os, const CharProxy &rhs); */
private:
char *_pstr;
};
std::ostream &operator<<(std::ostream &os, const String &rhs)
{
//判断一下rhs的数据成员是否为空
if(rhs._pstr)
{
os << rhs._pstr;
}
return os;
}
#if 0
//因为rhs要同时访问String和CharProxy的私有成员
//所以要将该输出流运算符同时设置为String和CharProxy的友元函数
std::ostream &operator<<(std::ostream &os, const String::CharProxy &rhs)
{
os << rhs._str._pstr[rhs._idx];
return os;
}
#endif
int main()
{
String s1("hello");
String s2 = s1;
String s3("world");
s3 = s1;
cout << "s1.getRef = " << s1.getRef() << endl;
cout << "s2.getRef = " << s2.getRef() << endl;
cout << "s3.getRef = " << s3.getRef() << endl;
cout <<"s1 = " << s1 << endl << "s2 = " << s2 << endl << "s3 = " << s3 << endl;
printf("s1's address = %p\n", s1.c_str());
printf("s2's address = %p\n", s2.c_str());
printf("s3's address = %p\n", s3.c_str());
cout << endl;
s3[0] = 'H';
cout << "s1.getRef = " << s1.getRef() << endl;
cout << "s2.getRef = " << s2.getRef() << endl;
cout << "s3.getRef = " << s3.getRef() << endl;
cout <<"s1 = " << s1 << endl << "s2 = " << s2 << endl << "s3 = " << s3 << endl;
printf("s1's address = %p\n", s1.c_str());
printf("s2's address = %p\n", s2.c_str());
printf("s3's address = %p\n", s3.c_str());
cout << endl;
//s[0]将自动匹配可以转换的类型,CharProxy类中定义了char的类型转换函数
//所以此时s[0]将自动从CharProxy类型转换成char,所以可以直接输出,而不需要重载CharProxy的输出流运算符
cout << "s3[0]" << s1[0] << endl;
cout << "s1.getRef = " << s1.getRef() << endl;
cout << "s2.getRef = " << s2.getRef() << endl;
cout << "s3.getRef = " << s3.getRef() << endl;
cout <<"s1 = " << s1 << endl << "s2 = " << s2 << endl << "s3 = " << s3 << endl;
printf("s1's address = %p\n", s1.c_str());
printf("s2's address = %p\n", s2.c_str());
printf("s3's address = %p\n", s3.c_str());
return 0;
}