C++学习笔记---010
C++知识string类的模拟实现
前言:
前面篇章学习了C++对于string类的基本使用以及常用接口的认识,接下来继续学习,C++的string类的模拟实现等知识。
/知识点汇总/
1、模拟实现string
class string
{
friend ostream& operator<<(ostream& _cout, const bit::string& s);
friend istream& operator>>(istream& _cin, bit::string& s);
public:
typedef char* iterator;
typedef const char* const_iterator;
static const int npos;
public:
string(const char* str = "");
string(const string& s);
string& operator=(const string& s);
~string();
//
// iterator
iterator begin();
iterator end();
const_iterator begin() const;
const_iterator end() const;
/
// modify
void push_back(char c);
string& operator+=(char c);
void append(const char* str);
string& operator+=(const char* str);
void clear();
swap(string& s);
const char* c_str() const;
//substr返回子串
string substr(size_t pos = 0, size_t len = npos);
/
// capacity
size_t size() const;
size_t capacity() const;
bool empty() const;
void resize(size_t n, char c = '\0');
void reserve(size_t n);
/
// access
char& operator[](size_t index);
const char& operator[](size_t index) const;
/
// 返回c在string中第一次出现的位置
size_t find(char c, size_t pos = 0) const;
// 返回子串s在string中第一次出现的位置
size_t find(const char* s, size_t pos = 0) const;
// 在pos位置上插入字符c/字符串str,并返回该字符的位置
void insert(size_t pos, char c);
void insert(size_t pos, const char* str);
// 删除pos位置上的元素,并返回该元素的下一个位置
void erase(size_t pos, size_t len);
private:
char* _str = nullptr;
size_t _capacity = 0;
size_t _size = 0;
};
const int string::npos = -1;
1.1、构造函数的两种方式
写法1:分离写法
//写法1:分离写法
string()
//:_str(nullptr)
//注意,传空串的处理,需要开一个字节的空间
:_str(new char[1])
, _size(0)
, _capacity(0)
{
_str[0] = '\0';//这样才好放入'\0'
}
string(const char* str)
//:_str(str)//error,str为const,不允许修改
:_size(strlen(str))//顺序的变换,防止随机值
, _str(new char[strlen(str) + 1])
, _capacity(strlen(str))
{}
//存在的问题:反复调用strlen使得效率低下。
//其次,当传空指针时,strlen不能接收空指针
//再三,初始化列表与成员变量的顺序一致,否则容易产生随机值。
//以上细节错误,需要多加注意。
写法2:合并写法,更推荐这样使用
//采用初始化列表分离,并只用一次strlen
//string(const char* str = '\0')//error,字符对于string1无法隐式类型转换
//string(const char* str = "\0")//可以,但不推荐,因为这种写法会有两个字符'\0'
//string(const char* str = "")//空串,这样就行了,一个'\0'
//string(const char* str = nullptr)//不能处理空串的情况
string(const char* str = "")
:_size(strlen(str))
{
_capacity = _size;
_str = new char[_capacity + 1];
strcpy(_str, str);
}
释放空间
//不管上述哪个方法对需要对应执行空间的释放
//析构函数
~string()
{
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}
1.2、运算符重载[]和函数重载
//返回size,写成const,保护内部数据不被修改,且外部普通对象可访问调用,const对象也可访问
size_t size() const
{
return _size;
}
//返回容量
size_t capacity() const
{
return _capacity;
}
//运算符重载[]
//利用引用返回,返回的是别名,所以就实现了可读可写的操作
//另外,此时注意,对于const对象就无法调用下标+[]了
//char& operator[](size_t pos)
//添加const,权限可以缩小,但不能放大
//但是参考库里的写法,可以发现,提供的两个函数构成的函数重载。
// 即: 各走各的函数。
// 1.char& operator[] (size_t pos);
char& operator[](size_t pos)
{
//检查是否越界
assert(pos < _size);
return _str[pos];
}
// 2.const char& operator[] (size_t pos) const;
const char& operator[](size_t pos) const
{
//检查是否越界
assert(pos < _size);
return _str[pos];
}
//其根本原因:
//只管非const那么const对象就无法调用,只管const对象,
//但又导致非const对象可访问但不能修改了,所以提供了函数重载.
1.3、iterator迭代器遍历以及c_str()转回字符数组
//迭代器版本1:const和非const
typedef char* iterator;
typedef const char* const_iterator;
iterator begin()
{
return _str;
}
iterator end()
{
return _str + _size;
}
const_iterator begin() const
{
return _str;
}
const_iterator end() const
{
return _str + _size;
}
//string转回字符数组
const char* c_str()
{
return _str;
}
1.4、插入insert/追加append/删除erase/尾插push_back/扩容reserve
//扩容
void reserve(size_t n)
{
//判断这里,目的是兼容reserve的单独预先开辟空间的使用
if (n > _capacity)
{
//char* tmp = new char[n];
//值得注意的地方是,_capacity统计的是有效字符所用的空间大小,并不包括'\0'
//所以,在开辟空间是的细节需要,额外加一个单位的空间给'\0'。
char* tmp = new char[n + 1];
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
_capacity = n;
}
}
//尾插
void push_back(char ch)
{
//检查容量--扩容(通常2倍,这里就不用checkcapacity了,最好先写reserve())
if (_size == _capacity)
{
//reserve(2 * _capacity);//如果_capacity初始化为0,这样写就存在问题
reserve(_capacity == 0 ? 4 : 2 * _capacity);//灵活控制
}
_str[_size] = ch;
_size++;
//注意把'\0'放在最后
_str[_size] = '\0';
}
//追加
void append(const char* str)
{
//检查容量--扩容(2倍不一定够了,最好的方式根据长度计算更好)
size_t len = strlen(str);
if (_size + len > _capacity)
{
reserve(_size + len);//灵活控制
}
//strcat//追加导'\0'到末尾,可以但效率不高
strcpy(_str + _size, str);
_size += len;
}
//运算符重载+=,同理函数重载
string& operator+=(char ch)
{
push_back(ch);
return *this;
}
string& operator+=(const char* str)
{
append(str);
return *this;
}
//插入,同理函数重载
//插入操作涉及到一些检查操作
void insert(size_t pos, char ch)
{
assert(pos <= _size);//pos==size是尾插,所以可加=
//扩容
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : 2 * _capacity);
}
//真正的问题是pos == 0的处理
//解决1:强转pos == end == -1结束 ==> 强转size_t的pos 1 --> -1
//int end = _size;
while (end >= (int)pos)
//while (end >= (int)pos)
//{
// _str[end + 1] = _str[end];
// --end;
//}
//解决2:巧妙的利用end=0就结束了,就不用解决强转问题
size_t end = _size + 1;
while (end > pos)
{
_str[end] = _str[end - 1];
--end;
}
_str[pos] = ch;
++_size;
}
void insert(size_t pos, const char* str)
{
assert(pos <= _size);//pos==size是尾插,所以可加=
size_t len = strlen(str);
if (_size + len > _capacity)
{
reserve(_size + len);//灵活控制
}
size_t end = _size + len;
while (end > pos)
{
_str[end] = _str[end - len];
--end;
}
while (*str != '\0')
{
_str[pos++] = *str++;
++_size;
}
}
//删除,同理函数重载
void erase(size_t pos, size_t len = npos)
{
//断言检查
assert(pos < _size);//删除,不能取到'\0'的位置所以这里不能取=
//判断长度是否超出size,超出说明是当前pos位置之后都进行删除
//if (len == npos || pos + len >= _size)
//pos+len有溢出的风险
if (len == npos || len >= _size - pos)
{
_str[pos] = '\0';
_size = pos;
}
else
{
strcpy(_str + pos, _str + pos + len);
_size -= len;
}
}
1.5、swap/resize/find/substr/拷贝2构造,深浅拷贝
//resize改变的是有效元素个数,当调整之后的size大于容量时,容量会发生变化。
void resize(size_t n, char ch = '\0')//默认插入'\0'
{
if (n <= _size)
{
_str[n] = '\0';//截断即可
_size = n;
}
else
{
reserve(n);
for (size_t i = _size; i < n; i++)
{
_str[i] = ch;
}
_str[n] = '\0';
_size = n;
}
}
//运算符重载= s1 = s3
string& operator=(const string& s)
{
char* tmp = new char[s._capacity + 1];
strcpy(tmp, s._str);
delete[] _str;
_str = tmp;
_size = s._size;
_capacity = s._capacity;
return *this;
}
//swap同理,函数重载
void swap(string& s)
{
//直接写成员函数交换,注意此处为了避免,编译器在此类域或命名空间找浪费时间或编译出错
//直接使用域操作符,指定到std里找
//其次复习:编译器默认查找匹配机制
//先局部,再全局,即先现成再挪用
std::swap(_str, s._str);
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
//find,同理函数重载
size_t find(char ch, size_t pos = 0) const //缺省参数只能从右向左赋初始值
{
assert(pos < _size);
for (size_t i = pos; i < _size; i++)
{
if (_str[i] == ch)
return i;
}
return npos;
}
size_t find(const char* str, size_t pos = 0) const//缺省参数只能从右向左赋初始值
{
assert(pos < _size);
//1.kmp 2.bm算法
//3.strstr
//const char * strstr ( const char * str1, const char * str2 );
//char* strstr(char* str1, const char* str2);
const char* ptr = strstr(_str, str);
if (ptr)
{
//指针减指针,下标位置
return ptr - _str;
}
else
{
return npos;
}
}
//substr返回子串
string substr(size_t pos = 0, size_t len = npos)
{
string sub;
//当前位置之后都取出
//if (len == npos || len >= _size - pos)
if (len >= _size - pos)
{
for (size_t i = pos; i < _size; i++)
{
sub += _str[i];
}
}
else
{
for (size_t i = pos; i < pos + len; i++)
{
sub += _str[i];
}
}
return sub;
}
1.6、clear、运算符重载>,<,>=,<=,==,!=,+,-
//清空数据
void clear()
{
_size = 0;
_str[_size] = '\0';
}
//全局:运算符重载==
bool operator==(const string& s1, const string& s2)
{
int ret = strcmp(s1.c_str(), s2.c_str());
return ret == 0;
}
//同理全局:运算符重载<
bool operator<(const string& s1, const string& s2)
{
int ret = strcmp(s1.c_str(), s2.c_str());
return ret < 0;
}
//同理全局:运算符重载<=
bool operator<=(const string& s1, const string& s2)
{
int ret = strcmp(s1.c_str(), s2.c_str());
return ret <= 0;
//复用
//return s1 < s2 || s1 == s2;
}
//同理全局:运算符重载>
bool operator>(const string& s1, const string& s2)
{
int ret = strcmp(s1.c_str(), s2.c_str());
return ret > 0;
//复用
//return !(s1 <= s2);
}
//同理全局:运算符重载>=
bool operator>=(const string& s1, const string& s2)
{
int ret = strcmp(s1.c_str(), s2.c_str());
return ret >= 0;
//复用
//return !(s1 < s2);
}
//同理全局:运算符重载!=
bool operator!=(const string& s1, const string& s2)
{
int ret = strcmp(s1.c_str(), s2.c_str());
return !(ret == 0);
//复用
//return !(s1 == s2);
}
1.7、流插入,流提取,getline
//全局,流插入、流提取
ostream& operator<<(ostream& out, const string& s)
{
for (auto ch : s)
{
out << ch;
}
return out;
}
istream& operator>>(istream& in, string& s)
{
清空数据
//s.clear();
//char ch;
in >> ch;
//ch = in.get();//获取单个字符
while (ch != ' ' && ch != '\n')
//while (ch != '\n')//等价getline,提取一行直到遇见'\n'
//{
// s += ch;
// //in >> ch;
// ch = in.get();
//}
此时发现返回值,会存在数据上的冲突,所以还需要在最前面清空空间的数据
//return in;
//优化:解决频繁的开辟空间
s.clear();
char ch = in.get();
char buf[128];
size_t index = 0;
while (ch != ' ' && ch != '\n')
{
buf[index++] = ch;
if (index == 127)//为了防止频繁扩容
{
buf[127] = '\0';
s += buf;//控制一次性加127个字符
//简单的说,利用缓冲区的原理
index = 0;
}
ch = in.get();
}
if (index != 0)//处理尾巴置'\0',不满127个字符
{
buf[index] = '\0';
s += buf;
}
return in;
}
//getline
istream& getline(istream& in, string& s)
{
//清空数据
s.clear();
char ch;
ch = in.get();//获取单个字符
while (ch != '\n')
{
s += ch;
ch = in.get();
}
return in;
}
1.8、运算符重载优化:现代写法
//运算符重载= s1 = s3 -- 传统写法
string& operator=(const string& s)
{
char* tmp = new char[s._capacity + 1];
strcpy(tmp, s._str);
delete[] _str;
_str = tmp;
_size = s._size;
_capacity = s._capacity;
return *this;
}
//优化:现代写法
string& operator=(string& s)
{
//这里传值传参也是一种拷贝,s充当的就是tmp
//使用传值拷贝,相当于一次拷贝构造函数的调用。
swap(s);//直接交换
return *this;//返回左值,支持连续赋值
}
//拷贝构造s1(s2); -- 传统写法
string(const string& s)
{
_str = new char[s._capacity + 1];//重新new一个大小相同的空间,并拷贝数据
strcpy(_str, s._str);
_size = s._size;
_capacity = s._capacity;
}
//优化:现代写法,但注意s参数通常要默认置空
string(const string& s)
{
//思想是,利用tmp这个中间变量,去构造一个一样的对象,然后与s2交换指针。
//其次,如果一些内置成员的构造中未初始化,且编译器也未处理,就可能涉及随机值的访问,所以通常要给一个初始值(缺省值)
string tmp(s._str);
swap(tmp);
}
//运算符重载= s1 = s3 -- 传统写法
string& operator=(const string& s)
{
char* tmp = new char[s._capacity + 1];//深度拷贝,防止指向同一块空间
strcpy(tmp, s._str);
delete[] _str;
_str = tmp;
_size = s._size;
_capacity = s._capacity;
return *this;
}
//现代写法
string& operator=(const string& s)
{
string ss(s);
swap(ss);
return *this;
}
//继续优化.本质是一种复用,减少资源的损耗和提升效率
string& operator=(string ss)
{
swap(ss);
return *this;
}
2、string类的实现完整代码
//模拟实现string的各个接口
class string
{
public:
//迭代器版本1:const和非const
typedef char* iterator;
typedef const char* const_iterator;
iterator begin()
{
return _str;
}
iterator end()
{
return _str + _size;
}
const_iterator begin() const
{
return _str;
}
const_iterator end() const
{
return _str + _size;
}
//构造函数
string(const char* str = "")
:_size(strlen(str))
{
_capacity = _size;
_str = new char[_capacity + 1];
strcpy(_str, str);
}
//析构函数,释放空间
~string()
{
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}
//拷贝构造s1(s2)
//优化:现代写法
string(const string& s)
{
//思想是,利用tmp这个中间变量,去构造一个一样的对象,然后与s2交换指针。
//其次,如果一些内置成员的构造中未初始化,且编译器也未处理,就可能涉及随机值的访问,所以通常要给一个初始值(缺省值)
string tmp(s._str);
swap(tmp);
}
//返回size,写成const,保护内部数据不被修改,且外部普通对象可访问调用,const对象也可访问
size_t size() const
{
return _size;
}
//返回容量
size_t capacity() const
{
return _capacity;
}
//string转回字符数组
const char* c_str() const
{
return _str;
}
//运算符重载[]
// 1.char& operator[] (size_t pos);
char& operator[](size_t pos)
{
//检查是否越界
assert(pos < _size);
return _str[pos];
}
// 2.const char& operator[] (size_t pos) const;
const char& operator[](size_t pos) const
{
//检查是否越界
assert(pos < _size);
return _str[pos];
}
//扩容
void reserve(size_t n)
{
//判断这里,目的是兼容reserve的单独预先开辟空间的使用
if (n > _capacity)
{
//细节额外加一个单位的空间给'\0'。
char* tmp = new char[n + 1];
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
_capacity = n;
}
}
//尾插
void push_back(char ch)
{
//检查容量--扩容
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : 2 * _capacity);//灵活控制
}
_str[_size] = ch;
_size++;
//注意把'\0'放在最后
_str[_size] = '\0';
//复用insert
//insert(_size, ch);
}
//追加
void append(const char* str)
{
//检查容量--扩容
size_t len = strlen(str);
if (_size + len > _capacity)
{
reserve(_size + len);//灵活控制
}
strcpy(_str + _size, str);
_size += len;
//复用insert
//insert(_size, str);
}
//运算符重载+=,同理函数重载
string& operator+=(char ch)
{
push_back(ch);
return *this;
}
string& operator+=(const char* str)
{
append(str);
return *this;
}
//插入,同理函数重载
//插入操作涉及到一些检查操作
void insert(size_t pos, char ch)
{
assert(pos <= _size);//pos==size是尾插,所以可加=
//扩容
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : 2 * _capacity);
}
size_t end = _size + 1;
while (end > pos)
{
_str[end] = _str[end - 1];
--end;
}
_str[pos] = ch;
++_size;
}
void insert(size_t pos, const char* str)
{
assert(pos <= _size);//pos==size是尾插,所以可加=
size_t len = strlen(str);//注意此时不能用sizeof,不计入'\0'
//扩容
size_t end = _size + len;
if (end > _capacity)
{
reserve(end);//灵活控制
}
//注意,pos + len -1需要减1,因为此时pos算的是下标,否则会少插入一位
while (end > pos + len - 1)
{
_str[end] = _str[end - len];
--end;
}
//方法2:调用库函数,但会连同'\0'一同拷贝,所以应征了不能用sizeof,并且注意_size最后+len
strncpy(_str + pos, str, len);
_size += len;
}
//删除,同理函数重载
void erase(size_t pos, size_t len = npos)
{
//断言检查
assert(pos < _size);//删除,不能取到'\0'的位置所以这里不能取=
//判断长度是否超出size,超出说明是当前pos位置之后都进行删除
if (len == npos || len >= _size - pos)
{
_str[pos] = '\0';
_size = pos;
}
else
{
strcpy(_str + pos, _str + pos + len);
_size -= len;
}
}
//resize改变的是有效元素个数,当调整之后的size大于容量时,容量会发生变化。
void resize(size_t n, char ch = '\0')//默认插入'\0'
{
if (n <= _size)
{
_str[n] = '\0';//截断即可
_size = n;
}
else
{
reserve(n);
for (size_t i = _size; i < n; i++)
{
_str[i] = ch;
}
_str[n] = '\0';
_size = n;
}
}
//运算符重载=
//现代写法
string& operator=(string ss)
{
swap(ss);
return *this;
}
//swap同理,函数重载
void swap(string& s)
{
std::swap(_str, s._str);
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
//find,同理函数重载
size_t find(char ch, size_t pos = 0) const //缺省参数只能从右向左赋初始值
{
assert(pos < _size);
for (size_t i = pos; i < _size; i++)
{
if (_str[i] == ch)
return i;
}
return npos;
}
size_t find(const char* str, size_t pos = 0) const//缺省参数只能从右向左赋初始值
{
assert(pos < _size);
//const char * strstr ( const char * str1, const char * str2 );
const char* ptr = strstr(_str, str);
if (ptr)
{
//指针减指针,下标位置
return ptr - _str;
}
else
{
return npos;
}
}
//substr返回子串
string substr(size_t pos = 0, size_t len = npos)
{
string sub;
//当前位置之后都取出
if (len >= _size - pos)
{
for (size_t i = pos; i < _size; i++)
{
sub += _str[i];
}
}
else
{
for (size_t i = pos; i < pos + len; i++)
{
sub += _str[i];
}
}
return sub;
}
//清空数据
void clear()
{
_size = 0;
_str[_size] = '\0';
}
private:
char* _str = nullptr;
size_t _size = 0;
size_t _capacity = 0;
public:
//静态成员变量,属于每个类属于所有对象
static const int npos;
};
//静态成员变量类外定义
const int string::npos = -1;
//全局swap
void swap(string& x, string& y)
{
x.swap(y);
}
//全局:运算符重载==
bool operator==(const string& s1, const string& s2)
{
int ret = strcmp(s1.c_str(), s2.c_str());
return ret == 0;
}
//同理全局:运算符重载<
bool operator<(const string& s1, const string& s2)
{
int ret = strcmp(s1.c_str(), s2.c_str());
return ret < 0;
}
//同理全局:运算符重载<=
bool operator<=(const string& s1, const string& s2)
{
int ret = strcmp(s1.c_str(), s2.c_str());
return ret <= 0;
//复用
//return s1 < s2 || s1 == s2;
}
//同理全局:运算符重载>
bool operator>(const string& s1, const string& s2)
{
int ret = strcmp(s1.c_str(), s2.c_str());
return ret > 0;
//复用
//return !(s1 <= s2);
}
//同理全局:运算符重载>=
bool operator>=(const string& s1, const string& s2)
{
int ret = strcmp(s1.c_str(), s2.c_str());
return ret >= 0;
//复用
//return !(s1 < s2);
}
//同理全局:运算符重载!=
bool operator!=(const string& s1, const string& s2)
{
int ret = strcmp(s1.c_str(), s2.c_str());
return !(ret == 0);
//复用
//return !(s1 == s2);
}
//全局,流插入、流提取
ostream& operator<<(ostream& out, const string& s)
{
for (auto ch : s)
{
out << ch;
}
return out;
}
istream& operator>>(istream& in, string& s)
{
//优化:解决频繁的开辟空间
s.clear();
char ch = in.get();
char buf[128];
size_t index = 0;
while (ch != ' ' && ch != '\n')
{
buf[index++] = ch;
if (index == 127)//为了防止频繁扩容
{
buf[127] = '\0';
s += buf;//控制一次性加127个字符
//简单的说,利用缓冲区的原理
index = 0;
}
ch = in.get();
}
if (index != 0)//处理尾巴置'\0',不满127个字符
{
buf[index] = '\0';
s += buf;
}
return in;
}
//getline
istream& getline(istream& in, string& s)
{
//清空数据
s.clear();
char ch;
ch = in.get();//获取单个字符
while (ch != '\n')
{
s += ch;
ch = in.get();
}
return in;
}
3、扩展知识:引用计数和写时拷贝
int main()
{
std::string s1("111111111111111");
std::string s2(s1);
//不走深拷贝,采用引用计数和写时拷贝方法
//1.析构多次
//2.一个修改会影响另一个
//3.计数
//有了引用计数以后,析构时,先--引用计数;
//如果计数被减完后,引用计数 == 0 代表当前是最后一个管理资源的对象,那么就释放。
//这样就不同开辟其他相同的空间,巧妙的调整指向使用同一块空间。
//其次,检查引用计数,如果等于1,说明资源是自己独占的,不用拷贝;
//如果大于1,先深拷贝再写入,这就叫写时拷贝。
//最后反正都要拷贝,搞这么复杂的意义在哪里?
//答:不是每个对象都需要修改,当拷贝后不发生修改时就成功节省了资源。
return 0;
}
引用计数和写时拷贝小结:
在C++中,引用计数(Reference Counting)和写时拷贝(Copy-on-Write,简称COW)是两种常用于实现共享和延迟复制的技术。
它们经常一起使用,以优化资源的使用和提高性能。
引用计数:
引用计数是一种内存管理技术,用于跟踪一个对象当前有多少个引用指向它。
每当一个新的引用指向一个对象时,引用计数增加;每当一个引用不再指向该对象时(例如,引用被销毁或重新赋值),引用计数减少。
当引用计数减少到0时,对象可以被安全地释放。
计数的一个主要优点是
它可以自动管理对象的生命周期,而无需显式的删除操作。
然而,它也有一些缺点:
比如循环引用问题(两个或多个对象相互引用,导致它们的引用计数永远不会降到0)和线程安全问题(需要额外的同步机制来确保引用计数的正确更新)。
写时拷贝:
写时拷贝是一种优化策略,用于延迟对象的复制操作。
其基本思想是:
在多个引用共享同一个对象时,并不立即创建对象的副本,而是等到某个引用尝试修改对象时才进行复制。
这样可以节省不必要的复制开销,特别是当对象很大或复制成本很高时。
写时拷贝通常与引用计数一起使用:
当一个对象被多个引用共享时,它们实际上共享的是同一个对象的引用计数和内存地址。
当某个引用尝试修改对象时,系统会检查引用计数。
如果引用计数大于1,说明有其他引用也在使用这个对象,此时系统会创建一个新的对象副本,并更新相关引用的指向,以确保修改不会影响到其他引用。
如果引用计数为1,说明没有其他引用共享这个对象,那么可以直接在原对象上进行修改。
结合使用
引用计数和写时拷贝的结合使用可以带来显著的性能提升。
通过引用计数,我们可以高效地管理对象的生命周期和共享状态;通过写时拷贝,我们可以延迟不必要的复制操作,提高程序的响应速度和资源利用率。