【C++指南】string(三):basic_string底层原理与模拟实现详解

【C++指南】string(三):basic_string底层原理与模拟实现详解
.💓 博客主页:倔强的石头的ZEEKLOG主页
📝Gitee主页:倔强的石头的gitee主页
⏩ 文章专栏:《C++指南》
期待您的关注

文章目录

引言

前文中,我们深入探讨了C++标准库中basic_string的成员变量、默认成员函数及常用操作。
本文作为系列第三篇,将结合模拟实现的代码,逐行解析basic_string的底层原理,涵盖构造函数、拷贝控制、容量管理、修改操作等核心功能的实现细节与优化技巧
通过手写一个简化版string类,帮助读者彻底理解std::string的内部工作机制。

一、成员变量与内存管理

1.1 核心成员变量

标准库的basic_string通过三个核心变量管理字符串:

  • 字符指针_str:指向动态分配的字符数组。
  • 当前长度_size:字符串有效字符个数(不含\0)。
  • 总容量_capacity:当前内存可容纳的最大字符数(含\0)。

模拟实现代码

namespace xc {classstring{private:char* _str;// 字符存储指针 size_t _size;// 有效字符数 size_t _capacity;// 总容量(含\0)public:staticconst size_t npos =-1;// 特殊标记};}

1.2 内存分配策略

  • 默认构造:初始化为空字符串(_str指向\0)。注意不能初始化为nullptr,否则调用c_str时,就会对空指针解引用
  • 动态扩容:当_size达到_capacity时,按2倍或需求大小扩容,避免频繁内存分配。

构造函数实现

// 默认构造(支持传入C字符串) string::string(constchar* str):_size(strlen(str)){ _str =newchar[_size +1];// 多分配1字节存放\0strcpy(_str, str); _capacity = _size;// 初始容量等于长度}

二、默认成员函数的实现与优化

2.1 拷贝构造函数

传统写法需要手动分配内存并拷贝数据,而现代C++写法通过“构造临时对象 + 交换资源”简化代码:
(关于swap函数的实现可跳转6.5查找)

// 传统写法(易错且冗余) string::string(const string& s){ _str =newchar[s._capacity +1];strcpy(_str, s._str); _size = s._size; _capacity = s._capacity;}// 现代写法(利用临时对象) string::string(const string& s){ string tmp(s._str);// 调用构造函数swap(tmp);// 交换资源}

2.2 赋值运算符重载

通过**“拷贝构造临时对象 + 交换”**避免自赋值问题,同时减少重复代码:

//传统写法 string& string::operator=(const string& s){if(this!=&s){delete[] _str; _str =newchar[s._capacity +1];strcpy(_str, s._str); _size = s._size; _capacity = s._capacity;}return*this;}// 优化版赋值重载 string& string::operator=(const string& s){if(this!=&s){// 防止自赋值 string tmp(s);// 调用拷贝构造swap(tmp);// 交换资源}return*this;}

2.3 析构函数

释放动态内存并将成员变量归零:

string::~string(){delete[] _str;// 释放堆内存 _size =0; _capacity =0;}

三、迭代器与元素访问

3.1 迭代器实现

模拟原生指针的行为,提供begin()end()

using iterator =char*; iterator begin(){return _str;} iterator end(){return _str + _size;}

3.2 运算符重载

通过operator[]提供随机访问,并使用assert检查越界:

char&operator[](size_t i){assert(i < _size);// 越界检查return _str[i];}

四、容量管理

4.1 reserve:预分配内存

若需求容量大于当前容量,重新分配内存并拷贝数据:

void string::reserve(size_t n){if(n > _capacity){char* tmp =newchar[n +1];strcpy(tmp, _str);delete[] _str;// 释放旧内存 _str = tmp; _capacity = n;// 更新容量}}

4.2 resize:调整字符串长度

根据新长度截断或填充字符:

void string::resize(size_t n,char c){if(n < _size){ _str[n]='\0';// 截断 _size = n;}else{reserve(n);// 确保容量足够for(size_t i = _size; i < n;++i){ _str[i]= c;// 填充字符} _size = n; _str[_size]='\0';}}

五、修改操作

5.1 清空字符串:clear

清空字符串内容但不释放内存(保留容量):

void string::clear(){ _str[0]='\0';// 首字符置为结束符 _size =0;// 长度归零}

5.2 push_back与append

  • 尾插字符:检查扩容后直接写入:
void string::push_back(char c){if(_size == _capacity){reserve(_capacity ==0?4:2* _capacity);} _str[_size++]= c; _str[_size]='\0';}
  • 追加字符串:计算长度后扩容并拷贝:
void string::append(constchar* str){ size_t len =strlen(str);if(_size + len > _capacity){reserve(_size + len);// 按需扩容}strcpy(_str + _size, str);// 直接拷贝 _size += len;}

5.3 insert与erase

  • 插入字符:移动后续字符腾出位置:
string& string::insert(size_t pos,char c){assert(pos <= _size);if(_size == _capacity)reserve(2* _capacity); size_t end = _size +1;while(end > pos){// 从后向前移动 _str[end]= _str[end -1]; end--;} _str[pos]= c; _size++;return*this;}
  • 删除字符:覆盖后续字符并更新长度:
string& string::erase(size_t pos, size_t len){assert(pos < _size);if(len == npos || len > _size - pos){ _str[pos]='\0'; _size = pos;}else{strcpy(_str + pos, _str + pos + len);// 覆盖删除区域 _size -= len;}return*this;}

六、其他关键函数实现

6.1 查找函数:find

查找字符
size_t string::find(char c, size_t pos)const{assert(pos < _size);for(size_t i = pos; i < _size;++i){if(_str[i]== c)return i;}return npos;// 未找到返回特殊标记}
查找子串

利用标准库的strstr函数优化子串查找:

size_t string::find(constchar* s, size_t pos)const{assert(pos < _size);constchar* ptr =strstr(_str + pos, s);// 直接调用C库函数return ptr ? ptr - _str : npos;}

6.2 子串生成:substr

截取从pos开始的len个字符生成新字符串:

string string::substr(size_t pos, size_t len)const{assert(pos <= _size); len =(len == npos)? _size - pos : len;// 默认取到末尾 len = std::min(len, _size - pos);// 防止越界 string result; result.reserve(len);// 预分配内存for(size_t i =0; i < len;++i){ result += _str[pos + i];// 逐字符追加}return result;}

6.3 流运算符重载

流插入(operator<<

直接遍历输出有效字符:

ostream&operator<<(ostream& os,const xc::string& s){for(size_t i =0; i < s.size();++i){ os << s[i];// 支持链式调用}return os;}
流提取(operator>>

优化版输入,通过缓冲区减少扩容次数:

istream&operator>>(istream& is, xc::string& s){ s.clear();// 清空原内容char buff[256];// 局部缓冲区char ch;int idx =0;while(is.get(ch)&&!isspace(ch)){ buff[idx++]= ch;if(idx ==255){// 缓冲区满时批量追加 buff[idx]='\0'; s += buff; idx =0;}}if(idx >0){// 处理剩余字符 buff[idx]='\0'; s += buff;}return is;}

6.4 比较运算符重载

等于与不等于
bool string::operator==(const string& s)const{returnstrcmp(_str, s._str)==0;// 直接比较C字符串}bool string::operator!=(const string& s)const{return!(*this== s);// 复用等于运算符}
大小比较
bool string::operator<(const string& s)const{returnstrcmp(_str, s._str)<0;// 字典序比较}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);}

6.5 交换函数:swap

高效交换两个字符串的资源(避免深拷贝):

void string::swap(string& s){ std::swap(_str, s._str);// 交换指针 std::swap(_size, s._size);// 交换长度 std::swap(_capacity, s._capacity);// 交换容量}

七、性能优化与注意事项

  1. substr的优化
    • 避免直接使用newstrcpy,通过reserve预分配内存减少扩容次数。
    • 若需要高性能,可实现“浅拷贝+引用计数”(需处理写时复制逻辑)。
  2. find的局限性
    • 当前实现为暴力匹配,标准库可能使用更高效的算法(如KMP)。
  3. 流提取的安全性
    • 缓冲区大小固定为256,若输入过长可能丢失数据,可动态调整缓冲区大小。
  4. swap的优势
    • 仅交换指针和元数据,时间复杂度为O(1),适合频繁交换场景。

结语

通过手写string类,我们深入理解了basic_string的底层机制。标准库的实现在此基础上进行了大量优化(如SSO、内存池),但核心逻辑与本文的模拟实现高度一致。掌握这些原理后,读者可以更高效地使用std::string,并能在需要时定制自己的字符串类。

相关阅读

关注博主,第一时间获取更新!

Read more

Flutter 三方库 wasm_ffi 深入鸿蒙端侧硬核 WebAssembly 虚拟机沙盒穿透适配全景:通过异步极速 FFI 中继管道打通底层高算力异构服务-适配鸿蒙 HarmonyOS ohos

Flutter 三方库 wasm_ffi 深入鸿蒙端侧硬核 WebAssembly 虚拟机沙盒穿透适配全景:通过异步极速 FFI 中继管道打通底层高算力异构服务-适配鸿蒙 HarmonyOS ohos

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net Flutter 三方库 wasm_ffi 深入鸿蒙端侧硬核 WebAssembly 虚拟机沙盒穿透适配全景:通过异步极速 FFI 中继管道打通底层高算力异构服务并全面实现无损语言壁垒交互 前言 在 OpenHarmony 应用向高性能计算领域扩展的过程中,如何优雅地接入已有的 C/C++ 算法库(如加密引擎、重型图像处理、数学模拟)而又不失跨平台的便捷性?传统的 NAPI 虽然稳健,但在 Flutter 生态中,直接利用 WebAssembly (WASM) 配合 FFI(External Function Interface)的语义可以在一定程度上实现代码的高度复用。wasm_ffi 库为 Flutter 开发者提供了一套在 Dart 环境下调用 WASM

By Ne0inhk
三种适用于Web版IM(即时通讯)聊天信息的加密算法实现方案

三种适用于Web版IM(即时通讯)聊天信息的加密算法实现方案

文章目录 * **第一部分:引言与核心密码学概念** * **1.1 为什么IM需要端到端加密(E2EE)?** * **1.2 核心密码学概念与工具** * **第二部分:方案一:静态非对称加密(基础方案)** * **2.1 方案概述与流程** * **2.2 前端Vue实现(使用node-forge)** * **1. 安装依赖** * **2. 核心工具类 `crypto.js`** * **3. Vue组件中使用** * **2.3 后端Java实现(Spring Boot)** * **1. 实体类** * **2. Controller层** * **3. WebSocket配置** * **2.4 密钥管理、注册与登录集成** * **1. 用户注册/登录时生成密钥** * **2. 密钥设置页面** * **2.

By Ne0inhk
前端代码生成的大洗牌:当 GLM 4.7 与 MiniMax 挑战 Claude Opus,谁才是性价比之王?

前端代码生成的大洗牌:当 GLM 4.7 与 MiniMax 挑战 Claude Opus,谁才是性价比之王?

在 AI 辅助编程领域,长期以来似乎存在一条不成文的铁律:如果你想要最好的结果,就必须为最昂贵的模型买单(通常是 Anthropic 或 OpenAI 的旗舰模型)。然而,随着国产大模型如 GLM 4.7 和 MiniMax M2.1 的迭代,这一格局正在发生剧烈震荡。 最近,一场针对Claude Opus 4.5、Gemini 3 Pro、GLM 4.7 和 MiniMax M2.1 的前端 UI生成横向测评,打破了许多人的固有认知。在这场包含落地页、仪表盘、移动端应用等五个真实场景的较量中,不仅出现了令人咋舌的“滑铁卢”,更诞生了性价比极高的“新王”。 本文将深入拆解这场测试的细节,透过代码生成的表象,探讨大模型在工程化落地中的真实效能与成本逻辑。

By Ne0inhk
【Java Web学习 | 第14篇】JavaScript(8) -正则表达式

【Java Web学习 | 第14篇】JavaScript(8) -正则表达式

🌈个人主页: Hygge_Code🔥热门专栏:从0开始学习Java | Linux学习| 计算机网络💫个人格言: “既然选择了远方,便不顾风雨兼程” 文章目录 * JavaScript 正则表达式详解 * 什么是正则表达式🤔 * JavaScript 正则表达式的定义与使用🥝 * 1. 字面量语法 * 2. 常用匹配方法 * test() 方法🍋‍🟩 * exec() 方法🍋‍🟩 * 正则表达式的核心组成部分🐦‍🔥 * 1. 元字符 * 边界符 * 量词 * 字符类 * 2. 修饰符 * 简单示例🍂 JavaScript 正则表达式详解 正则表达式是处理字符串的强大工具,在 JavaScript 中被广泛应用于表单验证、文本处理和数据提取等场景。本文将从正则表达式的基本概念出发,详细介绍其语法规则和实际应用方法。 什么是正则表达式🤔 正则表达式是用于匹配字符串中字符组合的模式,在 JavaScript

By Ne0inhk