目录
一:四个默认成员函数
1.private 成员
private:
char* _str;
size_t _size;
size_t _capacity;
const static size_t npos;
- _str是存储字符串的指针,_size用来存储当前字符串的结尾位置,_capacity用于存储字符串的容量大小
- npos的值被定义为-1,又因为npos的类型为size_t,是无符号整型,所以npos实际上的值是size_t类型的最大值4294967295
- npos做参数时,通常表示字符串的结尾,因为我们认为一个字符串的长度不会超过npos,用做返回值时通常用于查找,表示未找到要查找的值
- npos的初始化需要在类外,因为npos是静态成员
2.构造函数
string(const char* str = "")
{
//防止传nullptr
if (str == nullptr) str = "";
_size = strlen(str);
_capacity = _size;
_str = new char[_capacity + 1];
strcpy(_str, str);
}
- 构造函数简单的来说就是传一个字符串用来初始化string,不过写缺省参数时不能写nullptr
- 因为strlen计算字符串长度时不能传nullptr,所以缺省参数通常写为“”,表示空串
- 还需要注意的是,_capacity是指存储有效字符的长度,不包括结尾的
'\0'
,所以给_str分配空间时要比_capacity多一字节
3.析构函数
~string()
{
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}
- delete 针对nullptr什么也不做,所以可以不用判断_str是否为空
4.拷贝构造
<1> 传统方式
string(const string& str)//拷贝构造,必须传引用,否则会无限递归
:_str(new char[strlen(str._str)+1])
{
strcpy(_str, str._str);
}
- 拷贝构造和构造函数很相似,不过是用一个string对象的_str来构造一个新的string对象的_str,就是一个拷贝的过程,调用库函数即可
- 需要注意的是:参数必须传引用
- 如果不传引用,会造成无限递归
<2> 现代方式
void swap(string& str)
{
::swap(_str, str._str);
::swap(_size, str._size);
::swap(_capacity, str._capacity);
}
string(const string& str)
:_str(nullptr)
, _size(0)
, _capacity(0)
{
string tmp(str._str);
swap(tmp);
}
- 现在我们定义拷贝构造函数时可以不用strcpy了,我们先初始化一个空string对象,然后用构造函数构造一个和需要拷贝构造的string数值相同的string,再把它们交换,就完成了拷贝构造,并且也是深拷贝
- 出了拷贝构造函数,我们临时构造的对象tmp会自动调用析构函数析构它
- 这里注意我们需要对_str进行初始化,否则_str为随机值,交换后出了拷贝构造调用析构函数进行delete时会报错,因为此时_str的空间不是动态分配得来的
- 这里的swap函数可以不自己写,调用库里的,但是库里的效率没有我们自己重新写的效率高,因为我们仅仅是交换,而库里的swap会新建变量进行三次的深拷贝
5.赋值运算符重载(operator=)
<1> 传统方式
string& operator=(const string& str)
{
if (this!=&str)//防止s1=s1
{
delete[] _str;
_str = new char[strlen(str._str) + 1];
strcpy(_str, str._str);
}
return *this;
}
- 和拷贝构造不同的是,operator=需要delete释放原空间再申请和str一样大的空间再进行拷贝(因为要保证它们的空间一样大,小了能扩容,大了的话只能重新申请空间,不如直接重新申请空间)
- 为了防止自己给自己赋值,加上if判断,当this!=&str时再进行赋值
<2> 现代方式
string& operator=(string str)//s1=s1时_str的地址会变
{
swap(str);
return *this;
}
- 和拷贝构造一样,operator=也有现在方式,使代码更为简洁
- 这里的参数是关键,并没有传引用,所以传参时就进行了拷贝构造,之后直接进行swap就完成了赋值
- 缺点时是无法防止自己给自己赋值
6.迭代器与operator[]
const char& operator[](int pos) const
//专门重载用于const成员函数,防止修改_str的值
{
assert(pos < _size);
return _str[pos];
}
char& operator[](int pos)
{
assert(pos < _size);
return _str[pos];
}
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;
}
- 编写operator[]时别忘记加上assert防止越界访问
- string类的迭代器其实就是char和const char指针的封装
二:reserve与resize
1.reserve函数
void reserve(size_t n)
{
if (n > _capacity)
{
char* tmp = new char[n + 1];
strncpy(tmp, _str, _size + 1);
//不能用strcpy,有可能有多的\0没有被拷贝过来,拷贝_size+1个,\0也要拷贝
delete[] _str;
_str = tmp;
_capacity = n;
}
}
- n<_capacity时不扩容
- 不能使用strcpy进行拷贝,因为string类中间可能含有`\0``,string类的结束标志是pos==_size
- 所以我们使用strncpy进行拷贝,直接拷贝_size+1个字符,
\0
也要拷贝
2.resize函数
void resize(size_t n, char val = '\0')//别忘了缺省参数
{
if (n < _size)//分成n<_size和n>=size两种情况
{
_size = n;
_str[_size] = '\0';
}
else
{
if (n > _capacity)
{
reserve(n);
}
while (_size < n)
{
_str[_size++] = val;
}
_str[_size] = '\0';
}
}
- resize可以分为两种情况
- (1) n<_size (2)n>=_size
- 第一种情况比较简单,直接将第n个位置的字符改为
\0
,再修改_size的值即可 - 第二种情况就要从_size一直尾插val字符,并在最后以
\0
结尾
三:插入删除和查找
1.push_back
void push_back(char c)
{
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
_str[_size++] = c;
_str[_size] = '\0';
}
- 每次新增字符前需要检查空间是否足够,不够需要扩容
- 如果是空串,则分配4个字节的空间,否则增容2倍
2.append
void append(const char* str)
{
int len = _size + strlen(str);
if (len > _capacity)
{
reserve(len);
}
strcpy(_str + _size, str);
_size = len;
}
- 和push_back一样,需要检查是否需要扩容
3.operator+=
string& operator+=(char c)
{
push_back(c);
return *this;
}
string& operator+=(const char* str)
{
append(str);
return *this;
}
- 需要重载两个版本的operator+=,分别对应尾插一个字符和尾插一个字符串
- 复用push_back和append即可
4.insert
//pos位置之前插入
string& insert(size_t pos, char ch)
{
assert(pos <= _size);//只能在size及之前位置插入
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : _capacity * 2);//capacity可能为0
}
char* end = _str + _size;
while (end >= _str + pos)
{
*(end + 1) = *end;
end--;
}
_str[pos] = ch;
_size++;
return *this;
}
string& insert(size_t pos, const char* str)//不加const传常量字符串会有问题
{
assert(pos <= _size);
size_t len = _size+strlen(str);
if ( len > _capacity)
{
reserve(len);
}
char* end = _str + _size;
while (end >= _str + pos)
{
*(end + len) = *end;
end--;
}
_size += len;
strncpy(_str + pos, str, len);
return *this;
}
- 注意点:(1)插入的位置不能超过_size
- (2)别忘了扩容
- (3)如果不使用指针进行字符的移动的话,需要从_size+1进行赋值
- 如果从_size开始,令_str[end+1]=_str[end]的话,循环条件写成while(end>=pos)
- pos为0时,end最后会变为-1,但end的类型是size_t,会是一个很大的正数,就会导致无限循环
string& insert(size_t pos, char ch)
{
assert(pos <= _size);//只能在size及之前位置插入
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : _capacity * 2);//capacity可能为0
}
size_t end = _size+1;//将end类型改为int也不行,会发生隐式类型转换
while (end > pos)
{
_str[end] = _str[end-1];
end--;
}
_str[pos] = ch;
_size++;
return *this;
}
5.erase
string& erase(size_t pos = 0, size_t len = npos)//不给参数默认全删
{
assert(pos < _size);//_size位置是\0,不能删
//1.要删的字符数量大于剩余字符
//2.要删的字符数量小于剩余字符
size_t leftLen = _size - pos;
if (len >= leftLen)//pos及以后全删
{
_str[pos] = '\0';
_size = pos;
}
else
{
strcpy(_str + pos, _str + pos + len);
_size -= len;
}
return *this;
}
- 注意点:(1)pos必须小于_size,_size位置是
\0
,也不能删除 - (2)需要计算删除的长度和pos后剩余长度的大小,如果要删除的长度大于剩余长度,直接将pos后全部删除,否则只删除len长度的字符,可以通过strcpy来完成字符的移动
6.find
size_t find(char ch, size_t pos = 0)
{
assert(pos < _size);
for (int i = pos; i < _size; i++)
{
if (_str[i] == ch)
{
return i;
}
}
return npos;
}
size_t find(const char* str, size_t pos = 0)
{
assert(pos < _size);
const char* ret = strstr(_str + pos, str);
if (ret)
{
return ret - _str;
}
return npos;
}
- find函数也需重载,分为查找一个字符和查找一个字符串
- 查找字符时遍历查找即可,查找字符串时可以通过调用库函数strstr实现
- 需要注意的是,find返回的是下标,查找字符串时strstr返回的是指针,需要通过ret-_str计算出下标再返回
四:operator<<与operator>>
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;
ch = in.get();
while (ch != ' ' && ch != '\n')
{
s += ch;
ch = in.get();
}
return in;
}
- 这里的operator<<和operator>>不用写成友元函数,因为并没有直接修改string的内部成员
- 而是通过string的接口间接修改,比如operator>>就是先清除原字符串,再不断进行+=操作达成输入的目的
- 输出则是通过范围for()
五:比较大小的运算符重载
bool operator<(string& s1, string& s2)
{
return strcmp(s1.c_str(), s2.c_str()) < 0;
}
bool operator==(string& s1, string& s2)
{
return strcmp(s1.c_str(), s2.c_str()) == 0;
}
bool operator<=(string& s1, string& s2)
{
return s1 < s2 || s1 == s2;
}
bool operator>(string& s1, string& s2)
{
return !(s1 <= s2);
}
bool operator>=(string& s1, string& s2)
{
return !(s1 < s2);
}
bool operator!=(string& s1, string& s2)
{
return !(s1 == s2);
}
- 在对string类进行排序时需要比较string类的大小,所以我们需要对>,<,==等运算符进行重载
- 在stl库中这些重载没有写成成员函数,所以编写时也可以写在类外面
- 可以通过调用strcmp库函数快速实现
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。