0基础手撕从0搭建string核心底层逻辑(附实现代码)2.1 底层构造逻辑string类的成变量与构造逻辑string 的底层本质上是靠三个成员变量支撑先搭建好基本类框架再逐步实现功能新手也能一步步自己手撕出一个基本的string类。string.h代码语言javascriptAI代码解释#pragma once #includeiostream #includeassert.h #includealgorithm #includestring.h using namespace std; namespace Lotso { class string { public: string(const char* str ); string(const string s); //先实现这个是为了在还没实现cout前方便测试打印观察 const char* c_str()const { return _str; } string operator(const string s); ~string(); void resize(size_t n, char c \0); void reserve(size_t n); size_t size()const { return _size; } size_t capacity()const { return _capacity; } private: char* _str; size_t _capacity; size_t _size; public: //这里比较特殊const static整型可以这么用特殊处理 //当然也可以声明和定义分离 const static size_t npos -1; //const static double npos-1;//这个是不行的 }; };2.1.1 构造与析构对象的 “创建” 与 “销毁”构造函数负责为 string 对象分配初始内存初始化状态析构函数则在对象生命周期结束时回收动态分配的内存避免泄漏。同时拷贝构造和赋值重载要实现深拷贝确保多个对象间内存的独立这个在之前类和对象中讲过这里也会把链接放上大家如果不理解这里深浅拷贝区别的一定要去看看当然在后续的创作中博主也会再详细解析一下这个问题的。参考往期博客《吃透 C 类和对象中拷贝构造函数与赋值运算符重载深度解析》-CSDN博客代码演示(注意看注释)string.h代码语言javascriptAI代码解释public: string(const char* str ); string(const string s); string operator(const string s); ~string();string.cpp代码语言javascriptAI代码解释//构造 string::string(const char* str)//.h里面给缺省值 :_size(strlen(str)) { //_size strlen(str);//这个写里面也可以 //初始化列表初始化顺序跟声明顺序有关 //所以这里写在函数体里比较好可以灵活使用size _str new char[_size 1];//多开一个给\0; _capacity _size; strcpy(_str, str); } //析构 string::~string() { delete[] _str; _str nullptr; _size 0; _capacity 0; } //拷贝构造深拷贝避免内存共享 //string s2(s1); string::string(const string s) { _str new char[s._capacity 1]; //strcpy(_str, s._str);//这里可以用strcpy但是memcpy更好 memcpy(_str, s._str, s._size 1); _size s._size; _capacity s._capacity; } //赋值运算符重载先释放旧内存再深拷贝新内容 //s1s3 string string::operator (const string s) { if (this ! s) { char* tmp new char[s._capacity 1]; //strcpy(_str,s._str); //这里用memcpy是因为处理串里中间有\0的情况 memcpy(tmp, s._str, s._size 1); delete[] _str; _str tmp; _size s._size; _capacitys._capacity; } return *this; }--这里需要注意拷贝构造和赋值运算符重载都采用了深拷贝的方式即新对象会分配独立的内存空间来存储字符串内容而不是简单的直接复制指针这样可以避免多个对象共享同一块内存导致的重复释放等问题。2.1.2 c_str与辅助接口兼容C风格c_str 函数的作用是返回string对象内部存储的C风格字符串(以\0结束),方便与C语言的字符串处理进行交互。string.h代码语言javascriptAI代码解释public: // 返回C风格字符串方便兼容C语言接口 const char* c_str() const { return _str; } // 获取有效字符数 size_t size() const { return _size; } // 获取容量 size_t capacity() const { return _capacity; }2.1.3 容量管理resize和reserve的协同使用reserve 用于提前预留内存空间避免频繁扩容resize 则用于调整字符串的有效长度在需要时还会调用 reserve 进行扩容还可以指定填充字符。string.h代码语言javascriptAI代码解释public: void resize(size_t n, char c \0); void reserve(size_t n);string.cpp代码语言javascriptAI代码解释// 预留内存空间只改变容量不改变有效字符数 void string::reserve(size_t n) { if (n _capacity) { char* tmp new char[n 1]; //strcpy(tmp, _str); memcpy(tmp, _str, _size 1); delete[] _str; _str tmp; _capacity n; } } // 调整有效字符长度可指定填充字符 void string::resize(size_t n, char ch) { if (n _size) { //删除保留前n个 _size n; _str[_size] \0; } else { reserve(n); for (size_t i _size; i n; i) { _str[i] ch; } _size n; _str[_size] \0; } }--当 resize 的目标长度 n 超过当前容量时会调用 reserve 来扩容可以指定字符(默认为’\0‘)填充新的位置最后更新有效字符个数 _size 并在末尾补上\02.1.4 代码测试验证当前模块功能test.cpp代码语言javascriptAI代码解释namespace Lotso { void test_string1() { string s1; cout s1.c_str() endl; string s2(Hello Lotso); cout s2.c_str() endl; s2[0] h; for (size_t i 0; i s2.size(); i) { s2[i]; } cout s2.c_str() endl; string s3 hello world;//隐式类型转换构造拷贝构造-优化为构造 string s4(hello world); string s5; s5.resize(100, *); cout s5.c_str() endl; s5.resize(10); cout s5.c_str() endl; s5.resize(20, #); cout s5.c_str() endl; } }; int main() { try { Lotso::test_string1(); /* cout typeid(Lotso::string::iterator).name() endl; cout typeid(std::string::iterator).name() endl;*/ } catch (const exception e) { cout e.what() endl; } return 0; }补充测试这个运行结果没放出来--测试结果符合预期退出码为0该模块功能可以正常使用。2.2 迭代器与下标string遍历的两大高效工具2.2.1 迭代器的基本框架与实现迭代器是遍历容器元素的抽象机制对于string,可以通过封装指针来实现简单迭代器结合下标访问可以覆盖不同遍历场景。string.h代码语言javascriptAI代码解释typedef char* iterator; typedef const char* const_iterator; // iterator iterator begin() { return _str; } const_iterator begin() const { return _str; } iterator end() { return _str _size; } const_iterator end() const { return _str _size; }--这里将迭代器 typedef 为 char*begin 函数返回指向字符串起始位置的指针end 函数返回指向字符串有效字符结尾的下一个位置(\0所在的位置)的指针这样就可以利用指针的算术运算和解引用操作来实现迭代器的功能。2.2.2 operator[] 的底层逻辑与实现下标访问是string最常用的操作之一通过重载operator 可以像访问数组一样操作string的字符底层本质是对 _str 指针的索引访问同时也需要确保访问不会越界(这个可以加断言)string.h代码语言javascriptAI代码解释public char operator[](size_t index) { assert(index _size); return _str[index]; } const char operator[](size_t index)const { assert(index _size); return _str[index]; }operator 支持两种核心场景读取字符和修改字符配合循环可实现字符串遍历比迭代器更直观关键说明两个版本的重载非const版本返回char支持修改字符如s1iconst版本返回const char仅允许读取用于const对象。越界检查通过assert(pos_size)在调试阶段拦截越界访问 Release 模式下断言会失效若需严格检查可改为抛异常。与迭代器的对比operator 更适合已知索引的场景如随机访问第i个字符迭代器更适合范围遍历两者底层都是通过指针访问内存效率一致。2.2.3 代码测试验证当前模块功能test.cpp代码语言javascriptAI代码解释namespace Lotso { void test_string2() { string s2(Hello Lotso); cout s2.c_str() endl; s2[0] h; for (size_t i 0; i s2.size(); i) { s2[i]; } cout s2.c_str() endl; string s4(hello world); const string s5(hello Lotso); for (size_t i 0; i s5.size(); i) { //s5[i];不可以写但可以读 cout s5[i] -; } cout endl; for (auto ch : s4) { cout ch ; } cout endl; string::iterator it4 s4.begin(); while (it4 ! s4.end()) { *it4 1; cout *it4 ; it4; } cout endl; for (auto ch : s5) { cout ch ; } cout endl; string::const_iterator it5 s5.begin(); while (it5 ! s5.end()) { //*it51;//这个不行 cout *it5 ; it5; } cout endl; } }; int main() { try { //Lotso::test_string1(); Lotso::test_string2(); /* cout typeid(Lotso::string::iterator).name() endl; cout typeid(std::string::iterator).name() endl;*/ } catch (const exception e) { cout e.what() endl; } return 0; }--测试结果符合预期退出码为0该模块功能可以正常使用。2.3 字符串修改push_backappendinsert与的实现字符串的修改操作是string的核心功能push_back用于尾插单个字符append用于追加字符串insert支持指定位置插入但是三者的底层实现都需要处理内存扩容和确保高效的操作.