7.8 string类insert函数、append函数、push_back函数、+=重载
insert函数
insert的模拟实现主要实现字符和字符串插入两种
字符插入
string& insert(size_t pos, char ch) { assert(pos <= _size); // 满了就扩容 if (_size == _capacity) { reserve(_capacity == 0 ? 4 : _capacity * 2); } size_t end = _size + 1; while (end > pos) { _str[end] = _str[end - 1]; --end; } _str[pos] = ch; ++_size; return *this; }
insert 函数在字符串中指定位置插入一个字符。这里简单解释一下这个函数的实现:
string& insert(size_t pos, char ch)
:这是 std::string 类中的 insert 函数的声明,表示该函数将在指定位置 pos 插入字符 ch。pos 是传入的参数,表示插入位置的索引;ch 是要插入的字符。
assert(pos <= _size);
:使用 assert 断言来确保插入位置 pos 不超过字符串的实际大小 _size。如果断言失败(pos 大于 _size),则会触发断言失败错误,帮助调试找到错误的位置。
if (_size == _capacity)
:检查当前字符串是否已满(即 _size 等于 _capacity)。如果字符串已满,则需要扩容,以确保有足够的容量来插入新字符。这里使用 reserve 函数扩容,使字符串有足够的容量来容纳新字符。
size_t end = _size + 1;
:在插入字符前,先将字符串的末尾位置(实际字符个数 _size 后面)向后移动一个位置,为新字符留出空间。这样做是为了将插入位置 pos 之后的字符后移。
while (end > pos)
:通过一个循环,将插入位置 pos 之后的字符依次向后移动一个位置。
_str[pos] = ch;
:将字符 ch 插入到指定的插入位置 pos。
++_size;
:插入字符后,将字符串的实际大小 _size 增加 1。
return *this;
:返回当前 std::string 对象的引用,以支持链式调用。
字符串插入
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; while (end >= pos + len) { _str[end] = _str[end - len]; --end; } strncpy(_str + pos, str, len); _size += len; return *this; }
与上一个 insert 函数相比,这里的参数 str 是一个 C-style 字符串(const char*),而不是一个单个字符。函数的功能是在字符串中指定位置插入一个 C-style 字符串。现在来解释这个函数的实现:
string& insert(size_t pos, const char* str)
:这是 std::string 类中的 insert 函数的声明,表示该函数将在指定位置 pos 插入一个 C-style 字符串 str。pos 是传入的参数,表示插入位置的索引;str 是要插入的 C-style 字符串。
assert(pos <= _size);
:使用 assert 断言来确保插入位置 pos 不超过字符串的实际大小 _size。如果断言失败(pos 大于 _size),则会触发断言失败错误,帮助调试找到错误的位置。
size_t len = strlen(str);
:计算要插入的 C-style 字符串 str 的长度,即字符个数。
if (_size + len > _capacity)
:检查插入后的字符串大小是否超过当前的容量 _capacity,如果超过,则需要扩容,以确保有足够的容量来容纳插入的字符串。
reserve(_size + len);
:调用 reserve 函数来扩容,保证有足够的容量来容纳插入的字符串。
size_t end = _size + len;
:在插入字符串前,先将字符串的末尾位置(实际字符个数 _size 后面)向后移动 len 个位置,为新字符串留出空间。
while (end >= pos + len)
:通过一个循环,将插入位置 pos 之后的字符依次向后移动 len 个位置,为新字符串的插入留出空间。
strncpy(_str + pos, str, len);
:使用 strncpy 函数将 C-style 字符串 str 复制到指定的插入位置 pos,并且只复制 len 个字符。
_size += len;
:插入字符串后,将字符串的实际大小 _size 增加 len,以反映插入后的新大小。
return *this;
:返回当前 std::string 对象的引用,以支持链式调用。
append函数
void append(const char* str) { size_t len = strlen(str); // 满了就扩容 if (_size + len > _capacity) { reserve(_size+len); } strcpy(_str + _size, str); //strcat(_str, str); 需要找\0,效率低 _size += len; }
append 函数用于在字符串末尾添加一个 C-style 字符串。现在来解释这个函数的实现:
void append(const char* str)
:这是 std::string 类中的 append 函数的声明,表示该函数将在字符串末尾添加一个 C-style 字符串 str。str 是传入的参数,表示要添加的 C-style 字符串。
size_t len = strlen(str);
:计算要添加的 C-style 字符串 str 的长度,即字符个数。
if (_size + len > _capacity)
:检查添加后的字符串大小是否超过当前的容量 _capacity,如果超过,则需要扩容,以确保有足够的容量来容纳添加的字符串。
reserve(_size + len);
:调用 reserve 函数来扩容,保证有足够的容量来容纳添加的字符串。
strcpy(_str + _size, str);
:使用 strcpy 函数将 C-style 字符串 str 复制到字符串末尾,即从 _str 的实际字符个数 _size 处开始复制。
_size += len;
:添加字符串后,将字符串的实际大小 _size 增加 len,以反映添加后的新大小。
这样,append 函数将 C-style 字符串 str 添加到字符串末尾,并且在必要时进行了内存扩容。
当然你可以对insert函数复用
void append(const char* str) { insert(_size, str); }
push_back函数
void push_back(char ch) { // 满了就扩容 if (_size == _capacity) { reserve(_capacity == 0 ? 4 : _capacity * 2); } _str[_size] = ch; ++_size; _str[_size] = '\0'; }
push_back 函数用于在字符串末尾添加一个字符。现在来解释这个函数的实现:
void push_back(char ch)
:这是 std::string 类中的 push_back 函数的声明,表示该函数将在字符串末尾添加一个字符 ch。ch 是传入的参数,表示要添加的字符。
if (_size == _capacity)
:检查当前字符串是否已满(即 _size 等于 _capacity)。如果字符串已满,则需要扩容,以确保有足够的容量来容纳新增的字符。这里使用 reserve 函数扩容,使字符串有足够的容量来容纳新字符。
_str[_size] = ch;
:将字符 ch 添加到字符串末尾,即在 _str 的实际字符个数 _size 处添加字符。
++_size;
:字符串的实际大小 _size 增加 1,以反映添加后的新大小。
_str[_size] = '\0';
:在字符串末尾添加一个空字符 ‘\0’,以保证新的字符串正确终止。
这样,push_back 函数将字符 ch 添加到字符串末尾,并在必要时进行了内存扩容。
同样的,push_back 函数你也可以对insert函数复用
void push_back(char ch) { insert(_size, ch); }
+=重载
string& operator+=(char ch) { push_back(ch); return *this; } string& operator+=(const char* str) { append(str); return *this; }
operator+= 运算符重载用于在现有字符串后追加字符或 C-style 字符串。现在来解释这个函数的实现:
string& operator+=(char ch)
:这是 operator+= 运算符重载的第一个版本,表示该运算符将在字符串末尾追加一个字符 ch。在这个版本中,直接调用了 push_back 函数,将字符 ch 添加到字符串末尾。
string& operator+=(const char* str)
:这是 operator+= 运算符重载的第二个版本,表示该运算符将在字符串末尾追加一个 C-style 字符串 str。在这个版本中,直接调用了 append 函数,将 C-style 字符串 str 添加到字符串末尾。
在两个版本的实现中,都返回当前 std::string 对象的引用,以支持链式调用。
7.9 string类erase函数
void erase(size_t pos, size_t len = npos) { assert(pos < _size); if (len == npos || pos + len >= _size) { _str[pos] = '\0'; _size = pos; } else { strcpy(_str + pos, _str + pos + len); _size -= len; } }
erase 函数用于从字符串中删除指定位置开始的一定长度的字符。现在来解释这个函数的实现:
void erase(size_t pos, size_t len = npos)
:这是 std::string 类中的 erase 函数的声明,表示该函数将从指定位置 pos 开始删除一定长度 len 的字符。pos 是传入的参数,表示删除的起始位置的索引;len 是要删除的字符个数,默认值为 npos,表示删除从起始位置开始的所有字符。
assert(pos < _size);
:使用 assert 断言来确保删除的起始位置 pos 不超过字符串的实际大小 _size。如果断言失败(pos 大于等于 _size),则会触发断言失败错误,帮助调试找到错误的位置。
if (len == npos || pos + len >= _size)
:检查是否要删除从起始位置 pos 开始的所有字符(即 len 等于 npos),或者是否要删除的字符个数超过字符串末尾(即 pos + len 大于等于 _size)。如果是其中一种情况,表示要删除从 pos 开始的所有字符或从 pos 开始直到末尾的所有字符。
_str[pos] = '\0'; 和 _size = pos;
:在上述情况下,将字符串从位置 pos 处截断,即将字符数组的第 pos 个字符设置为空字符 ‘\0’,并更新字符串的实际大小 _size 为 pos,以反映删除后的新大小。
else
:如果要删除的字符个数小于字符串末尾的字符个数,则需要将后面的字符向前移动。
strcpy(_str + pos, _str + pos + len);
:将从位置 pos + len 开始的字符复制到位置 pos,覆盖掉要删除的字符。
_size -= len;
:删除字符后,将字符串的实际大小 _size 减去 len,以反映删除后的新大小。
7.10 string类erase函数
size_t find(char ch, size_t pos = 0) const { assert(pos < _size); for (size_t i = pos; i < _size; ++i) { if (ch == _str[i]) { return i; } } return npos; }
find 函数用于在字符串中查找指定字符或子串,并返回其位置。现在来解释这个函数的实现:
size_t find(char ch, size_t pos = 0) const
:这是 std::string 类中的 find 函数的第一个版本,表示该函数将在字符串中从位置 pos 开始查找字符 ch。pos 是传入的参数,表示查找的起始位置的索引,默认值为 0,表示从字符串的开头开始查找。
assert(pos < _size);
:使用 assert 断言来确保查找的起始位置 pos 不超过字符串的实际大小 _size。如果断言失败(pos 大于等于 _size),则会触发断言失败错误,帮助调试找到错误的位置。在这个版本中,使用了简单的循环遍历,从位置 pos 开始遍历字符串,查找是否存在字符 ch。如果找到了,就返回该字符的位置索引;如果未找到,则返回 npos。
size_t find(const char* sub, size_t pos = 0) const { assert(sub); assert(pos < _size); const char* ptr = strstr(_str + pos, sub); if (ptr == nullptr) { return npos; } else { return ptr - _str; } }
size_t find(const char* sub, size_t pos = 0) const
:这是 std::string 类中的 find 函数的第二个版本,表示该函数将在字符串中从位置 pos 开始查找子串 sub。sub 是传入的参数,表示要查找的子串;pos 是传入的参数,表示查找的起始位置的索引,默认值为 0,表示从字符串的开头开始查找。
assert(sub);
:使用 assert 断言来确保传入的子串 sub 不为空指针。如果断言失败(sub 为空指针),则会触发断言失败错误,帮助调试找到错误的位置。
assert(pos < _size);
:同样,使用 assert 断言来确保查找的起始位置 pos 不超过字符串的实际大小 _size。在这个版本中,使用 strstr 函数在字符串中查找子串 sub,如果找到了,就返回子串的位置索引;如果未找到,则返回 npos。
7.11 string类substr 函数
string substr(size_t pos, size_t len = npos) const { assert(pos < _size); size_t realLen = len; if (len == npos || pos + len > _size) { realLen = _size - pos; } string sub; for (size_t i = 0; i < realLen; ++i) { sub += _str[pos + i]; } return sub; }
substr 函数用于从字符串中提取子串,从指定位置 pos 开始,并且可选地指定子串的长度 len。现在来解释这个函数的实现:
string substr(size_t pos, size_t len = npos) const
:这是 std::string 类中的 substr 函数的声明,表示该函数将从指定位置 pos 开始提取子串,并且可选地指定子串的长度 len。pos 是传入的参数,表示提取子串的起始位置的索引;len 是传入的参数,表示要提取的子串的长度,默认值为 npos,表示提取从起始位置 pos 开始的所有字符。
assert(pos < _size);
:使用 assert 断言来确保提取子串的起始位置 pos 不超过字符串的实际大小 _size。如果断言失败(pos 大于等于 _size),则会触发断言失败错误,帮助调试找到错误的位置。
size_t realLen = len;
:定义一个变量 realLen,用于存储实际要提取的子串的长度。初始值为传入的参数 len。
if (len == npos || pos + len > _size)
:检查是否要提取从起始位置 pos 开始的所有字符(即 len 等于 npos),或者是否要提取的字符个数超过字符串末尾(即 pos + len 大于等于 _size)。如果是其中一种情况,表示要提取从 pos 开始的所有字符或从 pos 开始直到末尾的所有字符。此时,将 realLen 更新为从 pos 开始到末尾的字符个数。创建一个名为 sub 的新的 std::string 对象,用于存储提取的子串。使用循环从位置 pos 开始,逐个字符地将子串添加到 sub 中。返回提取的子串 sub。
7.12 string类比较运算符重载
bool operator>(const string& s) const { return strcmp(_str, s._str) > 0; }
这是大于运算符 > 的重载版本,表示该运算符用于比较当前字符串与另一个字符串 s 的大小关系。在这个版本中,使用 strcmp 函数比较两个字符串 _str 和 s._str 的字典序。如果 _str 大于 s._str,则返回 true,否则返回 false。
bool operator==(const string& s) const { return strcmp(_str, s._str) == 0; }
这是等于运算符 == 的重载版本,表示该运算符用于比较当前字符串与另一个字符串 s 是否相等。同样,使用 strcmp 函数比较两个字符串 _str 和 s._str 的内容是否相同。如果相同,返回 true,否则返回 false。
bool operator>=(const string& s) const { return *this > s || *this == s; }
这是大于等于运算符 >= 的重载版本,表示该运算符用于比较当前字符串是否大于或等于另一个字符串 s。在这个版本中,直接使用已经定义好的大于运算符 > 和等于运算符 == 进行组合,如果当前字符串大于 s 或者与 s 相等,则返回 true,否则返回 false。
bool operator<=(const string& s) const { return !(*this > s); }
这是小于等于运算符 <= 的重载版本,表示该运算符用于比较当前字符串是否小于或等于另一个字符串 s。同样,直接使用已经定义好的大于等于运算符 >= 进行取反,如果当前字符串小于 s,则返回 true,否则返回 false。
bool operator<(const string& s) const { return !(*this >= s); }
这是小于运算符 < 的重载版本,表示该运算符用于比较当前字符串是否小于另一个字符串 s。同样,直接使用已经定义好的大于等于运算符 >= 进行取反,如果当前字符串不大于等于 s,则说明当前字符串小于 s,返回 true,否则返回 false。
bool operator!=(const string& s) const { return !(*this == s); }
这是不等于运算符 != 的重载版本,表示该运算符用于比较当前字符串是否不等于另一个字符串 s。同样,直接使用已经定义好的等于运算符 == 进行取反,如果当前字符串与 s 不相等,则返回 true,否则返回 false。
其实和之前类和对象的文章中讲到的日期类比较运算符重载一样,先实现> ==
或< ==
后面的都可以进行复用。
7.13 string类流插入<<和流提取>>重载
首先这里要注意的是,流插入和流提取在这里定义为全局函数,因此我们不要再类中定义,而是在类外,即全局定义。这样定义的运算符重载函数不属于类的成员,因此在其实现中不能直接访问类的私有成员,而需要通过类的公有接口进行访问。
运算符重载函数可以作为成员函数或全局非成员函数进行定义,具体取决于使用场景和设计需求。通常情况下,如果运算符的操作数为类对象本身或需要直接访问类的私有成员,可以考虑将其定义为成员函数。而如果运算符的操作数为类对象外的其他类型,或者运算符涉及的操作不仅限于类对象本身,可以考虑将其定义为全局非成员函数。
流插入<<
ostream& operator<<(ostream& out, const string& s) { for (size_t i = 0; i < s.size(); ++i) { out << s[i]; } return out; }
这是输出运算符 << 的重载版本,表示将 std::string 类对象 s 输出到输出流 out 中。
使用一个循环遍历 s 中的每个字符,并将每个字符依次输出到输出流 out 中。最后,将输出流 out 返回,以支持链式输出。
istream& operator>>(istream& in, string& s) { s.clear(); char ch; ch = in.get(); const size_t N = 32; char buff[N]; size_t i = 0; while (ch != ' ' && ch != '\n') { buff[i++] = ch; if (i == N - 1) { buff[i] = '\0'; s += buff; i = 0; } ch = in.get(); } buff[i] = '\0'; s += buff; return in; }
这是输入运算符 >> 的重载版本,表示将输入流 in 中的数据读取并存储到 std::string 类对象 s 中。
首先调用 s.clear() 函数,将 s 的内容清空,以便接收新的输入。然后,使用一个循环从输入流 in 中逐个读取字符 ch。如果字符 ch 不是空格或换行符,就将字符添加到一个临时字符数组 buff 中,并增加索引 i。一旦 buff 已满(i == N - 1),就将 buff 最后一个元素设为空字符 ‘\0’,然后将 buff 添加到 s 中,然后将索引 i 重置为 0,以继续接收后续字符。如果字符 ch 是空格或换行符,说明一个单词的输入结束,将 buff 最后一个元素设为空字符 ‘\0’,然后将 buff 添加到 s 中。最后,将输入流 in 返回,以支持链式输入。
8.string类的模拟实现(完整代码)
#include<iostream> #include<string.h> #include<assert.h> using namespace std; namespace mystring { class string { public: 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(const string& s) // :_str(new char[s._capacity+1]) // , _size(s._size) // , _capacity(s._capacity) //{ // strcpy(_str, s._str); //} // 现代写法 void swap(string& tmp) { ::swap(_str, tmp._str); ::swap(_size, tmp._size); ::swap(_capacity, tmp._capacity); } string(const string& s) :_str(nullptr) , _size(0) , _capacity(0) { string tmp(s._str); swap(tmp); } //string& operator=(const string& s) //{ // if (this != &s) // { // //string tmp(s._str); // string tmp(s); // swap(tmp); // this->swap(tmp); // } // return *this; //} string& operator=(string s) { swap(s); return *this; } ~string() { delete[] _str; _str = nullptr; _size = _capacity = 0; } const char* c_str() const { return _str; } size_t size() const { return _size; } size_t capacity() const { return _capacity; } const char& operator[](size_t pos) const { assert(pos < _size); return _str[pos]; } char& operator[](size_t pos) { assert(pos < _size); return _str[pos]; } void reserve(size_t n) { if (n > _capacity) { char* tmp = new char[n + 1]; strcpy(tmp, _str); delete[] _str; _str = tmp; _capacity = n; } } void resize(size_t n, char ch = '\0') { if (n > _size) { // 插入数据 reserve(n); for (size_t i = _size; i < n; ++i) { _str[i] = ch; } _str[n] = '\0'; _size = n; } else { // 删除数据 _str[n] = '\0'; _size = n; } } void push_back(char ch) { // 满了就扩容 if (_size == _capacity) { reserve(_capacity == 0 ? 4 : _capacity * 2); } _str[_size] = ch; ++_size; _str[_size] = '\0'; //insert(_size, ch); } void append(const char* str) { size_t len = strlen(str); // 满了就扩容 if (_size + len > _capacity) { reserve(_size+len); } strcpy(_str + _size, str); //strcat(_str, str); 需要找\0,效率低 _size += len; //insert(_size, str); } string& operator+=(char ch) { push_back(ch); return *this; } string& operator+=(const char* str) { append(str); return *this; } string& insert(size_t pos, char ch) { assert(pos <= _size); // 满了就扩容 if (_size == _capacity) { reserve(_capacity == 0 ? 4 : _capacity * 2); } size_t end = _size + 1; while (end > pos) { _str[end] = _str[end - 1]; --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; while (end >= pos + len) { _str[end] = _str[end - len]; --end; } strncpy(_str + pos, str, len); _size += len; return *this; } void erase(size_t pos, size_t len = npos) { assert(pos < _size); if (len == npos || pos + len >= _size) { _str[pos] = '\0'; _size = pos; } else { strcpy(_str + pos, _str + pos + len); _size -= len; } } void clear() { _str[0] = '\0'; _size = 0; } size_t find(char ch, size_t pos = 0) const { assert(pos < _size); for (size_t i = pos; i < _size; ++i) { if (ch == _str[i]) { return i; } } return npos; } size_t find(const char* sub, size_t pos = 0) const { assert(sub); assert(pos < _size); // kmp/bm const char* ptr = strstr(_str + pos, sub); if (ptr == nullptr) { return npos; } else { return ptr - _str; } } string substr(size_t pos, size_t len = npos) const { assert(pos < _size); size_t realLen = len; if (len == npos || pos + len > _size) { realLen = _size - pos; } string sub; for (size_t i = 0; i < realLen; ++i) { sub += _str[pos + i]; } return sub; } bool operator>(const string& s) const { return strcmp(_str, s._str) > 0; } bool operator==(const string& s) const { return strcmp(_str, s._str) == 0; } bool operator>=(const string& s) const { return *this > s || *this == s; } bool operator<=(const string& s) const { return !(*this > s); } bool operator<(const string& s) const { return !(*this >= s); } bool operator!=(const string& s) const { return !(*this == s); } private: size_t _capacity; size_t _size; char* _str; public: const static size_t npos = -1; }; ostream& operator<<(ostream& out, const string& s) { for (size_t i = 0; i < s.size(); ++i) { out << s[i]; } return out; } istream& operator>>(istream& in, string& s) { s.clear(); char ch; ch = in.get(); const size_t N = 32; char buff[N]; size_t i = 0; while (ch != ' ' && ch != '\n') { buff[i++] = ch; if (i == N - 1) { buff[i] = '\0'; s += buff; i = 0; } ch = in.get(); } buff[i] = '\0'; s += buff; return in; } }
结语
有兴趣的小伙伴可以关注作者,如果觉得内容不错,请给个一键三连吧,蟹蟹你哟!!!
制作不易,如有不正之处敬请指出
感谢大家的来访,UU们的观看是我坚持下去的动力
在时间的催化剂下,让我们彼此都成为更优秀的人吧!!!