C++ string自定义类的实现
目录
1. 头文件----string.h
1.1 头文件引入与命名空间
1.2 bit::string 类核心成员(public部分)
1. 迭代器类型声明
2. 迭代器获取函数
3. 构造、析构与拷贝控制
4. 基础属性与访问接口
5. 容量管理
6. 字符串修改操作
7. 字符串查找操作
8. 子串提取与清空
9. 字符串比较运算符重载
1.3 bit::string类私有成员(private部分)
1.4 bit::string 类静态常量(public部分)
1.5 全局流运算符与交换函数(命名空间bit内)
1.6 头文件整体设计总结
2. 实现文件---string.cpp
2.1 静态常量初始化与拷贝控制(核心基础)
1. 静态常量 npos 初始化
2. 构造函数(默认+带参)
3. 析构函数
4. 拷贝构造函数
5. 赋值运算符重载(operator=)
2.2 迭代器与基础访问接口
1. 迭代器接口(begin()/end())
2. 基础访问(c_str()/size()/operator[])
2.3 内存管理(reserve)
2.4 增删查改接口(核心功能)
2.5 比较运算符重载
2.6 全局流运算符与 swap
2.3 总结
在基本学习完了string类的内容之后 , 小编将用关于string类实现的一篇文章来进一步深入理解string类。小编仍然用string.h头文件 , string.cpp实现文件进行实现:
1. 头文件----string.h
#include<iostream>
#include<string.h>
#include<assert.h>using namespace std;namespace bit
{class string{public:typedef char* iterator;typedef const char* const_iterator;iterator begin();iterator end();const_iterator begin() const;const_iterator end() const;//string();string(const char* str = "");const char* c_str() const;~string();string(const string& s);//string& operator=(const string& s);string& operator=(string s);void swap(string& s);size_t size() const;char& operator[](size_t i);const char& operator[](size_t i) const;void reserve(size_t n);void push_back(char ch);void append(const char* str);string& operator+=(char ch);string& operator+=(const char* str);void pop_back();string& insert(size_t pos, char ch);string& insert(size_t pos, const char* str);string& erase(size_t pos = 0, size_t len = npos);size_t find(char ch, size_t pos = 0) const;size_t find(const char* str, size_t pos = 0) const;string substr(size_t pos, size_t len = npos) const;void clear();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 = nullptr;size_t _size = 0;size_t _capacity = 0;// _size < 16 串存在buff数组中// _size >= 16 串存在_str指向的数组中char _buff[16];public:static const size_t npos;};ostream& operator<<(ostream& out, const string& s);istream& operator>>(istream& in, string& s);istream& getline(istream& is, string& str, char delim = '\n');void swap(string& x, string& y);
}
上面是完整代码 , 我们进行详细分析:
该头文件实现了一个自定义string类 , 整体结构包含头文件引入 , 命名空间 , 类成员(变量/函数)声明, 友元函数声明:
1.1 头文件引入与命名空间
#include<iostream>
- 作用:引入标准输入输出流库。
- 关联:为后续声明 ostream& operator<<(输出) , istream& operator>> (输入)等流运算符重载提供基础 , 因为 ostream , istream 类定义在 <iostream> 中。
#include<string.h>
- 作用:引入C语言字符串处理库(如 strlen , strcpy , strcmp , strstr 等函数)。
- 关联:自定义 string 类的底层字符串操作(如构造时拷贝字符串 , 比较字符串 , 查找子串) , 需依赖这些C函数 , 例如通过 strlen 计算传入字符串长度 , 通过 strcpy 拷贝字符串内容。
#include<assert.h>
- 作用:引入断言库 , 提供 assert() 宏。
- 关联:用于成员函数的参数合法性检查 , 例如 operator[] 访问下标时检查 i <_size , insert / erase 时检查 pos <= _size , 若断言失败则直接终止程序 , 便于调试。
using namespace std;
- 作用:引入标准命名空间 std。
- 关联:使得代码中可直接使用 std 下的类(如 ostream , istream)和函数 , 无需写std::ostream , 简化流运算符重载的声明。
namespace bit { ... }
- 作用:定义自定义命名空间 bit。
- 关联:避免与标准库 std::string 或其他库的 string 类命名冲突 , 使用时需通过 bit::string 指定作用域。
1.2 bit::string 类核心成员(public部分)
1. 迭代器类型声明
typedef char* iterator;
- 作用:将 char* (字符指针)这个原生类型 , 重命名为 iterator (普通迭代器类型名)。
- 功能:用于遍历 , 修改 string 对象的字符(如 begin() 返回首字符地址 , end() 返回尾后字符地址) , 支持 ++ / -- /解引用( * )等指针操作。后续通过 iterator 定义的变量 , 本质就是 char* , 用于遍历和修改 string 对象中的非 const 字符(比如通过 begin() / end() 获取迭代器 , 修改字符串内容)。
- 与标准库关联:对应 std::string::iterator , 行为一致(如遍历字符串时从 begin() 到 end() )。
typedef const char* const_iterator;
- 作用:将 const char* (指向 const 字符的指针)重命名为 const_iterator (const 迭代器类型名)。
- 功能:用于遍历 const string 对象(或普通 string 的 const 场景) , 只能读取字符 , 不能修改(比如 const 对象调用 begin() const 获取的就是这种迭代器)。
- 与标准库关联:对应 std::string::const_iterator , 仅支持读操作 , 不支持写操作。
和标准库 std::string 的关系:
- 标准库 std::string 的迭代器底层也是类似逻辑(普通迭代器通常是 char* 封装 , const 迭代器是 const char* 封装) , 这里的命名(iterator / const_iterator)完全对齐标准库 , 目的是让自定义 string 的使用体验和标准库保持一致(比如都能通过 for (auto it = s.begin(); it != s.end(); ++it) 遍历)。
2. 迭代器获取函数
iterator begin();
- 返回值:iterator(char*) , 指向字符串首字符(_size<16 时指向 _buff[0] , _size>=16 时指向 _str[0] )。
- 功能 : 获取非const对象的起始迭代器 , 用于修改字符(如 for (auto it = s.begin(); it != s.end(); ++it) *it = 'a'; )。
- 标准库对应:std::string::begin() , 行为完全一致。
iterator end();
- 返回值: iterator(char*) , 指向字符串尾后字符(_size<16 时指向 _buff[_size] , _size>=16 时指向 _str[_size] )。
- 功能:标记非const对象的迭代器结束位置(不指向有效字符) , 作为遍历终止条件。
- 标准库对应:std::string::end() , 行为完全一致。
const_iterator begin() const;
- 返回值:const_iterator (const char*) , 指向const对象的首字符。
- 功能:获取const对象的起始迭代器 , 仅用于读字符(如 for (auto it = cs.begin(); it != cs.end(); ++it) cout << *it; , cs 是 const bit::string)。
- 重载逻辑:const 成员函数 , 仅作用于const对象 , 避免与非const版本冲突。
- 标准库对应:std::string::begin() const。
const_iterator end() const;
- 返回值:const_iterator(const char*) , 指向const对象的尾后字符。
- 功能:标记const对象的迭代器结束位置 , 仅用于读遍历的终止条件。
- 标准库对应:std::string::end() const。
3. 构造、析构与拷贝控制
//string(); (注释掉的默认构造)
- 作用:原默认构造函数被注释 , 因为下面的带参构造用了默认参数 , 可替代默认构造的功能(避免默认构造与带参构造重载冲突)。
string(const char* str = ""); (带默认参数的构造函数)
- 参数:const char* str , 传入的C风格字符串 , 默认值为空串 "" (覆盖默认构造的需求)。
- 功能:初始化 string 对象:
- 若 str 长度( strlen(str) )<16:将 str 拷贝到 _buff , _str 设为 nullptr , _size 设为 strlen(str) , _capacity 设为15( _buff 容量为16 , 留1个位置存 '\0');
- 若长度>=16 : 从堆申请内存(长度+1 , 存 '\0') , _str 指向堆内存 , 拷贝 str 到 _str , _size 设为 strlen(str) , _capacity 设为长度。
- 标准库对应:std::string(const char* s = "") , 行为一致(标准库也可能用SSO优化)。
const char* c_str() const;
- 返回值:const char* , 指向C风格字符串(以 '\0' 结尾)。
- 功能:提供与C语言兼容的接口 , 例如将 bit::string 传入需要 const char* 参数的函数(如 printf("%s", s.c_str()); )。
- 实现逻辑:_size<16 时返回 _buff , _size>=16 时返回 _str (两者均保证以 '\0' 结尾)。
- 标准库对应:std::string::c_str() , 功能完全一致。
~string(); (析构函数)
- 功能:释放对象占用的堆内存(避免内存泄漏)。
- 实现逻辑:仅当 _size >=16 时(_str 指向堆内存) , 才调用 delete[] _str;_size<16 时 _buff 是栈上数组 , 无需手动释放。
- 标准库对应:std::string::~string() , 同样负责释放堆内存(若有)。
string(const string& s); (拷贝构造函数)
- 参数:const string& s , 被拷贝的源对象(const保证不修改源对象)。
- 功能:深拷贝构造新对象(避免浅拷贝导致的内存重复释放/修改冲突)。
- 实现逻辑:
- 若 s._size <16 :直接拷贝 s._buff 到新对象的 _buff , _str 设为 nullptr;
- 若 s._size >=16 :新对象从堆申请与 s 相同容量的内存 , 拷贝 s._str 内容到新内存 , _str 指向新内存;
- 同时拷贝 s._size 和 s._capacity 到新对象。
- 标准库对应:std::string::string(const string& s) , 同样是深拷贝(C++11后可能有优化 , 但语义是深拷贝)。
//string& operator=(const string& s); (注释掉的赋值运算符重载)
- 作用:原传统赋值重载被注释 , 替换为下面的“值传递参数”版本 , 利用拷贝构造简化实现。
string& operator=(string s); (赋值运算符重载,值传递参数)
- 参数:string s , 源对象的拷贝(值传递 , 会调用拷贝构造生成临时对象 s)。
- 返回值:string& , 返回当前对象的引用(支持链式赋值 , 如 s1 = s2 = s3;)。
- 功能:深拷贝赋值(将 s 的内容赋值给当前对象)。
- 实现逻辑:通过 swap(s) 交换当前对象与临时对象 s 的成员(_str 、_size 、_capacity 、_buff ) , 临时对象 s 析构时会自动释放当前对象原来的堆内存(若有) , 简化代码且避免内存泄漏。
- 标准库对应:std::string& operator=(const string& s) , 语义一致(深拷贝) , 此版本是“拷贝并交换”(Copy-and-Swap) idiom 的实现 , 更简洁安全。
void swap(string& s); (成员 swap 函数)
- 参数:string& s , 要交换的另一个对象。
- 功能:交换两个 string 对象的所有成员( _str 、_size 、_capacity 、_buff )。
- 实现逻辑:直接交换成员变量(如 std::swap(this->_str, s._str) , std::swap(this->_size , s._size) 等) , 无堆内存申请/释放 , 效率高。
- 与非成员 swap 的关系:下面的非成员 swap 函数会调用此成员 swap (如 void swap(string& x, string& y) { x.swap(y); } ) , 符合标准库习惯(std::swap 也会优先调用成员 swap)。
4. 基础属性与访问接口
size_t size() const;
- 返回值:size_t(无符号整数) , 字符串的有效字符个数(不包含 '\0')。
- 功能:获取字符串长度(如 s.size() 得到 s 中字符的个数)。
- 标准库对应:std::string::size() , 功能完全一致(与 std::string::length() 等价 , 此自定义类未实现 length() , 但 size() 是标准命名)。
char& operator[](size_t i); (非const下标访问)
- 参数:size_t i , 要访问的字符下标(0-based)。
- 返回值:char& , 对应下标的字符引用(支持修改)。
- 功能:通过下标访问并修改字符(如 s[0] = 'A';)。
- 安全检查:内部会用 assert(i < _size) 检查下标合法性(越界则终止程序)。
- 标准库对应:std::string::operator[](size_t pos) , 行为一致(标准库 operator[] 默认不检查越界 , debug模式可能检查 , 此自定义版本用 assert 强制检查)。
const char& operator[](size_t i) const; (const下标访问)
- 参数:size_t i , 要访问的字符下标(0-based)。
- 返回值:const char& , 对应下标的字符const引用(只读 , 不可修改)。
- 功能:访问const对象的字符(如 const bit::string s = "abc"; cout << s[1]; )。
- 重载逻辑:const 成员函数 , 仅作用于const对象 , 避免与非const版本冲突 , 同样有 assert(i < _size) 检查。
- 标准库对应:std::string::operator[](size_t pos) const , 功能一致。
5. 容量管理
void reserve(size_t n);
- 参数:size_t n , 期望的最小容量(即能存储 n 个有效字符 , 不包含 '\0')。
- 功能:预分配容量(仅当 n > _capacity 时生效 , 避免频繁扩容)。
- 实现逻辑:
- 若 n <= _capacity :不做操作;
- 若 n <16 :无需申请堆内存(用 _buff 即可) , 仅更新 _capacity 为15;
- 若 n >=16 :从堆申请 n+1 字节内存(+1存 '\0') , 拷贝原字符串内容到新内存 , 释放原 _str (若有) , 更新 _str 指向新内存 , _capacity 设为 n。
- 标准库对应:std::string::reserve(size_t n) , 功能一致(标准库 reserve 不缩小容量 , 此版本也遵循)。
6. 字符串修改操作
void push_back(char ch);
- 参数:char ch , 要追加的单个字符。
- 功能:在字符串末尾追加一个字符(如 s.push_back('!'); 将 '!' 加到 s 末尾)。
- 实现逻辑:
- 1. 检查容量:若 _size +1 > _capacity , 调用 reserve(_size +1) 扩容;
- 2. 追加字符: _size<16 时写 _buff[_size] = ch , 否则写 _str[_size] = ch;
- 3. 更新 _size ( _size++ ) , 并在尾后位置写 '\0'(保证 c_str() 有效)。
- 标准库对应:std::string::push_back(char c) , 功能一致。
void append(const char* str);
- 参数:const char* str ,要追加的C风格字符串(需以 '\0' 结尾)。
- 功能:在字符串末尾追加一个C风格字符串(如 s.append("123"); 将 "123" 加到 s 末尾)。
- 实现逻辑:
- 1. 计算 str 长度 len = strlen(str) ;
- 2. 检查容量:若 _size + len > _capacity , 调用 reserve(_size + len) 扩容;
- 3. 拷贝 str 到字符串末尾( _size<16 时拷贝到 _buff[_size] , 否则拷贝到 _str[_size] );
- 4. 更新 _size (_size += len ) , 尾后写 '\0'。
- 标准库对应:std::string::append(const char* s) , 功能一致。
string& operator+=(char ch);
- 参数: char ch ,要追加的单个字符。
- 返回值: string& ,当前对象引用(支持链式操作,如 s += 'a' += 'b'; )。
- 功能:追加单个字符(语义同 push_back ,但支持运算符语法)。
- 实现逻辑:直接调用 push_back(ch) ,然后返回 *this 。
- 标准库对应: std::string& operator+=(char c) ,功能一致。
string& operator+=(const char* str);
- 参数: const char* str ,要追加的C风格字符串。
- 返回值: string& ,当前对象引用(支持链式操作,如 s += "ab" += "cd"; )。
- 功能:追加C风格字符串(语义同 append ,支持运算符语法)。
- 实现逻辑:直接调用 append(str) ,然后返回 *this 。
- 标准库对应: std::string& operator+=(const char* s) ,功能一致。
void pop_back();
- 功能:删除字符串末尾的最后一个有效字符(如 s = "abc"; s.pop_back(); 后 s 为 "ab")。
- 实现逻辑:
- 1. 先通过 assert(_size > 0) 检查字符串非空(空串删除会触发断言);
- 2. 直接将 _size-- (减少有效字符计数 , 无需真删除字符 , 后续操作会覆盖);
- 3. 在新的尾后位置(_size 下标处)写 '\0'(保证 c_str() 仍返回合法的C风格字符串)。
- 标准库对应:std::string::pop_back() , 功能一致(标准库也要求调用前字符串非空 , 否则行为未定义 , 此版本用 assert 强制检查)。
string& insert(size_t pos, char ch);
- 参数:
- size_t pos :插入位置(0-based,pos=0 表示插在开头 , pos=_size 表示插在末尾);
- char ch :要插入的单个字符。
- 返回值:string& , 当前对象引用(支持链式操作 , 如 s.insert(1, 'x').insert(3, 'y'); )。
- 功能:在指定位置插入单个字符(如 s = "abc"; s.insert(1, 'x'); 后 s 为 "axbc")。
- 实现逻辑:
- 1. 断言检查:assert(pos <= _size) (插入位置不能超过字符串长度);
- 2. 扩容:若 _size +1 > _capacity , 调用 reserve(_size +1);
- 3. 移动字符:从末尾( _size 下标)到 pos 下标 , 将字符依次后移1位(避免覆盖要插入的位置);
- 4. 插入字符:_size<16 时写 _buff[pos] = ch , 否则写 _str[pos] = ch;
- 5. 更新 _size ( _size++ ) , 尾后写 '\0'。
- 标准库对应:std::string::insert(size_t pos, char c) , 功能一致。
string& insert(size_t pos, const char* str);
- 参数:
- size_t pos :插入位置(0-based , 范围 [0, _size] );
- const char* str :要插入的C风格字符串(需以 '\0' 结尾)。
- 返回值:string& , 当前对象引用(支持链式操作)。
- 功能:在指定位置插入C风格字符串(如 s = "abc"; s.insert(1, "123"); 后 s 为 "a123bc")。
- 实现逻辑:
- 1. 断言检查:assert(pos <= _size) ;
- 2. 计算 str 长度 len = strlen(str) (若 str 为空串则不操作);
- 3. 扩容:若 _size + len > _capacity , 调用 reserve(_size + len) ;
- 4. 移动字符:从末尾到 pos 下标 , 将字符依次后移 len 位;
- 5. 拷贝字符串:将 str 内容拷贝到 pos 起始的位置( _buff 或 _str);
- 6. 更新 _size ( _size += len ) , 尾后写 '\0'。
- 标准库对应:std::string::insert(size_t pos, const char* s) , 功能一致。
string& erase(size_t pos = 0, size_t len = npos);
- 参数:
- size_t pos :删除起始位置(0-based) , 默认值 0 (从开头删);
- size_t len :要删除的字符个数 , 默认值 npos (删除从 pos 到末尾的所有字符)。
- 返回值:string& , 当前对象引用(支持链式操作)。
- 功能:删除指定范围的字符(如 s = "abcdef"; s.erase(2, 3); 后 s 为 "abf";s.erase(2); 后 s 为 "ab")。
- 实现逻辑:
- 1. 断言检查:assert(pos <= _size) (删除起始位置不能越界);
- 2. 调整删除长度:若 pos + len > _size 或 len == npos , 则 len = _size - pos (避免删超范围);
- 3. 移动字符:从 pos + len 下标开始 , 将字符依次前移 len 位(覆盖被删除的字符);
- 4. 更新 _size ( _size -= len ) , 尾后写 '\0';
- 5. (可选优化)若删除后 _size 大幅减小 , 可考虑缩容(此版本未实现 , 标准库也默认不缩容)。
- 与 npos 的关联:npos 是静态常量(值通常为 -1 , 因 size_t 是无符号类型 , 实际存储为最大值) , 用于表示“到末尾”的范围 , 与标准库 std::string::npos 语义完全一致。
- 标准库对应:std::string::erase(size_t pos = 0, size_t len = npos) , 功能一致。
7. 字符串查找操作
size_t find(char ch, size_t pos = 0) const;
- 参数:
- char ch :要查找的目标字符;
- size_t pos :查找起始位置(0-based) , 默认值 0 (从开头找)。
- 返回值:size_t , 找到则返回目标字符的下标 , 未找到则返回 npos。
- 功能:从指定位置开始查找单个字符(如 s = "abcabc"; s.find('b', 2); 返回 4)。
- 实现逻辑:
- 1. 遍历范围:从 pos 到 _size -1;
- 2. 匹配字符:若 _size<16 则遍历 _buff[i] , 否则遍历 _str[i] , 找到与 ch 相等的字符则返回 i;
- 3. 未找到:返回 npos。
- 标准库对应:std::string::find(char c, size_t pos = 0) const , 返回值语义完全一致。
size_t find(const char* str, size_t pos = 0) const;
- 参数:
- const char* str :要查找的目标C风格字符串(需以 '\0' 结尾);
- size_t pos :查找起始位置 , 默认值 0。
- 返回值:size_t , 找到则返回目标字符串的起始下标 , 未找到则返回 npos。
- 功能:从指定位置开始查找子串(如 s = "abcdef"; s.find("cd", 1); 返回 2)。
- 实现逻辑:
- 1. 边界检查:若 pos >= _size 或 str 为空串 , 返回 npos;
- 2. 调用C函数:获取当前字符串的C风格指针( c_str() ) , 调用 strstr(_cstr + pos, str) (strstr 是 <string.h> 中的子串查找函数);
- 3. 计算下标:若 strstr 返回非空指针 , 下标 = 返回指针 - c_str() ;若返回空指针 , 返回 npos。
- 标准库对应:std::string::find(const char* s, size_t pos = 0) const , 功能一致(标准库内部可能不用 strstr , 但语义相同)。
8. 子串提取与清空
string substr(size_t pos, size_t len = npos) const;
- 参数:
- size_t pos :子串起始位置(0-based);
- size_t len :子串长度 , 默认值 npos(子串从 pos 到末尾)。
- 返回值:string , 提取出的子串(新对象 , 深拷贝)。
- 功能:提取指定范围的子串(如 s = "abcdef"; s.substr(2, 3); 返回 "cde";s.substr(4); 返回 "ef")。
- 实现逻辑:
- 1. 断言检查:assert(pos <= _size);
- 2. 调整子串长度:若 pos + len > _size 或 len == npos , 则 len = _size - pos;
- 3. 创建新对象:构造一个新的 string 对象 , 将当前字符串 pos 开始 , 长度为 len 的字符拷贝到新对象中;
- 4. 返回新对象。
- 标准库对应:std::string::substr(size_t pos = 0, size_t len = npos) const , 功能完全一致。
void clear();
- 功能:清空字符串(仅保留空串 , 不释放容量)。
- 实现逻辑:
- 1. 将 _size 设为 0;
- 2. 在 _buff[0] (_size<16 )或 _str[0] ( _size>=16) 处写 '\0' (保证 c_str() 返回空串);
- 3. 不修改 _capacity 和 _str (容量保留 , 后续插入无需重新扩容)。
- 标准库对应:std::string::clear() , 功能一致(标准库也不释放容量 , 仅清空内容)。
9. 字符串比较运算符重载
bool operator<(const string& s) const;
- 参数:const string& s , 用于比较的另一个字符串对象。
- 返回值:bool , 当前对象 < 比较对象则返回 true , 否则返回 false。
- 功能:按字典序比较两个字符串(如 "abc" < "abd" 返回 true , "abc" < "ab" 返回 false )。
- 实现逻辑:调用 strcmp(c_str(), s.c_str()) ( <string.h> 中的字符串比较函数) , 若返回值 < 0 则返回 true , 否则返回 false。
- 标准库对应:std::string::operator<(const string& s) const , 字典序规则完全一致。
bool operator<=(const string& s) const;
- 返回值:bool , 当前对象 <= 比较对象则返回 true。
- 实现逻辑:复用 < 和 == 运算符 , 即 return *this < s || *this == s; 。
- 标准库对应 : std::string::operator<=(const string& s) const , 语义一致。
bool operator>(const string& s) const;
- 返回值:bool , 当前对象 > 比较对象则返回 true。
- 实现逻辑:复用 < 运算符 , 即 return !( *this <= s ); (当前对象不小于等于对方 , 即大于对方)。
- 标准库对应:std::string::operator>(const string& s) const , 语义一致。
bool operator>=(const string& s) const;
- 返回值:bool , 当前对象 >= 比较对象则返回 true。
- 实现逻辑:复用 < 运算符 , 即 return !( *this < s ); 。
- 标准库对应:std::string::operator>=(const string& s) const , 语义一致。
bool operator==(const string& s) const;
- 返回值:bool , 当前对象与比较对象完全相等则返回 true。
- 实现逻辑:
- 1. 先比较 _size:若 _size != s._size , 直接返回 false;
- 2. 再比较内容:调用 strcmp(c_str(), s.c_str()) , 若返回值 == 0 则返回 true , 否则返回 false (避免因 _size 相同但内容不同导致误判)。
- 标准库对应:std::string::operator==(const string& s) const , 语义一致。
bool operator!=(const string& s) const;
- 返回值:bool , 当前对象与比较对象不相等则返回 true。
- 实现逻辑:复用 == 运算符 , 即 return !( *this == s ); 。
- 标准库对应:std::string::operator!=(const string& s) const , 语义一致。
1.3 bit::string类私有成员(private部分)
char* _str = nullptr;
- 作用:存储长字符串的堆内存指针(小字符串优化的核心成员)。
- 使用逻辑:
- 当 _size < 16 (短字符串):_str 保持 nullptr , 字符串内容存在 _buff 数组中;
- 当 _size >= 16 (长字符串):_str 指向堆上申请的内存(存储字符串内容 , 以 '\0' 结尾)。
- 与标准库的差异:标准库 std::string 也用SSO , 但 _str 可能被优化为“联合体(union)”(节省内存) , 此版本用独立的 _str 和 _buff , 实现更简单但占用内存略多( _str 始终占8字节 , _buff 占16字节)。
size_t _size = 0;
- 作用:存储字符串的有效字符个数(不包含末尾的 '\0' )。
- 初始值:默认初始化为 0 (C++11后类内成员可直接初始化) , 空字符串对象的 _size 为 0。
- 与其他成员的关联:_size 决定 operator[] , pop_back() , insert() 等函数的操作范围 , 也是 size() 函数的返回值。
size_t _capacity = 0;
- 作用:存储字符串的容量(即当前可存储的最大有效字符个数 , 不包含 '\0')。
- 使用逻辑:
- 当 _size < 16 :_capacity 固定为 15 (_buff 数组大小16 , 留1字节存 '\0' );
- 当 _size >= 16 :_capacity 为堆内存的有效容量(如申请20字节内存 , _capacity 为19)。
- 与 reserve() 的关联:reserve(n) 仅当 n > _capacity 时才扩容 , 并更新 _capacity 为 n。
char _buff[16];
- 作用:存储短字符串的栈数组(小字符串优化的核心成员) , 大小为16字节(可存15个有效字符 + 1个 '\0' )。
- 使用逻辑:_size < 16 时 , 字符串内容直接存在 _buff 中(无需申请堆内存 , 避免内存碎片 , 提高效率);_size >=16 时 , _buff 闲置(此版本未复用 , 属于简单实现)。
1.4 bit::string 类静态常量(public部分)
static const size_t npos;
- 作用:表示“无效下标”或“到末尾”的静态常量(全类共享 , 仅需定义一次)。
- 值的约定:通常在 .cpp 文件中定义为 const size_t bit::string::npos = -1; (因 size_t 是无符号类型 , -1 会被解释为该类型的最大值 , 确保大于任何合法下标)。
- 与成员函数的关联:erase() , find() , substr() 等函数用 npos 作为默认参数或返回值 , 语义与标准库 std::string::npos 完全一致。
1.5 全局流运算符与交换函数(命名空间bit内)
ostream& operator<<(ostream& out, const string& s); (输出运算符重载)
- 参数:
- ostream& out :输出流对象(如 cout ) , 传引用是为了链式输出(如 cout << s1 << s2; );
- const string& s :要输出的 string 对象(const保证不修改 , 传引用避免拷贝)。
- 返回值:ostream& , 返回 out 的引用 , 支持链式输出。
- 功能:实现 string 对象的流式输出(如 cout << s; , 直接打印字符串内容 , 而非地址)。
- 实现逻辑(.cpp 中):遍历 s 的字符(从 s.begin() 到 s.end() ) , 通过 out.put(c) 或 out << c 逐个输出 , 最终返回 out。
- 与标准库的关联:对应 std::ostream& operator<<(std::ostream&, const std::string&) , 使用方式完全一致 , 是 string 类的核心全局接口之一(需声明为友元或通过 c_str() 访问内容 , 此头文件未显式声明友元 , 推测 .cpp 中通过 c_str() 实现: out << s.c_str(); , 更简洁)。
istream& operator>>(istream& in, string& s); (输入运算符重载)
- 参数:
- istream& in :输入流对象(如 cin ) , 传引用支持链式输入;
- string& s :存储输入内容的 string 对象(非const , 需修改)。
- 返回值:istream& , 返回 in 的引用 , 支持链式输入(如 cin >> s1 >> s2; )。
- 功能:实现 string 对象的流式输入(如 cin >> s; ) , 默认跳过空白字符(空格、回车、制表符等) , 直到遇到下一个空白字符停止。
- 实现逻辑(.cpp 中):
- 1. 先调用 s.clear() 清空 s 原有内容;
- 2. 定义临时字符 char ch , 通过 in.get(ch) 读取字符 , 跳过开头的空白字符(while (isspace(ch)) in.get(ch); );
- 3. 循环读取非空白字符 , 调用 s.push_back(ch) 追加到 s , 直到读取到空白字符或流结束;
- 4. 返回 in。
- 与标准库的关联:对应 std::istream& operator>>(std::istream&, std::string&) , 行为一致(默认跳空白) , 但不支持读取带空格的字符串(需用 getline )。
istream& getline(istream& is, string& str, char delim = '\n'); (全局getline函数)
- 参数:
- istream& is :输入流对象(如 cin);
- string& str :存储输入内容的 string 对象;
- char delim :终止字符(默认是换行符 '\n' ) , 表示读取到该字符时停止(不包含终止字符)。
- 返回值:istream& , 返回 is 的引用 , 支持链式调用。
- 功能:读取一行字符串(包括空格) , 直到遇到终止字符 delim 或流结束(解决 operator>> 不能读空格的问题) , 如 getline(cin, s); 读取一整行输入。
- 实现逻辑(.cpp 中):
- 1. 调用 str.clear() 清空原有内容;
- 2. 定义临时字符 char ch , 通过 is.get(ch) 循环读取字符;
- 3. 若读取到的字符不是 delim 且流未结束 , 调用 str.push_back(ch) 追加;若读取到 delim , 终止循环(不将 delim 加入 str );
- 4. 返回 is。
- 与标准库的关联:对应 std::getline(std::istream&, std::string&, char) , 功能和参数完全一致 , 是读取带空格字符串的核心接口。
void swap(string& x, string& y); (全局swap函数)
- 参数:string& x , string& y :要交换的两个 string 对象(传引用避免拷贝)。
- 功能:交换两个 string 对象的内容(替代 std::swap ,效率更高)。
- 实现逻辑(.cpp 中):直接调用成员函数 x.swap(y) (成员 swap 直接交换成员变量 , 无堆内存操作 , 比 std::swap 的“拷贝-赋值-拷贝”效率高)。
- 与标准库的关联:符合C++标准习惯——为自定义类型提供全局 swap 函数 , 并优先调用成员 swap , 确保 std::swap(x, y) 时也能匹配到高效的自定义实现( std::swap 内部会检测是否有自定义 swap , 若有则调用)。
1.6 头文件整体设计总结
- 1. 核心优化:采用 小字符串优化(SSO) , 通过 _buff[16] 存储短字符串(<16字符) , 避免频繁堆内存申请 , 提升效率;长字符串用 _str 指向堆内存 , 保证扩展性。
- 2. 接口完整性:覆盖 std::string 的核心接口(构造/析构/拷贝赋值 , 迭代器 , 容量管理 , 修改/查找/比较操作 , 流输入输出) , 使用方式与标准库高度兼容 , 降低使用成本。
- 3. 安全与效率平衡:用 assert 做参数合法性检查(调试阶段防越界) , 用“拷贝并交换”实现赋值重载(简化代码且避免内存泄漏) , 全局 swap 复用成员函数(保证效率)。
- 4. 依赖与关联:依赖 <iostream> (流操作) , <string.h> (C字符串函数) , <assert.h> (调试检查) , 成员函数间高度复用(如 operator+= 调用 push_back / append , 比较运算符复用 strcmp 和 == ) , 逻辑清晰且减少冗余。
2. 实现文件---string.cpp
#include"string.h"namespace bit
{const size_t string::npos = -1;/*string::string():_str(new char[1]{'\0'}),_size(0),_capacity(0){}*/string::string(const char* str):_size(strlen(str)){//cout << "string::string(const char* str)" << endl;_capacity = _size;_str = new char[_size + 1];//strcpy(_str, str);memcpy(_str, str, _size + 1);}string::~string(){delete[] _str;_str = nullptr;_size = _capacity = 0;}// s3(s1)/*string::string(const string& s){cout << "string::string(const string& s)" << endl;_str = new char[s._capacity + 1];memcpy(_str, s._str, s._size + 1);_size = s._size;_capacity = s._capacity;}*/void string::swap(string& s){std::swap(_str, s._str);std::swap(_size, s._size);std::swap(_capacity, s._capacity);}// s3(s1)string::string(const string& s){cout << "string::string(const string& s)" << endl;string tmp(s._str);swap(tmp);}// s1 = s2/*string& string::operator=(const string& s){if (this != &s){char* tmp = new char[s._capacity + 1];memcpy(tmp, s._str, s._size + 1);delete[] _str;_str = tmp;_size = s._size;_capacity = s._capacity;}return *this;}*/// s1 = s2/*string& string::operator=(const string& s){if (this != &s){string tmp(s);swap(tmp);}return *this;}*/string& string::operator=(string tmp){cout << "string& string::operator=(string tmp)" << endl;swap(tmp);return *this;}string::iterator string::begin(){return _str;}string::iterator string::end(){return _str + _size;}string::const_iterator string::begin() const{return _str;}string::const_iterator string::end() const{return _str + _size;}const char* string::c_str() const{return _str;}size_t string::size() const{return _size;}char& string::operator[](size_t i){assert(i < _size);return _str[i];}const char& string::operator[](size_t i) const{assert(i < _size);return _str[i];}void string::reserve(size_t n){if (n > _capacity){cout << "reserve:" << n << endl;char* tmp = new char[n + 1];//strcpy(tmp, _str);memcpy(tmp, _str, _size + 1);delete[] _str;_str = tmp;_capacity = n;}}void string::push_back(char ch){if (_size >= _capacity){size_t newcapacity = _capacity == 0 ? 4 : 2 * _capacity;reserve(newcapacity);}_str[_size] = ch;++_size;_str[_size] = '\0';}void string::append(const char* str){size_t len = strlen(str);if (_size + len > _capacity){size_t newcapacity = 2 * _capacity > _size + len ? 2 * _capacity : _size + len;reserve(newcapacity);}//strcpy(_str+_size, str);memcpy(_str + _size, str, len + 1);_size += len;}string& string::operator+=(char ch){push_back(ch);return *this;}string& string::operator+=(const char* str){append(str);return *this;}void string::pop_back(){assert(_size > 0);--_size;_str[_size] = '\0';}string& string::insert(size_t pos, char ch){assert(pos <= _size);if (_size >= _capacity){size_t newcapacity = _capacity == 0 ? 4 : 2 * _capacity;reserve(newcapacity);}// 挪动数据/*int end = _size;while (end >= (int)pos){_str[end + 1] = _str[end];--end;}*/size_t end = _size + 1;while (end > pos){_str[end] = _str[end - 1];--end;}_str[pos] = ch;++_size;return *this;}string& string::insert(size_t pos, const char* str){assert(pos <= _size);size_t len = strlen(str);if (_size + len > _capacity){size_t newcapacity = 2 * _capacity > _size + len ? 2 * _capacity : _size + len;reserve(newcapacity);}// 挪动数据/*int end = _size;while (end >= (int)pos){_str[end + len] = _str[end];--end;}*/size_t end = _size + len;while (end > pos + len - 1){_str[end] = _str[end - len];--end;}for (size_t i = 0; i < len; i++){_str[pos + i] = str[i];}_size += len;return *this;}string& string::erase(size_t pos, size_t len){assert(pos < _size);// 要删除的数据,大于pos后面的字符个数// pos后面全删if (len == npos || len >= (_size - pos)){_size = pos;_str[_size] = '\0';}else{size_t i = pos + len;memmove(_str + pos, _str + i, _size + 1 - i);_size -= len;}return *this;}size_t string::find(char ch, size_t pos) const{for (size_t i = pos; i < _size; i++){if (_str[i] == ch){return i;}}return npos;}size_t string::find(const char* str, size_t pos) const{// kmpconst char* p1 = strstr(_str + pos, str);if (p1 == nullptr){return npos;}else{return p1 - _str;}}string string::substr(size_t pos, size_t len) const{if (len == npos || len >= _size - pos){len = _size - pos;}string ret;ret.reserve(len);for (size_t i = 0; i < len; i++){ret += _str[pos + i];}//cout << &ret << endl;return ret;}// s1 < s2// "hello" "hello" -> false// "hellox" "hello" -> false// "hello" "hellox" -> truebool string::operator<(const string& s) const{size_t i1 = 0, i2 = 0;while (i1 < _size && i2 < s._size){if (_str[i1] < s[i2]){return true;}else if (_str[i1] > s[i2]){return false;}else{++i1;++i2;}}return i2 < s._size;}bool string::operator<=(const string& s) const{return *this < s || *this == s;}bool string::operator>(const string& s) const{return !(*this <= s);}bool string::operator>=(const string& s) const{return !(*this < s);}bool string::operator==(const string& s) const{size_t i1 = 0, i2 = 0;while (i1 < _size && i2 < s._size){if (_str[i1] != s[i2]){return false;}else{++i1;++i2;}}return i1 == _size && i2 == s._size;}bool string::operator!=(const string& s) const{return !(*this == s);}void string::clear(){_str[0] = '\0';_size = 0;}ostream& operator<<(ostream& out, const string& s){//out << s.c_str();for (size_t i = 0; i < s.size(); i++){out << s[i];}return out;}istream& operator>>(istream& in, string& s){s.clear();char buff[128];int i = 0;char ch = in.get();while (ch != ' ' && ch != '\n'){buff[i++] = ch;if (i == 127){buff[i] = '\0';s += buff;i = 0;}ch = in.get();}if (i > 0){buff[i] = '\0';s += buff;}return in;}istream& getline(istream& in, string& s, char delim){s.clear();char buff[128];int i = 0;char ch = in.get();while (ch != delim){buff[i++] = ch;if (i == 127){buff[i] = '\0';s += buff;i = 0;}ch = in.get();}if (i > 0){buff[i] = '\0';s += buff;}return in;}void swap(string& x, string& y){x.swap(y);}
}
2.1 静态常量初始化与拷贝控制(核心基础)
这部分是自定义 string 的“基石” , 决定内存管理和对象拷贝的安全性 , 实现整体规范 , 采用了业内推荐的高效写法。
1. 静态常量 npos 初始化
const size_t string::npos = -1;
- 分析:
- 正确性:npos 是 string 类的“无效位置”标记 , 用 -1 初始化(因 size_t 是无符号类型 , -1 会被解释为该类型的最大值) , 符合 C++ 标准库 std::string 的设计 , 后续 find , erase 等接口用它判断“未找到”或“删除至末尾” , 逻辑正确。
- 注意:需确保头文件中已声明 static const size_t npos; , 当前实现与头文件匹配。
2. 构造函数(默认+带参)
带参构造:
string::string(const char* str):_size(strlen(str)){//cout << "string::string(const char* str)" << endl;_capacity = _size;_str = new char[_size + 1];//strcpy(_str, str);memcpy(_str, str, _size + 1);}
- 逻辑:计算字符串长度 _size = strlen(str) , 按长度开辟内存(_size + 1 , 预留 \0 空间) , 用 memcpy 拷贝字符串。
- 优点:用 memcpy 替代 strcpy 更高效( memcpy 直接按字节拷贝 , 无需判断 \0 , 且已知长度 _size + 1) , 边界处理正确(给 \0 留位置)。
/*string::string():_str(new char[1]{'\0'}),_size(0),_capacity(0) {}*/
- 默认构造:头文件中默认构造已被注释(合并到带参构造的默认参数 const char* str = "" ) , 实现中也注释了独立默认构造 , 逻辑一致(带参构造传入空串时 , _size=0 , _capacity=0 , _str 指向长度为1的 \0 字符串) , 无问题。
3. 析构函数
string::~string(){delete[] _str;_str = nullptr;_size = _capacity = 0;}
- 逻辑:释放 _str 指向的动态内存 , 将成员置空/置 0。
- 正确性:无内存泄漏风险( new[] 分配的内存用 delete[] 释放) 。
4. 拷贝构造函数
第一段注释的拷贝构造函数(传统写法)
/*string::string(const string& s) {cout << "string::string(const string& s)" << endl;_str = new char[s._capacity + 1];memcpy(_str, s._str, s._size + 1);_size = s._size;_capacity = s._capacity; }*/
- 作用与逻辑:
- 这是最基础的深拷贝构造函数实现 , 用于创建一个新的 string 对象 , 拷贝参数 s 的内容:
- _str = new char[s._capacity + 1] : 为新对象的 _str 动态分配内存(容量和原对象一致 , +1 是为存储字符串结束符 \0 )。
- memcpy(_str, s._str, s._size + 1) :把原对象 s 的字符串内容(包括 \0 )拷贝到新分配的内存。
- _size = s._size; _capacity = s._capacity :同步原对象的字符串长度和容量。
第二段拷贝构造函数(优化写法)
void string::swap(string& s){std::swap(_str, s._str);std::swap(_size, s._size);std::swap(_capacity, s._capacity);}// s3(s1)string::string(const string& s){cout << "string::string(const string& s)" << endl;string tmp(s._str);swap(tmp);}
- 优化思路:
- 这是利用 “拷贝 - 交换(Copy-Swap)” 惯用法的写法 , 优点是:
- 1. 代码简洁:借助已有的构造函数(string tmp(s._str)调用带 const char* 参数的构造函数)和 swap 成员函数 , 避免重复写“分配内存、拷贝数据、同步大小容量”的逻辑。
- 2. 异常安全:如果 string tmp(s._str) 构造过程中抛异常(比如内存分配失败) , 当前对象的状态不会被破坏(因为 tmp 还没和当前对象交换)。
- 具体逻辑:
- string tmp(s._str) :先用原对象的 _str 构造一个临时对象 tmp (完成深拷贝)。
- swap(tmp) :通过 swap 成员函数 , 把当前对象和 tmp 的资源( _str 、_size 、_capacity)交换——当前对象获得 tmp 的拷贝数据 , tmp 则接管当前对象原来的(可能无效或旧的)资源 , 函数结束时 tmp 析构 , 自动释放这些旧资源。
总结:
- 第一段是传统深拷贝写法 , 逻辑直接但代码冗余;
- 第二段是拷贝 - 交换优化写法 , 利用已有接口简化代码 , 提升异常安全性。
- 通常实际项目里会优先选第二种优化写法 , 所以把第一段传统实现注释掉了。
5. 赋值运算符重载(operator=)
第一种:传统深拷贝写法(被注释的 operator=)
/*string& string::operator=(const string& s) {if (this != &s){char* tmp = new char[s._capacity + 1]; memcpy(tmp, s._str, s._size + 1); delete[] _str; _str = tmp; _size = s._size; _capacity = s._capacity; }return *this; }*/
- 逻辑步骤:
- if (this != &s) :避免自赋值(如 s1 = s1 , 防止释放自身内存后非法访问)。
- char* tmp = new char[s._capacity + 1] : 为新数据分配内存(容量和原对象s一致 , +1存 \0)。
- memcpy(tmp, s._str, s._size + 1)) : 拷贝原对象 s 的字符串内容(包括 \0)到临时内存 tmp。
- delete[] _str :释放当前对象旧内存 , 防止内存泄漏。
- _str = tmp; _size = s._size; _capacity = s._capacity :更新当前对象的资源(指向新内存 , 同步大小和容量)。
- return *this:返回当前对象 , 支持链式赋值(如 s1 = s2 = s3 )。
- 缺点:
- 若 new char[...] 失败(内存不足抛异常), 当前对象的 _str 已被 delete[] 释放 , 会变成无效状态(异常不安全)。
第二种:拷贝 - 交换优化写法(被注释的operator=)
/*string& string::operator=(const string& s) {if (this != &s){string tmp(s); swap(tmp); }return *this; }*/
- 逻辑步骤:
- if (this != &s) :同样避免自赋值。
- string tmp(s) :用拷贝构造函数创建临时对象 tmp(深拷贝原对象 s 的资源 , 若构造失败 , 当前对象状态不受影响)。
- swap(tmp) :通过 swap 成员函数 , 交换当前对象和 tmp 的资源(当前对象获得 tmp 的新拷贝数据 , tmp 接管当前对象旧资源)。
- 函数结束时 , tmp 析构(自动调用 ~string() 释放旧资源 , 无需手动管理)。
- 优点:
- 异常安全:若 string tmp(s) 抛异常(如内存分配失败) , 当前对象的旧资源还在 , 不会被破坏。
- 代码简洁:复用拷贝构造和 swap 逻辑 , 无需重复写“分配、拷贝、释放”代码。
第三种 : 值传递版赋值运算符重载
string& string::operator=(string tmp) {cout << "string& string::operator=(string tmp)" << endl;swap(tmp);return *this; }
核心逻辑 : 这是 赋值运算符重载的“值传递的最终优化版” , 利用 C++ 的值传递特性 + swap 惯用法,实现高效、异常安全的赋值:
- 1. 参数是值传递( string tmp ):
- 调用该函数时 , 编译器会自动拷贝一份实参(如 s1 = s2 中 , s2 会被拷贝给 tmp) , 相当于“自动帮你完成深拷贝”。
- 2. swap(tmp):
- 交换当前对象(*this)和 tmp 的资源( _str , _size , _capacity 等)。交换后:
- - 当前对象获得 tmp 的新拷贝数据(完成赋值);
- - tmp 持有当前对象的旧资源,函数结束后 tmp 会自动析构(调用 ~string() 释放旧资源)。
- 3. return *this :支持链式赋值(如 s1 = s2 = s3 )。
优点
- 异常安全:
- 若拷贝实参给 tmp 时抛异常(如内存不足) , 当前对象的状态不会被修改(因为 tmp 还没和它交换)。
- 代码极简:
- 无需手动写“分配内存、拷贝数据、释放旧内存” , 借助值传递和 swap 一键完成。
- 自赋值安全:
- 即使 s1 = s1 , 值传递会拷贝一份 s1 给 tmp , 交换后不影响逻辑(等价自交换)。
2.2 迭代器与基础访问接口
这部分接口是 string 支持“范围 for” , 随机访问的核心 , 实现完全正确 , 符合迭代器设计规范。
1. 迭代器接口(begin()/end())
string::iterator string::begin()
{return _str;
}string::iterator string::end()
{return _str + _size;
}string::const_iterator string::begin() const
{return _str;
}string::const_iterator string::end() const
{return _str + _size;
}
- 实现:begin() 返回 _str(首字符地址) , end() 返回 _str + _size(尾字符的下一个位置 , 符合“左闭右开”区间)。
- 正确性:
- 普通迭代器( iterator) : 返回 char* , 支持读写;
- const 迭代器( const_iterator ) : 返回 const char* , 仅支持读 , 符合 const 正确性。
- 配合范围 for 使用时 , for (auto ch : s) 会自动调用 begin() 和 end() , 逻辑正常。
2. 基础访问(c_str()/size()/operator[])
const char* string::c_str() const
{return _str;
}size_t string::size() const
{return _size;
}char& string::operator[](size_t i)
{assert(i < _size);return _str[i];
}const char& string::operator[](size_t i) const
{assert(i < _size);return _str[i];
}
- c_str() :返回 _str , 符合标准(需确保 _str 以 \0 结尾 , 当前构造/修改接口均满足 , 无问题)。
- size() :直接返回 _size , 简洁正确( _size 记录字符串有效长度 , 不含 \0 )。
- operator[] :
- 用 assert(i < _size) 检查下标合法性 , 避免越界访问。
- 普通版本返回 char&(支持修改 , 如 s[0] = 'a') , const 版本返回 const char&(仅读) , 符合const正确性。
2.3 内存管理(reserve)
void string::reserve(size_t n)
{if (n > _capacity){cout << "reserve:" << n << endl;char* tmp = new char[n + 1];//strcpy(tmp, _str);memcpy(tmp, _str, _size + 1);delete[] _str;_str = tmp;_capacity = n;}
}
reserve 是控制容量 , 减少扩容次数的关键接口 , 实现逻辑正确。
- 实现逻辑:若 n > _capacity , 则开辟 n + 1 字节的新内存 , 拷贝旧数据到新内存 , 释放旧内存 , 更新 _str 和 _capacity。
- 优点:
- 仅在 n 大于当前容量时扩容 , 避免无效操作。
- 预留 n + 1 字节(含 \0) , 后续修改无需额外扩容。
2.4 增删查改接口(核心功能)
1. 尾部增加(push_back/append/operator+=)
void string::push_back(char ch)
{if (_size >= _capacity){size_t newcapacity = _capacity == 0 ? 4 : 2 * _capacity;reserve(newcapacity);}_str[_size] = ch;++_size;_str[_size] = '\0';
}void string::append(const char* str)
{size_t len = strlen(str);if (_size + len > _capacity){size_t newcapacity = 2 * _capacity > _size + len ? 2 * _capacity : _size + len;reserve(newcapacity);}//strcpy(_str+_size, str);memcpy(_str + _size, str, len + 1);_size += len;
}string& string::operator+=(char ch)
{push_back(ch);return *this;
}string& string::operator+=(const char* str)
{append(str);return *this;
}
- push_back(char ch) :
- 扩容判断:若 _size >= _capacity , 按“0 则扩 4 , 否则扩 2 倍”的策略扩容(符合标准库的扩容逻辑 , 平衡性能和内存)。
- 操作:在 _str[_size] 赋值 ch , _size 自增后补 \0 , 确保字符串结尾正确。
- append(const char* str) :
- 计算 str 长度 len , 若 _size + len 超过容量则扩容(扩容到 2*_capacity 和 _size+len 的较大值)。
- 用 memcpy 直接拷贝 str 到 _str + _size , 效率高于循环赋值 , 最后更新 _size。
- operator+= :直接调用 push_back 或 append , 代码复用性好 , 逻辑一致
2. 尾部删除(pop_back)
void string::pop_back()
{assert(_size > 0);--_size;_str[_size] = '\0';
}
- 逻辑 : assert(_size > 0)确保非空 , _size 自减后补 \0 , 简洁正确。
- 注意:不修改 _capacity (string的容量默认只增不减 , 符合标准行为)。
3. 插入(insert : 单字符/字符串)
string& string::insert(size_t pos, char ch)
{assert(pos <= _size);if (_size >= _capacity){size_t newcapacity = _capacity == 0 ? 4 : 2 * _capacity;reserve(newcapacity);}// 挪动数据/*int end = _size;while (end >= (int)pos){_str[end + 1] = _str[end];--end;}*/size_t end = _size + 1;while (end > pos){_str[end] = _str[end - 1];--end;}_str[pos] = ch;++_size;return *this;
}string& string::insert(size_t pos, const char* str)
{assert(pos <= _size);size_t len = strlen(str);if (_size + len > _capacity){size_t newcapacity = 2 * _capacity > _size + len ? 2 * _capacity : _size + len;reserve(newcapacity);}// 挪动数据/*int end = _size;while (end >= (int)pos){_str[end + len] = _str[end];--end;}*/size_t end = _size + len;while (end > pos + len - 1){_str[end] = _str[end - len];--end;}for (size_t i = 0; i < len; i++){_str[pos + i] = str[i];}_size += len;return *this;
}
- 共性优点:
- 用 assert(pos <= _size) 检查插入位置合法性(pos 可等于 _size , 即尾部插入)。
- 扩容逻辑正确(单字符插入判断 _size >= _capacity , 字符串插入判断 _size + len >_capacity)。
- 数据挪动采用“无符号变量循环” (size_t end = ... ) , 避免注释中“int 强转”的潜在问题(如 pos 超过 int 范围导致循环错误)。
- 细节优化点:
- 字符串插入中 , “循环赋值 str 到 _str”可替换为 memcpy(_str + pos, str, len) , 效率更高(已知 len 长度 , 无需循环)。
4. 删除(erase)
string& string::erase(size_t pos, size_t len)
{assert(pos < _size);// 要删除的数据,大于pos后面的字符个数// pos后面全删if (len == npos || len >= (_size - pos)){_size = pos;_str[_size] = '\0';}else{size_t i = pos + len;memmove(_str + pos, _str + i, _size + 1 - i);_size -= len;}return *this;
}
- 逻辑:
- 边界判断:assert(pos < _size) 确保删除位置有效。
- 两种场景:
- 1. 若 len 是 npos 或 len >= _size - pos (删除长度超过剩余字符) , 直接将 _size 设为 pos 并补 \0 (尾部截断 , 高效)。
- 2. 否则用 memmove 挪动后续字符(memmove 支持内存重叠 , 比 memcpy 更安全 , 此处 _str + pos 和 _str + i 有重叠 , 必须用 memmove)。
- 正确性:无内存越界 , 逻辑完全符合预期。
5. 查找(find : 单字符/字符串)
size_t string::find(char ch, size_t pos) const
{for (size_t i = pos; i < _size; i++){if (_str[i] == ch){return i;}}return npos;
}size_t string::find(const char* str, size_t pos) const
{// kmpconst char* p1 = strstr(_str + pos, str);if (p1 == nullptr){return npos;}else{return p1 - _str;}
}
- 单字符查找:循环遍历 _str 从 pos 开始的字符 , 找到返回下标 , 否则返回 npos , 逻辑简单正确。
- 字符串查找:直接调用 strstr (C 标准库函数 , 查找子串首次出现位置) , 返回值转换为相对于 _str 的下标 , 代码简洁。
- 注意:strstr 依赖 _str 以 \0 结尾 , 当前 string 的所有修改接口均满足 , 无问题。
6. 子串(substr)
string string::substr(size_t pos, size_t len) const{if (len == npos || len >= _size - pos){len = _size - pos;}string ret;ret.reserve(len);for (size_t i = 0; i < len; i++){ret += _str[pos + i];}//cout << &ret << endl;return ret;}
- 逻辑:
- 边界处理:若 len 是 npos 或 len >= _size - pos , 则 len 设为 _size - pos (避免越界)。
- 构造返回值:创建空 ret , 预留 len 容量(减少扩容) , 循环赋值子串字符 , 最后返回 ret。
- 正确性:无问题 , 若想优化可将循环赋值替换为 memcpy (需先 reserve , 再拷贝后手动更新 _size 和 \0 )。
7. 清空( clear )
void string::clear()
{_str[0] = '\0';_size = 0;
}
- 逻辑:将 _str[0] 设为 \0 , _size 置0 , 不修改 _capacity。
- 正确性:符合标准库行为( clear 只清空内容 , 不释放容量) , 高效(无需释放内存)。
2.5 比较运算符重载
// s1 < s2
// "hello" "hello" -> false
// "hellox" "hello" -> false
// "hello" "hellox" -> true
bool string::operator<(const string& s) const
{size_t i1 = 0, i2 = 0;while (i1 < _size && i2 < s._size){if (_str[i1] < s[i2]){return true;}else if (_str[i1] > s[i2]){return false;}else{++i1;++i2;}}return i2 < s._size;
}bool string::operator<=(const string& s) const
{return *this < s || *this == s;
}bool string::operator>(const string& s) const
{return !(*this <= s);
}bool string::operator>=(const string& s) const
{return !(*this < s);
}bool string::operator==(const string& s) const
{size_t i1 = 0, i2 = 0;while (i1 < _size && i2 < s._size){if (_str[i1] != s[i2]){return false;}else{++i1;++i2;}}return i1 == _size && i2 == s._size;
}bool string::operator!=(const string& s) const
{return !(*this == s);
}
实现了所有比较运算符(< / <= / > / >= / == / != ) , 逻辑严格遵循字符串比较规则(按ASCII码逐字符比较 , 长度短且前缀相同则短串更小)。
- 核心逻辑:以 < 和 == 为基础 , 其他运算符通过“逻辑取反”或“组合”实现(如 >= 是 !(*this < s) ) , 代码复用性极高 , 避免重复逻辑。
- 正确性:
- == 需判断“所有字符相同且长度相同”(i1 == _size && i2 == s._size ) , 避免“短串是长串前缀却误判相等”(如 "hello" 和 "helloworld" )。
- < 的最终判断 return i2 < s._size(若前 min(_size, s._size) 个字符相同 , 短串更小) , 逻辑正确。
2.6 全局流运算符与 swap
这部分接口是 string 支持 IO 操作和全局交换的关键 , 实现细节考虑周到。
1. 输出运算符(operator<<)
ostream& operator<<(ostream& out, const string& s)
{//out << s.c_str();for (size_t i = 0; i < s.size(); i++){out << s[i];}return out;
}
- 实现:循环打印 s[i] (从 0 到 s.size()-1) , 而非直接打印 s.c_str()。
- 优点:即使 _str 中包含 \0 (如二进制数据) , 也能打印到 size() 长度 , 更灵活;若仅打印文本 , 效果与 c_str() 一致。
2. 输入运算符(operator>>)
istream& operator>>(istream& in, string& s)
{s.clear();char buff[128];int i = 0;char ch = in.get();while (ch != ' ' && ch != '\n'){buff[i++] = ch;if (i == 127){buff[i] = '\0';s += buff;i = 0;}ch = in.get();}if (i > 0){buff[i] = '\0';s += buff;}return in;
}
- 优化点:
- 先 s.clear() 清空原有内容 , 避免追加旧数据。
- 用 char buff[128] 作为临时缓冲区 , 避免每次读一个字符就扩容(减少 reserve 调用次数 , 提升性能)。
- 跳过空格和换行(符合 cin >> s 的默认行为:不读空白字符)。
- 正确性:无内存越界 , 缓冲区满时及时追加到 s , 逻辑正确。
3. getline 函数
istream& getline(istream& in, string& s, char delim){s.clear();char buff[128];int i = 0;char ch = in.get();while (ch != delim){buff[i++] = ch;if (i == 127){buff[i] = '\0';s += buff;i = 0;}ch = in.get();}if (i > 0){buff[i] = '\0';s += buff;}return in;}
- 逻辑:与 operator>> 类似 , 但终止符是 delim(默认 \n ) , 且不跳过空白字符(符合 getline 读取整行的行为)。
- 正确性:解决了 operator>> 无法读取空格的问题 , 实现正确。
4. 全局 swap 函数
void swap(string& x, string& y)
{x.swap(y);
}
- 实现:直接调用 x.swap(y) (成员函数 swap ) , 符合标准库“全局函数调用成员函数”的设计,确保交换逻辑统一(避免全局函数与成员函数逻辑不一致)。
2.3 总结
这份实现文件核心逻辑正确 , 异常安全 , 代码风格简洁 , 完全能支撑 string 的日常使用(如字符串增删查改、IO 操作、比较)。
感谢大家的观看!