前言
通过实现常用的string类的函数接口进一步了解string类,能够使我们对string类理解更深。从而更好的运用string。
成员变量
private:
char* _str;
int _size;
int _capacity;
const static size_t npos = -1;
四大天选之子
默认构造函数
string(const char* str = "")
:_size(strlen(str))
{
_capacity = _size == 0 ? 4 : _size;
//需要多开一个空间放'\0'
_str = new char[_capacity + 1];
strcpy(_str, str);
}
拷贝构造函数
需要new一块空间拷贝,发生值拷贝则会出现编译器会调用两次析构函数报错
string(const string&str)
:_size(str._size)
, _capacity(str._capacity)
{
_str = new char[_capacity + 1];
strcpy(_str, str._str);
}
析构函数
~string()
{
delete[]_str;
_str = nullptr;
_size = _capacity = 0;
}
赋值重载
这个上面坑很多
1.首先需要考虑被赋值的空间是比要赋值的空间大?相等?还是小?
2.如果比较小,要扩容的话,不能先将原来的空间释放掉,再来new空间,万一new失败抛异常了呢,那原来的空间被释放掉了就有问题了。
3.如果是str1=str1呢?如果不用创建一个变量来记住_str,像我下面的代码开完空间后要进行拷贝的时候,拷贝下来的全部是随机值。没有出现拷贝下来是随机值,那么自己赋值自己,还要再开辟一块空间,使得效率变得很低。
string& operator=(const string& str)
{
if (*this != str)
{
_size = str._size;
_capacity = str._capacity;
char* tmp = new char[_capacity + 1];
delete[]_str;
_str = tmp;
strcpy(_str, str._str);
}
return *this;
}
运算符重载
[ ]重载 因为可读性的原因,使得访问string的元素用得最多是操作符[ ]一般对于获取值的函数常常加const,防止出现权限放大的问题。
const char& operator[](int pos) const
{
assert(pos < _size);
return _str[pos];
}
大于、小于、等于之类的运算符重载。只要实现一两个,其他相似的复用即可
bool operator==(const string& str) const
{
return !(*this != str);
}
bool operator>(const string& str) const
{
return strcmp(_str, str._str) > 0;
}
bool operator >=(const string& str) const
{
return *this > str || *this == str;
}
bool operator<(const string& str) const
{
return !(*this >= str);
}
bool operator<=(const string& str) const
{
return !(*this>str);
}
获取大小
获取size值
int size()const
{
return _size;
}
获取capacity值
int capacity()const
{
return _capacity;
}
获取字符串地址
const char* c_str()const
{
return _str;
}
扩容
reserve
void reserve(size_t n)
{
char* tmp = new char[n + 1];
strcpy(tmp, _str);
delete[]_str;
_str = tmp;
_capacity = n;
}
resize在实现的时候有比较多的点需要注意的,需要分三种情况来处理
1.要改变的大小在小于size时,相当于删除元素。
2.大于size小于capacity时,相当于时初始化>=size且<=capacity部分。
3.大于capacity时,相当于扩容,且初始化>=size且<=capacity部分
void resize(size_t n, char ch = '\0')
{
if (_size<n)
{
if (_size<n)
{
if (n>_capacity)
reserve(n);
for (size_t i = _size; i <= n+1; i++)
{
_str[i] = ch;
}
_size = n;
_str[_size] = '\0';
}
}
else
{
if (_size>n)
{
_str[n] = '\0';
_size = n;
}
}
}
修改string
** insert函数**
有分两种,一种插入字符,另一种插入字符串
string& insert(size_t pos, char ch)
{
assert(pos <= _size);
if (_size + 1 > _capacity)
{
reserve(_capacity * 2);
}
int end = _size;
int begin = pos;
while (begin <= end)
{
_str[end + 1] = _str[end];
end--;
}
_str[pos] = ch;
_size++;
return *this;
}
string& insert(size_t pos, const char* str)
{
assert(pos <= _size);
size_t len = strlen(str);
//挪动数据
if (_size + len > _capacity)
{
reserve(_size + len);
}
size_t end = _size + len+1;
int size = _size;
while (end>pos+len)
{
_str[end-1] = _str[size];
end--;
size--;
}
//拷贝
memcpy(_str+pos, str, len);
_size += len;
return *this;
}
删除数据Erase
要分两种情况,一种是从pos起删除的数据<_size, 另一种则是>=_size
string& Erase(size_t pos, size_t num = npos)
{
assert(pos < _size);
if (num == npos||pos+num>_size)
{
_str[pos] = '\0';
_size = pos;
}
else
{
strcpy(_str+pos, _str+pos+num);
_size-=num;
}
return *this;
}
push_back ---- 尾插字符
void push_back(char ch)
{
if (_size + 1 > _capacity)
{
reserve(_capacity * 2);
}
_str[_size] = ch;
_size++;
_str[_size] = '\0';
}
append ---- 追加字符串
void append(const char* str)
{
int len = strlen(str);
if (_size + len > _capacity)
{
reserve(_size + len);
}
strcpy(_str + _size, str);
_size += len;
}
运算符重载+= ---- 追加字符或者字符串
平时更加推荐用这个使可读性变高。
string& operator+=(char ch)
{
push_back(ch);
return *this;
}
string& operator+=(const char* str)
{
append(str);
return *this;
}
迭代器
这边采用的是SGI的版本,与VS使用的版本不同,采用的是原生指针作为迭代器
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类的实现并不是很难,但是有许多细节需要注意,但是自己实现一遍,对于string的理解比仅仅调用其成员函数来完成稍微滴更上一层了。