本篇详细介绍string底层实现的代码原理,以及相关的内容知识与写法讲解。
废话不多说,发车!!
1.头文件介绍
1.类定义和成员变量
class string { public: typedef char* iterator; typedef const char* const_iterator; private: char* _str; size_t _size; size_t _capacity; char _buff[16]; public: static const size_t npos; };主要功能模块:
类型别名定义:定义迭代器类型,便于进行字符串遍历
成员变量:
_str:指向动态分配字符串内存的指针
_size:字符串当前长度
_capacity:字符串容量
_buff[16]:小字符串优化(SSO)的缓冲区,当字符串长度小于16时使用栈内存静态常量:
npos表示无效位置,通常是-1或最大size_t值
扩展:
情况 1:短字符串(SSO 启用)
std::string s = "hello";
- 长度 = 5 ≤ 15
- 存在栈上
_buff[16]- 没有堆分配
- 速度极快
情况 2:长字符串(SSO 关闭)
std::string s = "this is a very long string more than 15 chars";
- 长度 > 15
- 用堆指针
_ptr分配内存- 会调用
malloc/free2. 迭代器接口
iterator begin(); iterator end(); const_iterator begin() const; const_iterator end() const;功能说明:提供迭代器支持,允许使用范围for循环遍历字符串,如
for (char c : str)3.构造函数、析构函数和拷贝控制
string(const char* str = ""); ~string(); string(const string& s); string& operator=(string s); void swap(string& s);
构造函数:从C风格字符串构造,支持默认构造(空字符串)
析构函数:释放动态分配的内存
拷贝构造函数:实现深拷贝
拷贝赋值运算符:采用拷贝并交换技术
交换函数:高效交换两个字符串的内容
4. 容量和访问操作
const char* c_str() const; size_t size() const; char& operator[](size_t i); const char& operator[](size_t i) const; void reserve(size_t n);功能说明:
c_str():返回C风格字符串指针
size():返回字符串长度
operator[]:支持读写和只读访问字符串元素
reserve():预留容量,避免频繁重新分配内存
5. 字符串修改操作
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); void clear();主要功能模块:
追加操作:
push_back()、append()、operator+=(),在字符串末尾添加字符或字符串删除操作:
pop_back()删除最后一个字符,erase()删除指定范围的字符插入操作:在指定位置插入字符或字符串
清空操作:
clear()清空字符串内容
6. 查找和子串操作
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;功能说明:
查找操作:
find()查找字符或子串的位置,返回找到的位置或npos子串操作:
substr()提取子字符串
7. 比较运算符
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;功能说明:实现完整的比较运算符,支持字符串的字典序比较
8. 流操作符和全局函数
ostream& operator<<(ostream& out, const string& s); istream& operator>>(istream& in, string& s); istream& getline(istream& is, string& str, char delim ); void swap(string& x, string& y);功能说明:
流操作符:
operator<<用于输出,operator>>用于输入getline():读取一行字符串
全局swap():交换两个字符串对象
2.函数实现
1.构造函数和析构函数
string::string(const char* str) :_size(strlen(str)) { _capacity = _size; _str = new char[_size + 1]; //size没有包含/0的空间大小 memcpy(_str, str, _size + 1); } string::~string() { delete[]_str; _str = nullptr; _size = _capacity = 0; } /*string::string(const string& s) { _str = new char[s._capacity + 1]; memcpy(_str, s._str, s._size + 1); _size = s._size; _capacity = s._capacity; }*/ string::string(const string& s) { string tmp(s._str); swap(tmp); }功能说明:
从C风格字符串构造字符串对象
计算输入字符串长度并赋值给
_size设置容量等于当前长度
分配足够的内存(长度+1用于空终止符)
复制字符串内容,包括空终止符
功能说明:
析构函数:释放动态分配的内存,并将指针置空,大小和容量归零
拷贝构造函数:有两种实现方式
第一种(被注释掉):传统深拷贝方式
第二种:采用"拷贝并交换"技术,先创建临时对象,然后交换内容,确保异常安全
复习:
函数 功能 特点 核心区别 memcpy 内存拷贝 不处理重叠 目标和源不能重叠 memmove 内存拷贝 处理重叠 万能拷贝,最安全 memset 内存设置 按字节赋值 常用于清 0 memcmp 内存比较 按字节比较 比较前 n 个字节
int a[5] = {1,2,3,4,5}; int b[5]; // 把 a 的 20个字节(5个int) 拷贝到 b memcpy(b, a, 5 * sizeof(int)); // 内存重叠场景(memcpy会错,memmove一定对) char arr[] = "abcdef"; memcpy(arr+2, arr, 3); // 可能错 memmove(arr+2, arr, 3); // 一定对 int arr[10]; memset(arr, 0, 10 * sizeof(int)); // 全变0,只能设置0或-1 int a[] = {1,2}; int b[] = {1,3}; memcmp(a, b, sizeof(a)); // 返回负数
2. 交换和迭代器函数
void string::swap(string& s) { std::swap(_str, s._str); std::swap(_size, s._size); std::swap(_capacity, s._capacity); } 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; }功能说明:
swap函数:交换两个字符串的所有成员,使用了
std::swap迭代器函数:
begin()和end():返回普通迭代器,指向字符串的开始和结束位置
begin() const和end() const:返回const迭代器,用于const对象支持STL风格的迭代器访问
扩展:
函数类型 写法 来源 效率 核心原理 适用场景 1. 标准库通用 std::swapstd::swap(a, b);<utility>慢(需拷贝数据) 1. 创建临时变量2. 拷贝赋值3. 数据量大时很慢 int、double 等基础类型 2. 成员函数 string::swapa.swap(b);类内部实现 极快(O (1) 常数时间) 只交换内部指针、大小、容量不拷贝任何数据 string、vector、list等容器交换首选3. 智能查找 ADL swapusing std::swap;``swap(a, b);智能匹配 智能择优 优先调用自定义高效版本没 有则用 std::swap通用模板代码(最规范写法)
3. 基本访问和容量函数
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():返回C风格字符串指针
size():返回字符串长度
operator[]:支持读写访问字符串中的字符,带边界检查
operator[] const:只读访问,用于const对象
4. 内存管理和容量调整
void string::reserve(size_t n) { if (n > _capacity) { char* tmp = new char[n + 1]; memcpy(tmp, _str, _size + 1); delete[]_str; _str = tmp; _capacity = n; } }功能说明:
扩容函数:增加字符串的容量
当**
n > _capacity**时执行扩容创建新的更大数组,复制原内容,释放旧数组
更新指针和容量值
扩展:
开辟方式 释放方式 正确 / 错误 new intdelete p✅ new int[10]delete[] p✅ new int[10]delete p❌ 泄漏 栈变量 delete p❌ 崩溃
5. 字符串修改操作
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); } 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'; }功能说明:
push_back:在字符串末尾添加单个字符
检查容量是否足够,不足时按指数增长策略扩容
添加字符,更新大小,确保以空字符结尾
append:在字符串末尾追加C风格字符串
计算追加字符串的长度
动态扩容,选择合适的新容量**// 新容量取:2倍扩容(小) 或 直接装下(大)**
复制字符串内容,更新大小
operator+=:重载
+=运算符,复用push_back和appendpop_back:删除最后一个字符,减少大小,添加空终止符
6. 插入和删除操作
1.插入单个字符
string& string::insert(size_t pos, char ch) { assert(pos <= _size); if (_size >= _capacity) { size_t newcapacity = _capacity == 0 ? 4 : 2 * _capacity; reserve(newcapacity); } size_t end = _size + 1; while (end > pos) { _str[end] = _str[end - 1]; --end; } _str[pos] = ch; ++_size; return *this; }功能说明:
字符插入:在指定位置插入单个字符
检查边界,确保插入位置有效
必要时扩容
从后向前移动字符,腾出插入位置
插入字符,更新大小
2.插入字符串
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); } 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; }功能说明:
1.字符串插入:在指定位置插入字符串
2.计算插入字符串长度
3.检查并调整容量****向后移动字符块
4.复制插入的字符串****更新大小
3.删除
string& string::erase(size_t pos, size_t len) { assert(pos < _size); 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; }功能说明:
删除子串:从指定位置开始删除指定长度的字符
检查边界
如果删除到末尾,直接截断
否则移动内存,覆盖要删除的部分
更新大小
7. 查找和子串操作
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 pos; } size_t string::find(const char* str, size_t pos) const { const char* p1 = strstr(_str + pos, _str); if (p1 == nullptr) { return npos; } else { return p1 - _str; } }功能说明:
查找字符:从指定位置开始查找字符
顺序遍历字符串,找到返回位置,找不到返回
npos(这里代码有误,应该是return npos而不是return pos)查找子串:使用
strstr函数查找C风格子串找到返回指针差,找不到返回
npos
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]; } return ret; }功能说明:
提取子串:从字符串中提取指定位置开始的指定长度的子串
处理默认长度(
npos)的情况创建新字符串对象
预留足够空间
逐个字符复制
返回新字符串
8. 流操作符
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 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; }功能说明:
输出流重载:重载
<<操作符,支持向输出流输出字符串逐个字符输出到流中
输入流重载:重载>>操作符,支持向输入流输出字符串
逐个字符输入到流中
扩展:
为什么用
char buff[128]而不是动态数组?
**栈分配速度快:在函数栈上分配,**无需动态内存管理
无内存泄漏风险:函数结束时自动释放
缓存友好:小数组通常能完全放入CPU缓存
9. 比较运算符
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 { 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; }功能说明:
小于比较:实现字符串的字典序比较
逐个字符比较,遇到不同字符时返回比较结果
如果所有字符都相同,较短的字符串被认为更小
等于比较:比较两个字符串是否完全相同
逐个字符比较,如果长度不同或字符不同则返回false
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
{
return !(*this == s);
}
10. 输入流和交换函数
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); }功能说明:
getline函数:从输入流读取一行(或到指定分隔符)
清空目标字符串
使用缓冲区分批读取,提高效率
读取直到遇到分隔符(默认为换行符)
处理缓冲区剩余内容
全局swap函数:调用成员函数
swap
3.全局代码
1.头文件
#pragma once #include<iostream> #include<assert.h> #include<string.h> using namespace std; namespace wxx { 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=(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; size_t _size; size_t _capacity; // _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); }
2.源文件
#include"string.h" namespace wxx { const size_t string::npos = -1; string::string(const char* str) :_size(strlen(str)) { _capacity = _size; _str = new char[_size + 1]; memcpy(_str, str, _size + 1); } string::~string() { delete[]_str; _str = nullptr; _size = _capacity = 0; } /*string::string(const string& s) { _str = new char[s._capacity + 1]; memcpy(_str, s._str, s._size + 1); _size = s._size; _capacity = s._capacity; }*/ string::string(const string& s) { cout << "string::string(const string& s)" << endl; string tmp(s._str); swap(tmp); } void string::swap(string& s) { std::swap(_str, s._str); std::swap(_size, s._size); std::swap(_capacity, s._capacity); } 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) { char* tmp = new char[n + 1]; 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); } 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); } 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); } 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); 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 pos; } size_t string::find(const char* str, size_t pos) const { 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]; } return ret; } 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 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; } 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 { 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 || *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 { return !(*this == s); } 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); } };

