string库
string类各函数接口总览
namespace cl
{//模拟实现string类class string{public:typedef char* iterator;typedef const char* const_iterator;//默认成员函数string(const char* str = ""); //构造函数string(const string& s); //拷贝构造函数string& operator=(const string& s); //赋值运算符重载函数~string(); //析构函数//迭代器相关函数iterator begin();iterator end();const_iterator begin()const;const_iterator end()const;//容量和大小相关函数size_t size();size_t capacity();void reserve(size_t n);void resize(size_t n, char ch = '\0');bool empty()const;//修改字符串相关函数void push_back(char ch);void append(const char* str);string& operator+=(char ch);string& operator+=(const char* str);string& insert(size_t pos, char ch);string& insert(size_t pos, const char* str);string& erase(size_t pos, size_t len);void clear();void swap(string& s);const char* c_str()const;//访问字符串相关函数char& operator[](size_t i);const char& operator[](size_t i)const;size_t find(char ch, size_t pos = 0)const;size_t find(const char* str, size_t pos = 0)const;size_t rfind(char ch, size_t pos = npos)const;size_t rfind(const char* str, size_t pos = 0)const;//关系运算符重载函数bool operator>(const string& s)const;bool operator>=(const string& s)const;bool operator<(const string& s)const;bool operator<=(const string& s)const;bool operator==(const string& s)const;bool operator!=(const string& s)const;private:char* _str; //存储字符串size_t _size; //记录字符串当前的有效长度size_t _capacity; //记录字符串当前的容量static const size_t npos; //静态成员变量(整型最大值)};const size_t string::npos = -1;//<<和>>运算符重载函数istream& operator>>(istream& in, string& s);ostream& operator<<(ostream& out, const string& s);istream& getline(istream& in, string& s);
}
默认成员函数
构造函数,用于初始化string对象
参数str默认为空字符串"",这样可以在不传入参数时创建一个空的string对象。
如果传入了有效的C风格字符串,则根据该字符串来初始化string对象。
string(const char* str = "") {// 如果str为空指针,创建一个空字符串。// 此时_size设为0表示字符串长度为0,_capacity也设为0表示当前容量为0,// 分配一个大小为1的字符数组用于存储结束符'\0',并将其赋值给_str。if (str == nullptr) {_size = 0;_capacity = 0;_str = new char[1];_str[0] = '\0';}// 如果str不为空,计算其长度并赋值给_size,_capacity初始化为与_size相同。// 分配一个大小为_strlen + 1的字符数组(包含结束符'\0'),并将str的内容复制到该数组中。else {_size = strlen(str);_capacity = _size;_str = new char[_capacity + 1];strcpy(_str, str);}}
拷贝构造函数
- 用于从另一个string对象创建新的string对象
- 采用深拷贝,目的是确保新对象和原对象相互独立,修改新对象不会影响原对象,反之亦然。
- 传统写法:先为新对象分配与原对象容量相同加1(包含结束符'\0')的内存空间,再将原对象的字符串内容复制到新分配的内存中,最后更新新对象的_size和_capacity。
string(const string& s): _str(new char[s._capacity + 1]), _size(0), _capacity(0) {strcpy(_str, s._str);_size = s._size;_capacity = s._capacity;}
赋值运算符重载函数
用于将一个string对象赋值给另一个string对象
// 传统写法:先检查是否为自赋值(即当前对象和要赋值的对象是否为同一个对象),// 如果不是自赋值,先释放原对象的内存,然后分配与赋值对象容量相同加1的内存空间,// 再将赋值对象的字符串内容复制到新分配的内存中,最后更新当前对象的_size和_capacity。string& operator=(const string& s) {if (this != &s) {delete[] _str;_str = new char[s._capacity + 1];strcpy(_str, s._str);_size = s._size;_capacity = s._capacity;}return *this;}// 现代写法:利用值传递的特性,让编译器调用拷贝构造函数创建临时对象// 然后通过交换函数将临时对象和当前对象的内容交换,实现赋值操作。// 这样写的好处是代码更简洁,并且避免了传统写法中复杂的内存管理操作。string& operator=(string s) {swap(s);return *this;}
析构函数
- 用于释放string对象占用的内存
- 释放_str指向的堆区内存,防止内存泄漏,然后将_str置为nullptr,
- 同时将_size和_capacity置为0,以表示对象已被销毁。
~string() {delete[] _str;_str = nullptr;_size = 0;_capacity = 0;}
迭代器相关函数
- 返回指向字符串起始和结束位置的迭代器
- begin函数返回指向字符串首字符的迭代器,这样可以方便地从字符串开头开始遍历。
iterator begin() {return _str;}// 用于const对象的begin函数,返回指向字符串首字符的const迭代器// 这样可以保证在不修改字符串的情况下对其进行遍历操作。const_iterator begin() const {return _str;}// end函数返回指向字符串末尾字符的下一个位置的迭代器,// 用于标识字符串的结束,配合begin函数实现完整的遍历。iterator end() {return _str + _size;}// 用于const对象的end函数,返回指向字符串末尾字符的下一个位置的const迭代器// 同样是为了保证在不修改字符串的情况下进行遍历。const_iterator end() const {return _str + _size;}
容量和大小相关函数
size()
返回字符串的有效字符个数(不包括'\0'),通过访问_size成员变量实现。
size_t size() const {return _size;}
capacity()
返回字符串当前已分配的内存容量(不包括'\0'的空间),通过访问_capacity成员变量实现。
size_t capacity() const {return _capacity;}
reserve()
- 调整字符串的容量为至少n个字符的空间
- 如果n大于当前容量,则重新分配内存并复制原字符串内容,以满足新的容量需求。
- 如果n小于等于当前容量,则不进行任何操作,因为当前容量已经足够。
void reserve(size_t n) {if (n > _capacity) {char* new_str = new char[n + 1];strcpy(new_str, _str);delete[] _str;_str = new_str;_capacity = n;}}
resize()
- 调整字符串的大小为n个字符
- 如果n小于当前大小,则截断字符串,将_size设置为n,并在新的末尾位置添加结束符'\0'。
- 如果n大于当前大小,且n大于当前容量,则先调整容量,再用指定字符ch填充新增的空间。
- 如果n大于当前大小,但n小于等于当前容量,则直接用指定字符ch填充新增的空间。
void resize(size_t n, char ch = '\0') {if (n < _size) {_size = n;_str[_size] = '\0';}else {if (n > _capacity) {reserve(n);}for (size_t i = _size; i < n; ++i) {_str[i] = ch;}_size = n;_str[_size] = '\0';}}
empty()
判断字符串是否为空,即字符串的有效字符个数是否为0,通过比较_size是否为0来实现。
bool empty() const {return _size == 0;}
修改字符串相关函数
push_back
第一种
// 在字符串末尾添加一个字符ch// 首先检查当前容量是否足够,如果当前_size等于_capacity,说明容量已满,// 此时进行扩容操作,扩容策略为:若当前容量为0则初始化为4,否则扩容为原来的2倍。// 然后将字符ch添加到字符串末尾,并更新_size和添加结束符'\0'。void push_back(char ch) {if (_size == _capacity) {reserve(_capacity == 0? 4 : _capacity * 2);}_str[_size] = ch;_str[_size + 1] = '\0';++_size;}
第二种
// 复用insert函数在字符串末尾插入字符,效果与push_back相同// 这样做的好处是可以减少代码重复,提高代码的复用性。void push_back(char ch) {insert(_size, ch);}
append
- 在字符串末尾添加一个C风格字符串str
- 先计算添加后的总长度,如果总长度超过当前容量,则进行扩容,
- 然后将str复制到字符串末尾,并更新_size,以反映新的字符串长度。
void append(const char* str) {size_t new_size = _size + strlen(str);if (new_size > _capacity) {reserve(new_size);}strcpy(_str + _size, str);_size = new_size;}
复用insert函数在字符串末尾插入字符串,效果与append相同
同样是为了提高代码的复用性,减少重复代码。
void append(const char* str) {insert(_size, str);}
重载+=运算符
重载+=运算符,用于在字符串末尾添加一个字符ch
调用push_back函数实现字符的添加,并返回当前对象的引用,
这样可以支持连续的+=操作,例如s += 'a' += 'b';。
string& operator+=(char ch) {push_back(ch);return *this;}
// 重载+=运算符,用于在字符串末尾添加一个C风格字符串str// 调用append函数实现字符串的添加,并返回当前对象的引用,// 同样支持连续的+=操作,如s += "abc" += "def";。string& operator+=(const char* str) {append(str);return *this;}
insert
- 在字符串的指定位置pos插入一个字符ch
- 先检查位置pos的合法性,如果pos大于_size则抛出异常,因为位置超出了字符串的范围。
- 再检查当前容量是否足够,不够则进行扩容,扩容策略与push_back中相同。
- 然后将pos及之后的字符向后移动一位,为插入字符ch腾出位置,插入字符ch,并更新_size。
string& insert(size_t pos, char ch) {if (pos > _size) {throw std::out_of_range("Invalid position");}if (_size == _capacity) {reserve(_capacity == 0? 4 : _capacity * 2);}for (size_t i = _size; i > pos; --i) {_str[i] = _str[i - 1];}_str[pos] = ch;++_size;return *this;}
在字符串的指定位置pos插入一个C风格字符串str
先检查位置pos的合法性,如果pos大于_size则抛出异常,因为位置超出了字符串的范围。
计算插入后的总长度,若超过当前容量则进行扩容,
将pos及之后的字符向后移动str的长度位,为插入字符串str腾出位置,插入字符串str,并更新_size。
string& insert(size_t pos, const char* str) {if (pos > _size) {throw std::out_of_range("Invalid position");}size_t len = strlen(str);if (_size + len > _capacity) {reserve(_size + len);}for (size_t i = _size; i >= pos; --i) {_str[i + len] = _str[i];}strncpy(_str + pos, str, len);_size += len;return *this;}
erase
删除从位置pos开始的len个字符
先检查位置pos的合法性,如果pos大于等于_size则抛出异常,因为位置超出了字符串的范围。
如果len大于等于剩余字符数,则直接截断字符串,将_size设置为pos,并在新的末尾位置添加结束符'\0'。
否则将pos + len之后的字符复制到pos位置,并更新_size,以反映删除后的字符串长度。
string& erase(size_t pos, size_t len) {if (pos >= _size) {throw std::out_of_range("Invalid position");}if (len >= _size - pos) {_size = pos;_str[_size] = '\0';}else {strcpy(_str + pos, _str + pos + len);_size -= len;}return *this;}
clear
// 清空字符串,将_size设置为0,并在字符串开头添加结束符'\0',// 这样就相当于清空了字符串的内容。void clear() {_size = 0;_str[0] = '\0';}
swap
交换两个string对象的内容
使用标准库的swap函数交换两个对象的_str、_size和_capacity成员,
这样可以高效地实现两个对象内容的交换,并且不会涉及复杂的内存分配和复制操作。
void swap(string& s) {std::swap(_str, s._str);std::swap(_size, s._size);std::swap(_capacity, s._capacity);}
c_str
// 返回一个指向字符串的C风格字符串(以'\0'结尾)// 用于与需要C风格字符串的函数进行交互,例如一些C标准库函数。const char* c_str() const {return _str;}
访问字符串相关函数
operator[]
重载[]运算符,用于通过下标访问字符串中的字符
非const版本,返回字符的引用,允许对字符进行修改,
先检查下标i的合法性,如果i大于等于_size则抛出异常,因为下标超出了字符串的范围,
否则返回_str[i]的引用。
char& operator[](size_t i) {if (i >= _size) {throw std::out_of_range("Invalid index");}return _str[i];}
const版本operator[]
重载[]运算符,用于通过下标访问字符串中的字符
const版本,返回字符的const引用,不允许对字符进行修改,
先检查下标i的合法性,如果i大于等于_size则抛出异常,因为下标超出了字符串的范围,
否则返回_str[i]的const引用。
const char& operator[](size_t i) const {if (i >= _size) {throw std::out_of_range("Invalid index");}return _str[i];}
find
从位置pos开始查找字符ch在字符串中第一次出现的位置
如果找到则返回该位置的下标,否则返回npos(定义为-1),表示未找到。
先检查位置pos的合法性,如果pos大于等于_size则抛出异常,因为位置超出了字符串的范围,
然后从pos位置开始遍历字符串,找到字符ch则返回其位置,遍历结束未找到则返回npos。
size_t find(char ch, size_t pos = 0) const {if (pos >= _size) {throw std::out_of_range("Invalid position");}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 {if (pos >= _size) {throw std::out_of_range("Invalid position");}const char* result = strstr(_str + pos, str);if (result) {return result - _str;}return npos;}
rfind
从位置pos开始反向查找字符ch在字符串中最后一次出现的位置
如果找到则返回该位置的下标,否则返回npos(定义为-1),表示未找到。
先检查位置pos的合法性,如果pos大于等于_size则将pos设为_size - 1,
然后将字符串逆置,调用正向查找函数,再将结果转换为原字符串的位置。
size_t rfind(char ch, size_t pos = npos) const {if (pos >= _size) {pos = _size - 1;}string tmp(*this);std::reverse(tmp.begin(), tmp.end());size_t rev_pos = _size - 1 - pos;size_t ret = tmp.find(ch, rev_pos);if (ret != npos) {return _size - 1 - ret;}return npos;}
关系运算符重载函数
重载输入运算符>>
解释:重载的输入运算符>>从输入流in中读取字符,直到遇到空格或换行符,
将读取的字符逐个添加到字符串s中,实现字符串的输入功能。
std::istream& operator>>(std::istream& in, string& s) {s.clear();char ch;while (in.get(ch) && ch != ' ' && ch != '\n') {s.push_back(ch);}return in;}
重载输出运算符<<
解释:重载的输出运算符<<将字符串s的C风格字符串(通过c_str函数获取)输出到输出流out中,实现字符串的输出功能。
std::ostream& operator<<(std::ostream& out, const string& s) {out << s.c_str();return out;}
getline
getline函数用于读取一行含有空格的字符串。实现时于>>运算符的重载基本相同,只是当读取到’\n’的时候才停止读取字符。
//读取一行含有空格的字符串
istream& getline(istream& in, string& s)
{s.clear(); //清空字符串char ch = in.get(); //读取一个字符while (ch != '\n') //当读取到的字符不是'\n'的时候继续读取{s += ch; //将读取到的字符尾插到字符串后面ch = in.get(); //继续读取字符}return in;
}
注:为了防止与标准库当中的string类产生命名冲突,模拟实现时需放在自己的命名空间当中。
测试代码
#include <iostream>
#include <cstring>
#include <stdexcept>// 上面的 my_ns::string 类定义代码int main() {my_ns::string s1("Hello");my_ns::string s2(" World");// 测试 += 运算符s1 += s2;std::cout << "s1 after += s2: " << s1 << std::endl;// 测试 insert 函数s1.insert(5, ",");std::cout << "s1 after insert: " << s1 << std::endl;// 测试 erase 函数s1.erase(5, 1);std::cout << "s1 after erase: " << s1 << std::endl;// 测试 find 函数size_t pos = s1.find("World");if (pos != std::string::npos) {std::cout << "Found 'World' at position: " << pos << std::endl;} else {std::cout << "Did not find 'World'" << std::endl;}return 0;
}