在仿写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& operator[](int index);
int getLen();
~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;
getCount() = 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);
}
this->my_str = src.my_str;
this->capacity = src.capacity;
this->str_len = src.str_len;
++getCount();
}
return *this;
}
char& operator[](int index)
{
if (index < 0 || index > str_len)
return ch;
if (getCount() == 1)
{
return my_str[index];
}
--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;
delete[] my_str;
my_str = NULL;
}
}
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)。 只有搞清楚了所有情况代码才能书写正确。
|