文章目录
在仿写string类之前,我们先来了解下两个概念:
(一)浅拷贝
调用拷贝构造函数生成新对象时,并不去真正的开辟堆区空间来存放新对象,而使用新对象的this指针指向被拷贝的对象的地址;
- 优点:节省空间
- 缺点:会带来空指针的问题,两个对象对同一个地址调用两次delete,导致程序发生崩溃
(二)深拷贝
调用拷贝构造函数生成新对象时,去堆区空间开辟新的地址空间去存放构造的对象;
- 优点:不会出现空指针的问题
- 缺点:浪费空间
(三)写时拷贝
由于浅拷贝的空指针问题、深拷贝的浪费空间的缺点,引出了写时拷贝技术,简单来说就是,当拷贝构造一个新对象的时候使用浅拷贝,当该对象被修改(写入)时,再重新开辟堆区空间,将旧对象的数据拷贝到新对象的空间中。最终完成修改(写入)操作。
- 优点:结合了浅拷贝的优点、深拷贝的优点,在一定程度上节省了空间、提高了效率。
(四)猜想写时拷贝的实现机制
假设我们已经实现了写时拷贝技术, 并且构造了str1这个对象,并用str1拷贝构造出了str2这个对象,当我们在修改str2的内容的时候,我们应该怎么判断str1这个对象究竟有多少个对象使用了浅拷贝,用this指针指向了str1呢?
有个叫做引用计数的东西(计数君:))他仿佛可以帮助我们判断一个对象究竟有多少个this指针指向了str1
(五)mystring类的设计:
我们把(int类型)引用计数放在字符串的前四个字节,逻辑图如下:
(1)成员属性的设置
class mystring
{
public:
private:
int str_len; //字符串的长度
int capacity; //容量
char* my_str; //字符串空间地址 在构造之前需要初始化
int& getCount()//获取引用计数的接口
{
return *((int*)(my_str - 4));
}
};
(2)成员方法的设计
#include <iostream>
#pragma warning(disable:4996)
using namespace std;
char ch = '\0';
class mystring
{
public:
//有参构造
mystring(const char* str);
//拷贝构造
mystring(mystring& src);
//赋值运算符重载
mystring& operator=(const mystring& src);
//[]读取操作运算符重载 类似char arr[2] = {0};读取arr[0];
char& operator[](int index);
//提供字符串长度接口
int getLen();
//析构
~mystring();
//输出mystring对象
friend ostream& operator<<(ostream& os, const mystring& src);
private:
int str_len; //字符串的长度
int capacity; //容量
char* my_str; //字符串空间地址 在构造之前需要初始化
int& getCount()//获取引用计数的接口
{
return *((int*)(my_str - 4));
}
};
(六)完整代码实现
#include <iostream>
#include <cstring>
using namespace std;
char ch = '\0';
class mystring
{
public:
//有参构造
mystring(const char* str)
:
str_len(strlen(str)), //字符串长度
capacity(1 + str_len + 4),
my_str(new char[capacity])
{
my_str += 4; //my_str移动到字符串空间
getCount() = 1; //计数君1
strcpy(my_str, str);
}
//拷贝构造
mystring(mystring& src)
{
//浅拷贝
this->my_str = src.my_str;
this->str_len = src.str_len;
this->capacity = src.capacity;
++getCount();
}
//赋值运算符重载
mystring& operator=(const mystring& src)
{
//防止自赋值
if (this != &src)
{
//判断左值对象是否有别的对象的引用
--getCount();
if (getCount() == 0)
{
delete[] (my_str - 4);
}
//左值引用计数不为0,说明有其他对象指向this的空间
this->my_str = src.my_str;
this->capacity = src.capacity;
this->str_len = src.str_len;
++getCount();
}
return *this;
}
//[]读取操作运算符重载 类似char arr[2] = {0};读取arr[0];
char& operator[](int index)
{
//下标访问越界
if (index < 0 || index > str_len)
return ch;
//判断修改的对象的引用只有自己
if (getCount() == 1)
{
return my_str[index];
}
//this指向的空间有其他对象指向
--getCount(); //释放自身计数
//开辟同等大小新空间
char* newspace = new char[capacity];
//转移数据
strcpy((newspace + 4), this->my_str);
this->my_str = newspace + 4;
getCount() = 1;
return my_str[index];
}
//提供字符串长度接口
int getLen()
{
return str_len;
}
//析构
~mystring()
{
getCount()--;
if (getCount() == 0)
{
my_str -= 4; //my_str回到空间首地址
delete[] my_str; //释放开辟的空间
my_str = NULL;
}
}
//输出mystring对象
friend ostream& operator<<(ostream& os, const mystring& src);
private:
int str_len; //字符串的长度
int capacity; //容量
char* my_str; //字符串空间地址 在构造之前需要初始化
int& getCount()//获取引用计数的接口
{
return *((int*)(my_str - 4));
}
};
//类外实现<<运算符重载
ostream& operator<<(ostream& os, const mystring& src)
{
os << "str:" << src.my_str << " ";
os << "str_len:" << src.str_len << " ";
os << "str_capacity:" << src.capacity << endl;
return os;
}
void Test()
{
mystring str1("hello");
cout << str1;
mystring str2(str1);
cout << str2;
mystring str3("jiege 1024");
cout << str3;
str1 = str3;
cout << str1;
str3[5] = 'g';
cout << str3;
}
int main()
{
Test();
return 0;
}
(七)测试结果
(八)总结语
需要注意引用计数器的值,考虑到所有的情况(引用数为1引用数不为1)。
只有搞清楚了所有情况代码才能书写正确。