尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

C++ std::string 从使用到模拟实现,彻底吃透字符串底层

C++ std::string 从使用到模拟实现,彻底吃透字符串底层 平时写 C 我们天天在用std::string可以直接赋值、拷贝、拼接、查找子串不用手动管理堆内存用起来十分丝滑。 但很多人只会调用 API不清楚底层内存是如何管理的。本篇先梳理 string 常用用法再动手模拟实现简易 string搞懂浅拷贝、深拷贝、RAII 这些核心考点。一、std::string 的常见使用1. 对象创建#include iostream #include string using namespace std; int main() { string s1; //空字符串 string s2(hello world); //C字符串构造 string s3 c; string s4(s2); //拷贝构造 string s5 s2; //拷贝构造 return 0; }2. 基础访问与修改string str abcdef; //下标访问 cout str[0] endl; //获取有效字符长度不包含\0 cout str.size() endl; cout str.length() endl; //获取容量已经开辟的堆空间大小 cout str.capacity() endl; //尾插字符 str.push_back(g); //追加字符串 str hij; //预留空间只扩容不改变有效长度 str.reserve(100); //修改有效字符长度多余截断不足补字符 str.resize(5); //清空size置0容量不变 str.clear();3. 查找、比较、c_strstring s hello, c; size_t pos s.find(c); //查找字符返回下标找不到返回string::npos string s1(apple),s2(banana); if(s1 s2) //运算符重载按字典序比较 { } const char* p s.c_str(); //返回C风格const char*指针4. 迭代器 / 范围 forstring str test; //迭代器 for(string::iterator it str.begin(); it ! str.end(); it) { cout *it; } //范围for底层调用begin end for(auto ch : str) { cout ch; }注意npos是 string 的静态常量size_t无符号类型代表查找失败。二、为什么要模拟实现 stringstd::string 底层在堆上开辟字符数组内部维护char* _str字符数组指针size_t _size有效字符个数size_t _capacity已分配空间容量编译器默认生成的拷贝构造、赋值运算符只会做浅拷贝直接拷贝指针。多个对象指针指向同一块堆内存对象销毁时多次析构释放同一块内存程序直接崩溃。 所以我们必须手动实现深拷贝每个对象拥有独立堆内存。三、模拟实现简易 string多文件工程工程分为三个文件String.h头文件放声明String.cpp放实现Test.cpp测试。String.h#pragma once #include cstring namespace bit { class string { public: typedef char* iterator; typedef const char* const_iterator; //构造函数默认参数写在声明 string(const char* str ); //拷贝构造 string(const string s); //赋值重载 string operator(const string s); //析构 ~string(); //迭代器 iterator begin(); iterator end(); //size capacity c_str size_t size() const; size_t capacity() const; const char* c_str() const; //reserve resize void reserve(size_t n); void resize(size_t n, char ch \0); //push_back void push_back(char c); string operator(char c); //find size_t find(char c, size_t pos 0) const; private: char* _str; size_t _size; size_t _capacity; }; }String.cpp#include String.h namespace bit { //构造 string::string(const char* str) { _size strlen(str); _capacity _size; _str new char[_capacity 1]; strcpy(_str, str); } //拷贝构造 深拷贝 string::string(const string s) { _size s._size; _capacity s._capacity; _str new char[_capacity 1]; strcpy(_str, s._str); } //赋值重载 string string::operator(const string s) { if(this s) { return *this; } delete[] _str; _size s._size; _capacity s._capacity; _str new char[_capacity 1]; strcpy(_str, s._str); return *this; } //析构释放堆内存 string::~string() { delete[] _str; _str nullptr; _size _capacity 0; } char* string::begin() { return _str; } char* string::end() { return _str _size; } size_t string::size() const { return _size; } size_t string::capacity() const { return _capacity; } const char* string::c_str() const { return _str; } //reserve扩容只改变capacity不改变size void string::reserve(size_t n) { if(n _capacity) { char* tmp new char[n 1]; strcpy(tmp, _str); delete[] _str; _str tmp; _capacity n; } } //resize 修改有效字符长度 void string::resize(size_t n, char ch) { if(n _size) { _size n; _str[_size] \0; } else { reserve(n); while(_size n) { _str[_size] ch; } _str[_size] \0; } } void string::push_back(char c) { if(_size _capacity) { size_t newcap _capacity 0 ? 4 : _capacity * 2; reserve(newcap); } _str[_size] c; _str[_size] \0; } string string::operator(char c) { push_back(c); return *this; } size_t string::find(char c, size_t pos) const { for(size_t i pos; i _size; i) { if(_str[i] c) { return i; } } //无npos实际项目可以添加static const size_t npos; return -1; } }Test.cpp 测试#include String.h #include iostream using namespace std; int main() { bit::string s1(hello); cout s1.c_str() endl; cout size: s1.size() capacity: s1.capacity() endl; s1.push_back(!); s1 ; s1 w; cout s1.c_str() endl; bit::string s2 s1; //拷贝构造 bit::string s3(test); s3 s1; //赋值重载 for(auto ch : s1) { cout ch; } return 0; }四、核心知识点拆解1. 浅拷贝问题编译器默认生成拷贝构造、赋值直接复制指针。 两个对象_str指向同一块堆内存对象销毁两次delete[]同一块内存程序崩溃。解决深拷贝重新 new 堆内存拷贝内容每个对象拥有独立资源。2.reserve 与 resize 区别reserve(n)只扩容容量不开改变有效字符_size不写数据用来提前预分配空间减少扩容开销。resize(n,ch)修改有效字符长度。变短直接截断变长会扩容多出位置填充字符。3.push_back 扩容逻辑当_size _capacity空间满了需要扩容。一般 2 倍扩容。开辟新空间拷贝旧数据释放旧空间。4. 迭代器模拟typedef char* iterator;迭代器本质就是字符指针。begin()返回首地址end()返回有效字符后一位。范围 for 语法底层就是调用 begin ()、end ()。5. 析构 RAII 思想构造函数申请堆内存析构函数释放堆内存。对象生命周期结束自动回收资源不用手动 free。五、高频踩坑清单默认参数只写在头文件声明cpp 实现不能重复写否则报重定义默认参数。new 开辟字符数组必须多 1用来保存结束符\0否则字符串乱码。赋值重载一定要判断自赋值if(this s)防止自己给自己赋值把内存提前释放。new [] 一定要配对 delete []漏掉方括号内存错误。reserve 扩容顺序new 临时空间→拷贝旧内容→释放旧空间→把临时指针交给成员变量。顺序不能颠倒。六、总结std::string看着简单底层是一套完整的资源管理逻辑。 从简单的构造、拷贝构造、赋值重载、析构四大函数到扩容 reserve、resize、push_back再到迭代器全部围绕堆上字符数组做管理。
返回列表