C++ string自定义类的实现

目录

[1. 头文件----string.h](#1. 头文件----string.h)

[1.1 头文件引入与命名空间](#1.1 头文件引入与命名空间)

[1.2 bit::string 类核心成员(public部分)](#1.2 bit::string 类核心成员(public部分))

[1. 迭代器类型声明](#1. 迭代器类型声明)

[2. 迭代器获取函数](#2. 迭代器获取函数)

[3. 构造、析构与拷贝控制](#3. 构造、析构与拷贝控制)

[4. 基础属性与访问接口](#4. 基础属性与访问接口)

[5. 容量管理](#5. 容量管理)

[6. 字符串修改操作](#6. 字符串修改操作)

[7. 字符串查找操作](#7. 字符串查找操作)

[8. 子串提取与清空](#8. 子串提取与清空)

[9. 字符串比较运算符重载](#9. 字符串比较运算符重载)

[1.3 bit::string类私有成员(private部分)](#1.3 bit::string类私有成员(private部分))

[1.4 bit::string 类静态常量(public部分)](#1.4 bit::string 类静态常量(public部分))

[1.5 全局流运算符与交换函数(命名空间bit内)](#1.5 全局流运算符与交换函数(命名空间bit内))

[1.6 头文件整体设计总结](#1.6 头文件整体设计总结)

[2. 实现文件---string.cpp](#2. 实现文件---string.cpp)

[2.1 静态常量初始化与拷贝控制(核心基础)](#2.1 静态常量初始化与拷贝控制(核心基础))

[1. 静态常量 npos 初始化](#1. 静态常量 npos 初始化)

[2. 构造函数(默认+带参)](#2. 构造函数(默认+带参))

[3. 析构函数](#3. 析构函数)

[4. 拷贝构造函数](#4. 拷贝构造函数)

[5. 赋值运算符重载(operator=)](#5. 赋值运算符重载(operator=))

[2.2 迭代器与基础访问接口](#2.2 迭代器与基础访问接口)

[1. 迭代器接口(begin()/end())](#1. 迭代器接口(begin()/end()))

[2. 基础访问(c_str()/size()/operator[])](#2. 基础访问(c_str()/size()/operator[]))

[2.3 内存管理(reserve)](#2.3 内存管理(reserve))

[2.4 增删查改接口(核心功能)](#2.4 增删查改接口(核心功能))

[2.5 比较运算符重载](#2.5 比较运算符重载)

[2.6 全局流运算符与 swap](#2.6 全局流运算符与 swap)

[2.3 总结](#2.3 总结)


在基本学习完了string类的内容之后 , 小编将用关于string类实现的一篇文章来进一步深入理解string类。小编仍然用string.h头文件 , string.cpp实现文件进行实现:

1. 头文件----string.h

cpp 复制代码
#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 头文件引入与命名空间

cpp 复制代码
#include<iostream> 
  • 作用:引入标准输入输出流库。
  • 关联:为后续声明 ostream& operator<<(输出) , istream& operator>> (输入)等流运算符重载提供基础 , 因为 ostream , istream 类定义在 <iostream> 中。
cpp 复制代码
#include<string.h> 
  • 作用:引入C语言字符串处理库(如 strlen , strcpy , strcmp , strstr 等函数)。
  • 关联:自定义 string 类的底层字符串操作(如构造时拷贝字符串 , 比较字符串 , 查找子串) , 需依赖这些C函数 , 例如通过 strlen 计算传入字符串长度 , 通过 strcpy 拷贝字符串内容。
cpp 复制代码
#include<assert.h> 
  • 作用:引入断言库 , 提供 assert() 宏。
  • 关联:用于成员函数的参数合法性检查 , 例如 operator[] 访问下标时检查 i <_size , insert / erase 时检查 pos <= _size , 若断言失败则直接终止程序 , 便于调试。
cpp 复制代码
using namespace std; 
  • 作用:引入标准命名空间 std。
  • 关联:使得代码中可直接使用 std 下的类(如 ostream , istream)和函数 , 无需写std::ostream , 简化流运算符重载的声明。
cpp 复制代码
namespace bit { ... } 
  • 作用:定义自定义命名空间 bit。
  • 关联:避免与标准库 std::string 或其他库的 string 类命名冲突 , 使用时需通过 bit::string 指定作用域。

1.2 bit::string 类核心成员(public部分)

1. 迭代器类型声明

cpp 复制代码
typedef char* iterator; 
  • 作用:将 char* (字符指针)这个原生类型 , 重命名为 iterator (普通迭代器类型名)。
  • 功能:用于遍历 , 修改 string 对象的字符(如 begin() 返回首字符地址 , end() 返回尾后字符地址) , 支持 ++ / -- /解引用( * )等指针操作。后续通过 iterator 定义的变量 , 本质就是 char* , 用于遍历和修改 string 对象中的非 const 字符(比如通过 begin() / end() 获取迭代器 , 修改字符串内容)。
  • 与标准库关联:对应 std::string::iterator , 行为一致(如遍历字符串时从 begin() 到 end() )。
cpp 复制代码
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. 迭代器获取函数

cpp 复制代码
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() , 行为完全一致。
cpp 复制代码
iterator end(); 
  • 返回值 : iterator(char*) , 指向字符串尾后字符(_size<16 时指向 _buff[_size] , _size>=16 时指向 _str[_size] )。
  • 功能:标记非const对象的迭代器结束位置(不指向有效字符) , 作为遍历终止条件。
  • 标准库对应:std::string::end() , 行为完全一致。
cpp 复制代码
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。
cpp 复制代码
const_iterator end() const; 
  • 返回值 :const_iterator(const char*) , 指向const对象的尾后字符
  • 功能:标记const对象的迭代器结束位置 , 仅用于读遍历的终止条件。
  • 标准库对应:std::string::end() const。

3. 构造、析构与拷贝控制

cpp 复制代码
 //string(); (注释掉的默认构造)
  • 作用:原默认构造函数被注释 , 因为下面的带参构造用了默认参数 , 可替代默认构造的功能(避免默认构造与带参构造重载冲突)。
cpp 复制代码
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优化)。
cpp 复制代码
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() , 功能完全一致。
cpp 复制代码
~string(); (析构函数)
  • 功能:释放对象占用的堆内存(避免内存泄漏)。
  • 实现逻辑:仅当 _size >=16 时(_str 指向堆内存) , 才调用 delete[] _str;_size<16 时 _buff 是栈上数组 , 无需手动释放。
  • 标准库对应:std::string::~string() , 同样负责释放堆内存(若有)。
cpp 复制代码
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后可能有优化 , 但语义是深拷贝)。
cpp 复制代码
//string& operator=(const string& s); (注释掉的赋值运算符重载)
  • 作用:原传统赋值重载被注释 , 替换为下面的"值传递参数"版本 , 利用拷贝构造简化实现。
cpp 复制代码
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 的实现 , 更简洁安全。
cpp 复制代码
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. 基础属性与访问接口

cpp 复制代码
size_t size() const; 
  • 返回值:size_t(无符号整数) , 字符串的有效字符个数(不包含 '\0')。
  • 功能:获取字符串长度(如 s.size() 得到 s 中字符的个数)。
  • **标准库对应:**std::string::size() , 功能完全一致(与 std::string::length() 等价 , 此自定义类未实现 length() , 但 size() 是标准命名)。
cpp 复制代码
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 强制检查)。
cpp 复制代码
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. 容量管理

cpp 复制代码
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. 字符串修改操作

cpp 复制代码
void push_back(char ch); 
  • 参数:char ch , 要追加的单个字符。
  • 功能:在字符串末尾追加一个字符(如 s.push_back('!'); 将 '!' 加到 s 末尾)。
  • 实现逻辑
    1. 检查容量:若 _size +1 > _capacity , 调用 reserve(_size +1) 扩容;
    1. 追加字符: _size<16 时写 _buff[_size] = ch , 否则写 _str[_size] = ch;
    1. 更新 _size ( _size++ ) , 并在尾后位置写 '\0'(保证 c_str() 有效)。
  • 标准库对应:std::string::push_back(char c) , 功能一致。
cpp 复制代码
void append(const char* str); 
  • 参数:const char* str ,要追加的C风格字符串(需以 '\0' 结尾)。
  • 功能:在字符串末尾追加一个C风格字符串(如 s.append("123"); 将 "123" 加到 s 末尾)。
  • 实现逻辑
    1. 计算 str 长度 len = strlen(str) ;
    1. 检查容量:若 _size + len > _capacity , 调用 reserve(_size + len) 扩容;
    1. 拷贝 str 到字符串末尾( _size<16 时拷贝到 _buff[_size] , 否则拷贝到 _str[_size] );
    1. 更新 _size (_size += len ) , 尾后写 '\0'。
  • 标准库对应:std::string::append(const char* s) , 功能一致。
cpp 复制代码
string& operator+=(char ch); 
  • 参数: char ch ,要追加的单个字符。
  • 返回值: string& ,当前对象引用(支持链式操作,如 s += 'a' += 'b'; )。
  • 功能:追加单个字符(语义同 push_back ,但支持运算符语法)。
  • 实现逻辑:直接调用 push_back(ch) ,然后返回 *this 。
  • 标准库对应: std::string& operator+=(char c) ,功能一致。
cpp 复制代码
string& operator+=(const char* str); 
  • 参数: const char* str ,要追加的C风格字符串。
  • 返回值: string& ,当前对象引用(支持链式操作,如 s += "ab" += "cd"; )。
  • **功能:**追加C风格字符串(语义同 append ,支持运算符语法)。
  • 实现逻辑:直接调用 append(str) ,然后返回 *this 。
  • 标准库对应: std::string& operator+=(const char* s) ,功能一致。
cpp 复制代码
void pop_back(); 
  • 功能:删除字符串末尾的最后一个有效字符(如 s = "abc"; s.pop_back(); 后 s 为 "ab")。
  • 实现逻辑
    1. 先通过 assert(_size > 0) 检查字符串非空(空串删除会触发断言);
    1. 直接将 _size-- (减少有效字符计数 , 无需真删除字符 , 后续操作会覆盖);
    1. 在新的尾后位置(_size 下标处)写 '\0'(保证 c_str() 仍返回合法的C风格字符串)。
  • 标准库对应:std::string::pop_back() , 功能一致(标准库也要求调用前字符串非空 , 否则行为未定义 , 此版本用 assert 强制检查)。
cpp 复制代码
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) (插入位置不能超过字符串长度);
    1. 扩容:若 _size +1 > _capacity , 调用 reserve(_size +1);
    1. 移动字符:从末尾( _size 下标)到 pos 下标 , 将字符依次后移1位(避免覆盖要插入的位置);
    1. 插入字符:_size<16 时写 _buff[pos] = ch , 否则写 _str[pos] = ch;
    1. 更新 _size ( _size++ ) , 尾后写 '\0'。
  • 标准库对应:std::string::insert(size_t pos, char c) , 功能一致。
cpp 复制代码
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) ;
    1. 计算 str 长度 len = strlen(str) (若 str 为空串则不操作);
    1. 扩容:若 _size + len > _capacity , 调用 reserve(_size + len) ;
    1. 移动字符:从末尾到 pos 下标 , 将字符依次后移 len 位;
    1. 拷贝字符串:将 str 内容拷贝到 pos 起始的位置( _buff 或 _str);
    1. 更新 _size ( _size += len ) , 尾后写 '\0'。
  • 标准库对应:std::string::insert(size_t pos, const char* s) , 功能一致。
cpp 复制代码
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) (删除起始位置不能越界);
    1. 调整删除长度:若 pos + len > _size 或 len == npos , 则 len = _size - pos (避免删超范围);
    1. 移动字符:从 pos + len 下标开始 , 将字符依次前移 len 位(覆盖被删除的字符);
    1. 更新 _size ( _size -= len ) , 尾后写 '\0';
    1. (可选优化)若删除后 _size 大幅减小 , 可考虑缩容(此版本未实现 , 标准库也默认不缩容)。
  • 与 npos 的关联:npos 是静态常量(值通常为 -1 , 因 size_t 是无符号类型 , 实际存储为最大值) , 用于表示"到末尾"的范围 , 与标准库 std::string::npos 语义完全一致。
  • 标准库对应:std::string::erase(size_t pos = 0, size_t len = npos) , 功能一致。

7. 字符串查找操作

cpp 复制代码
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;
    1. 匹配字符:若 _size<16 则遍历 _buff[i] , 否则遍历 _str[i] , 找到与 ch 相等的字符则返回 i;
    1. 未找到:返回 npos。
  • 标准库对应:std::string::find(char c, size_t pos = 0) const , 返回值语义完全一致。
cpp 复制代码
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;
    1. 调用C函数:获取当前字符串的C风格指针( c_str() ) , 调用 strstr(_cstr + pos, str) (strstr 是 <string.h> 中的子串查找函数);
    1. 计算下标:若 strstr 返回非空指针 , 下标 = 返回指针 - c_str() ;若返回空指针 , 返回 npos。
  • 标准库对应:std::string::find(const char* s, size_t pos = 0) const , 功能一致(标准库内部可能不用 strstr , 但语义相同)。

8. 子串提取与清空

cpp 复制代码
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);
    1. 调整子串长度:若 pos + len > _size 或 len == npos , 则 len = _size - pos;
    1. 创建新对象:构造一个新的 string 对象 , 将当前字符串 pos 开始 , 长度为 len 的字符拷贝到新对象中;
    1. 返回新对象。
  • 标准库对应:std::string::substr(size_t pos = 0, size_t len = npos) const , 功能完全一致。
cpp 复制代码
 void clear(); 
  • 功能:清空字符串(仅保留空串 , 不释放容量)。
  • 实现逻辑
    1. 将 _size 设为 0;
    1. 在 _buff[0] (_size<16 )或 _str[0] ( _size>=16) 处写 '\0' (保证 c_str() 返回空串);
    1. 不修改 _capacity 和 _str (容量保留 , 后续插入无需重新扩容)。
  • **标准库对应:**std::string::clear() , 功能一致(标准库也不释放容量 , 仅清空内容)。

9. 字符串比较运算符重载

cpp 复制代码
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 , 字典序规则完全一致。
cpp 复制代码
bool operator<=(const string& s) const; 
  • 返回值:bool , 当前对象 <= 比较对象则返回 true。
  • 实现逻辑:复用 < 和 == 运算符 , 即 return *this < s || *this == s; 。
  • **标准库对应 :**std::string::operator<=(const string& s) const , 语义一致。
cpp 复制代码
bool operator>(const string& s) const; 
  • **返回值:**bool , 当前对象 > 比较对象则返回 true。
  • 实现逻辑:复用 < 运算符 , 即 return !( *this <= s ); (当前对象不小于等于对方 , 即大于对方)。
  • **标准库对应:**std::string::operator>(const string& s) const , 语义一致。
cpp 复制代码
bool operator>=(const string& s) const; 
  • 返回值:bool , 当前对象 >= 比较对象则返回 true。
  • **实现逻辑:**复用 < 运算符 , 即 return !( *this < s ); 。
  • 标准库对应:std::string::operator>=(const string& s) const , 语义一致。
cpp 复制代码
bool operator==(const string& s) const; 
  • 返回值:bool , 当前对象与比较对象完全相等则返回 true。
  • 实现逻辑
    1. 先比较 _size:若 _size != s._size , 直接返回 false;
    1. 再比较内容:调用 strcmp(c_str(), s.c_str()) , 若返回值 == 0 则返回 true , 否则返回 false (避免因 _size 相同但内容不同导致误判)。
  • 标准库对应:std::string::operator==(const string& s) const , 语义一致。
cpp 复制代码
bool operator!=(const string& s) const; 
  • 返回值:bool , 当前对象与比较对象不相等则返回 true。
  • 实现逻辑:复用 == 运算符 , 即 return !( *this == s ); 。
  • 标准库对应:std::string::operator!=(const string& s) const , 语义一致。

1.3 bit::string类私有成员(private部分)

cpp 复制代码
char* _str = nullptr; 
  • 作用:存储长字符串的堆内存指针(小字符串优化的核心成员)。
  • 使用逻辑
  • 当 _size < 16 (短字符串):_str 保持 nullptr , 字符串内容存在 _buff 数组中;
  • 当 _size >= 16 (长字符串):_str 指向堆上申请的内存(存储字符串内容 , 以 '\0' 结尾)。
  • 与标准库的差异:标准库 std::string 也用SSO , 但 _str 可能被优化为"联合体(union)"(节省内存) , 此版本用独立的 _str 和 _buff , 实现更简单但占用内存略多( _str 始终占8字节 , _buff 占16字节)。
cpp 复制代码
size_t _size = 0; 
  • 作用:存储字符串的有效字符个数(不包含末尾的 '\0' )。
  • 初始值:默认初始化为 0 (C++11后类内成员可直接初始化) , 空字符串对象的 _size 为 0。
  • 与其他成员的关联:_size 决定 operator[] , pop_back() , insert() 等函数的操作范围 , 也是 size() 函数的返回值。
cpp 复制代码
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。
cpp 复制代码
char _buff[16]; 
  • 作用:存储短字符串的栈数组(小字符串优化的核心成员) , 大小为16字节(可存15个有效字符 + 1个 '\0' )。
  • 使用逻辑:_size < 16 时 , 字符串内容直接存在 _buff 中(无需申请堆内存 , 避免内存碎片 , 提高效率);_size >=16 时 , _buff 闲置(此版本未复用 , 属于简单实现)。

1.4 bit::string 类静态常量(public部分)

cpp 复制代码
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内)

cpp 复制代码
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(); , 更简洁)。
cpp 复制代码
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 原有内容;
    1. 定义临时字符 char ch , 通过 in.get(ch) 读取字符 , 跳过开头的空白字符(while (isspace(ch)) in.get(ch); );
    1. 循环读取非空白字符 , 调用 s.push_back(ch) 追加到 s , 直到读取到空白字符或流结束;
    1. 返回 in。
  • 与标准库的关联:对应 std::istream& operator>>(std::istream&, std::string&) , 行为一致(默认跳空白) , 但不支持读取带空格的字符串(需用 getline )。
cpp 复制代码
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() 清空原有内容;
    1. 定义临时字符 char ch , 通过 is.get(ch) 循环读取字符;
    1. 若读取到的字符不是 delim 且流未结束 , 调用 str.push_back(ch) 追加;若读取到 delim , 终止循环(不将 delim 加入 str );
    1. 返回 is。
  • 与标准库的关联:对应 std::getline(std::istream&, std::string&, char) , 功能和参数完全一致 , 是读取带空格字符串的核心接口。
cpp 复制代码
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 指向堆内存 , 保证扩展性。
    1. 接口完整性:覆盖 std::string 的核心接口(构造/析构/拷贝赋值 , 迭代器 , 容量管理 , 修改/查找/比较操作 , 流输入输出) , 使用方式与标准库高度兼容 , 降低使用成本。
    1. 安全与效率平衡:用 assert 做参数合法性检查(调试阶段防越界) , 用"拷贝并交换"实现赋值重载(简化代码且避免内存泄漏) , 全局 swap 复用成员函数(保证效率)。
    1. 依赖与关联:依赖 <iostream> (流操作) , <string.h> (C字符串函数) , <assert.h> (调试检查) , 成员函数间高度复用(如 operator+= 调用 push_back / append , 比较运算符复用 strcmp 和 == ) , 逻辑清晰且减少冗余。

2. 实现文件---string.cpp

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
	{
		// kmp
		const 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"  -> 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);
	}

	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 初始化

cpp 复制代码
const size_t string::npos = -1; 
  • 分析:
  • 正确性:npos 是 string 类的"无效位置"标记 , 用 -1 初始化(因 size_t 是无符号类型 , -1 会被解释为该类型的最大值) , 符合 C++ 标准库 std::string 的设计 , 后续 find , erase 等接口用它判断"未找到"或"删除至末尾" , 逻辑正确。
  • 注意:需确保头文件中已声明 static const size_t npos; , 当前实现与头文件匹配。

2. 构造函数(默认+带参)

带参构造:

cpp 复制代码
	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 留位置)。
cpp 复制代码
/*string::string()
	:_str(new char[1]{'\0'})
	,_size(0)
	,_capacity(0)
{}*/
  • 默认构造:头文件中默认构造已被注释(合并到带参构造的默认参数 const char* str = "" ) , 实现中也注释了独立默认构造 , 逻辑一致(带参构造传入空串时 , _size=0 , _capacity=0 , _str 指向长度为1的 \0 字符串) , 无问题。

3. 析构函数

cpp 复制代码
	string::~string()
	{
		delete[] _str;
		_str = nullptr;
		_size = _capacity = 0;
	}
  • 逻辑:释放 _str 指向的动态内存 , 将成员置空/置 0。
  • 正确性:无内存泄漏风险( new[] 分配的内存用 delete[] 释放) 。

4. 拷贝构造函数

第一段注释的拷贝构造函数(传统写法)

cpp 复制代码
/*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:同步原对象的字符串长度和容量。

第二段拷贝构造函数(优化写法)

cpp 复制代码
	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 成员函数 , 避免重复写"分配内存、拷贝数据、同步大小容量"的逻辑。
    1. 异常安全:如果 string tmp(s._str) 构造过程中抛异常(比如内存分配失败) , 当前对象的状态不会被破坏(因为 tmp 还没和当前对象交换)。
  • 具体逻辑:
  • string tmp(s._str) :先用原对象的 _str 构造一个临时对象 tmp (完成深拷贝)。
  • swap(tmp) :通过 swap 成员函数 , 把当前对象和 tmp 的资源( _str 、_size 、_capacity)交换------当前对象获得 tmp 的拷贝数据 , tmp 则接管当前对象原来的(可能无效或旧的)资源 , 函数结束时 tmp 析构 , 自动释放这些旧资源。

总结:

  • 第一段是传统深拷贝写法 , 逻辑直接但代码冗余;
  • 第二段是拷贝 - 交换优化写法 , 利用已有接口简化代码 , 提升异常安全性。
  • 通常实际项目里会优先选第二种优化写法 , 所以把第一段传统实现注释掉了。

5. 赋值运算符重载(operator=)

第一种:传统深拷贝写法(被注释的 operator=)

cpp 复制代码
/*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=)

cpp 复制代码
/*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 逻辑 , 无需重复写"分配、拷贝、释放"代码。

第三种 : 值传递版赋值运算符重载

cpp 复制代码
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) , 相当于"自动帮你完成深拷贝"。
    1. swap(tmp):
  • 交换当前对象(*this)和 tmp 的资源( _str , _size , _capacity 等)。交换后:
    • 当前对象获得 tmp 的新拷贝数据(完成赋值);
    • tmp 持有当前对象的旧资源,函数结束后 tmp 会自动析构(调用 ~string() 释放旧资源)。
    1. return *this :支持链式赋值(如 s1 = s2 = s3 )。

优点

  • 异常安全:
  • 若拷贝实参给 tmp 时抛异常(如内存不足) , 当前对象的状态不会被修改(因为 tmp 还没和它交换)。
  • 代码极简:
  • 无需手动写"分配内存、拷贝数据、释放旧内存" , 借助值传递和 swap 一键完成。
  • 自赋值安全:
  • 即使 s1 = s1 , 值传递会拷贝一份 s1 给 tmp , 交换后不影响逻辑(等价自交换)。

2.2 迭代器与基础访问接口

这部分接口是 string 支持"范围 for" , 随机访问的核心 , 实现完全正确 , 符合迭代器设计规范。

1. 迭代器接口(begin()/end())

cpp 复制代码
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[])

cpp 复制代码
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)

cpp 复制代码
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+=)

cpp 复制代码
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)

cpp 复制代码
void string::pop_back()
{
	assert(_size > 0);

	--_size;
	_str[_size] = '\0';
}
  • 逻辑 : assert(_size > 0)确保非空 , _size 自减后补 \0 , 简洁正确。
  • 注意:不修改 _capacity (string的容量默认只增不减 , 符合标准行为)。

3. 插入(insert : 单字符/字符串)

cpp 复制代码
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)

cpp 复制代码
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 (尾部截断 , 高效)。
    1. 否则用 memmove 挪动后续字符(memmove 支持内存重叠 , 比 memcpy 更安全 , 此处 _str + pos 和 _str + i 有重叠 , 必须用 memmove)。
  • **正确性:**无内存越界 , 逻辑完全符合预期。

5. 查找(find : 单字符/字符串)

cpp 复制代码
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
{
	// kmp
	const 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)

cpp 复制代码
	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 )

cpp 复制代码
void string::clear()
{
	_str[0] = '\0';
	_size = 0;
}
  • **逻辑:**将 _str[0] 设为 \0 , _size 置0 , 不修改 _capacity。
  • 正确性:符合标准库行为( clear 只清空内容 , 不释放容量) , 高效(无需释放内存)。

2.5 比较运算符重载

cpp 复制代码
// 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<<)

cpp 复制代码
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>>)

cpp 复制代码
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 函数

cpp 复制代码
	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 函数

cpp 复制代码
void swap(string& x, string& y)
{
	x.swap(y);
}
  • 实现:直接调用 x.swap(y) (成员函数 swap ) , 符合标准库"全局函数调用成员函数"的设计,确保交换逻辑统一(避免全局函数与成员函数逻辑不一致)。

2.3 总结

这份实现文件核心逻辑正确 , 异常安全 , 代码风格简洁 , 完全能支撑 string 的日常使用(如字符串增删查改、IO 操作、比较)。

感谢大家的观看!

相关推荐
高林雨露7 分钟前
kotlin 相关code
开发语言·kotlin
我还记得那天10 分钟前
函数的递归调用
c语言·开发语言·visualstudio
zhangfeng113311 分钟前
ThinkPHP5 事件系统的标准最佳实践 事件系统的完整设计逻辑tags.php tags.php(事件地图)
android·开发语言·php
xyq202414 分钟前
HTML 标签简写及全称
开发语言
tongluowan00715 分钟前
数据结构 Bitmap(位图)示例 - 用户签到系统
开发语言·数据结构·bitmap·用户签到系统
就叫_这个吧15 分钟前
Java线程池应用的四种方式+线程池底层实现原理
java·开发语言
Rust研习社18 分钟前
Rust 官方拟定 LLM 政策,防止 LLM 污染开源社区?
开发语言·后端·ai·rust·开源
muqsen19 分钟前
Java 分布式相关面试题总结
java·开发语言·分布式
fenglllle29 分钟前
JDK8升级JDK17使用CompletableFuture在线程中classloader的变化
java·开发语言·jvm
计算机安禾30 分钟前
【c++面向对象编程】第44篇:typename与class的区别,依赖类型名与template消除歧义
java·jvm·c++