【C++ STL】 string 类完全指南:入门STL的第一步 C STL 之 string 类完全指南入门STL的第一步哈喽大家好今天咱们来聊聊 C 里那个最熟悉的陌生人——string类。说它熟悉是因为写 C 几乎天天都在用说它陌生是因为很多人用了好几年也没真正搞懂它的底层逻辑和那些坑点。这篇文章我会带着大家从 STL 的整体框架出发把string类的构造、遍历、容量管理、修改操作、查找接口挨个讲透最后再聊聊迭代器这个灵魂设计。开篇前的叨扰string类得益于它底层逻辑的简单所以我认为是学习STL的第一步。但string接口多达上百个本文只挑选了对后续学习、使用有一定意义的接口对于其他接口我们要习惯于查文档再使用本文对涉及到的接口也都提供了文档通道还请放心食用。目录一、先聊聊 STL 到底是个啥二、string 类一个不算 STL 却胜似 STL的存在三、string 对象的四种常见构造方式四、怎么访问和遍历 string三种方式任你选4.1 下标访问operator[]4.2 迭代器访问begin / end 与 rbegin / rend4.3 三种遍历方式大比拼4.4 顺带聊聊 auto 和范围 for五、容量与大小别再傻傻分不清 size 和 capacity六、字符串修改操作尾插很香中间插入慎用七、查找与子串find 家族大盘点八、那些好用的外部函数operator 和 getline九、迭代器到底是个啥聊聊它的设计哲学十、总结一、先聊一聊 STL 到底是个啥STL全称Standard Template Library标准模板库是 C 标准库的重要组成部分。它不仅仅是一个可复用的组件库更是一个包罗数据结构与算法的软件框架。说白了STL 把程序员日常需要的容器、“算法”、迭代器这些东西都封装好了咱们拿来就能用不用重复造轮子。STL 历经发展迭代到第 4 个版本最后一版是SGI 版被 GLinux 环境下的编译器采用咱们后续学习也主要以这个版本为主。STL 有六大组件咱们先混个脸熟组件作用容器Container存放数据的地方比如 string、vector、list 等算法Algorithm操作容器中数据的函数比如 sort、find 等迭代器Iterator连接容器和算法的桥梁提供统一的访问方式仿函数Functor行为类似函数的对象用于定制算法逻辑配接器Adapter对容器或仿函数进行包装改变其接口空间配置器Allocator负责内存分配与管理底层是内存池技术这六大组件里容器是咱们日常接触最多的而string类就是容器中非常特殊的一员。二、string 类一个不算 STL 却胜似 STL的存在这里有个很有意思的点需要强调string 类其实并不属于 STL。为啥这么说呢因为string类的诞生比 STL 还要早它是 C 发展过程中历史遗留的产物。后来 STL 出现后string被重新设计具备了 STL 的众多特性比如迭代器、统一的接口风格但旧的设计和新的设计相互叠加导致它的接口十分冗杂。另外为了兼容 C 语言C 中关于字符串的函数在string里都可以使用字符串末尾会自动补\0作为终止符很多接口设计也都考虑到了\0的存在。小贴士虽然 string 不算严格意义上的 STL 容器但它的使用方式和 STL 容器高度一致学习 string 也是学习 STL 容器的最佳入门。在使用string类之前必须包含头文件并引入命名空间#includestringusingnamespacestd;官方文档参考string - C Reference三、string 对象的四种常见构造方式创建string对象有四种常见方式咱们挨个来看构造函数功能说明string()重点构造空的 string 对象即空字符串string(const char* s)重点用 C 风格字符串来构造 string 对象string(size_t n, char c)构造一个包含 n 个字符 c 的 string 对象string(const string s)重点拷贝构造函数来段代码感受一下voidTestString(){string s1;// 构造空的 string 对象 s1strings2(hello bit);// 用 C 格式字符串构造 s2strings3(10,a);// 构造包含 10 个 a 的 s3strings4(s2);// 拷贝构造 s4}这四个构造函数里前三个和拷贝构造是重中之重面试也经常考建议大家记牢。四、怎么访问和遍历 string三种方式任你选4.1 下标访问operator[]最直观的访问方式就是用[]下标运算符它返回 pos 位置的字符引用。string shello;couts[0]endl;// 输出 hs[0]H;// 修改第一个字符需要注意的是operator[]有两个版本非 const 版本返回char可读可写const 版本返回const char只读官方文档string::operator[] - C Reference4.2 迭代器访问begin / end 与 rbegin / rend迭代器是 STL 的通用访问接口string也提供了完整的迭代器支持。正向迭代器begin()获取第一个字符的迭代器end()获取最后一个字符下一个位置的迭代器尾后迭代器string shello;for(string::iterator its.begin();it!s.end();it){cout*it ;}重要提醒在迭代器层面不能认为end()就是指向\0的迭代器。end()只是一个尾后迭代器它指向的位置恰好是\0而已。C 标准并没有规定这个位置一定存\0。\0只是std::string额外为c_str()单独维护的产物这个咱们后面讲c_str()时再细说。反向迭代器rbegin()等价于--end()指向最后一个有效元素rend()等价于--begin()指向第一个元素前面的位置尾后标记反向迭代器用于反向遍历区间规则依然是左闭右开[rbegin, rend)只不过遍历方向反过来了。string shello;for(string::reverse_iterator its.rbegin();it!s.rend();it){cout*it ;// 输出 olleh}4.3 三种遍历方式大比拼遍历 string 有三种方式咱们来对比一下遍历方式写法特点for 循环 下标for (size_t i 0; i s.size(); i)最直观支持随机访问迭代器遍历for (auto it s.begin(); it ! s.end(); it)容器通用接口所有 STL 容器都支持范围 forfor (auto e : s)语法最简洁底层就是迭代器string shello;// 方式一下标遍历for(size_t i0;is.size();i)couts[i] ;// 方式二迭代器遍历for(string::iterator its.begin();it!s.end();it)cout*it ;// 方式三范围 forfor(autoe:s)coute ;性能结论这三种遍历方式在性能上基本一致范围 for 底层会被编译器替换成迭代器的写法所以不用纠结性能选自己写着舒服的就行。4.4 顺带聊聊 auto 和范围 for既然提到了范围 for咱们就顺便把auto关键字也讲了因为它俩经常一起出现。auto 是什么auto是一个类型说明符意为自动推导类型。推导过程发生在编译期和模板实例化是一个道理。autox10;// x 被推导为 intautoy3.14;// y 被推导为 doubleautoits.begin();// it 被推导为 string::iteratorauto 的使用规则和坑点auto 声明的变量必须立刻初始化——这和const、引用一样因为编译器需要根据初始值来推导类型。auto 不能修饰函数参数但可以做返回值类型不过要慎用坑多。auto 不能声明数组。用auto声明指针时auto和auto*没有任何区别但声明引用时必须加。同一行声明多个变量时这些变量必须是相同类型否则编译器报错——因为编译器实际只对第一个变量进行类型推导然后用这个类型定义其他变量。auto 的唯一用途缩短代码、自动化类型、省力。别把它想得太神秘它就是个懒人神器。范围 for 的语法for(autoe:array)e*2;// 修改元素for(autoe:array)coute ;// 只读元素范围 for 适用于容器和数组冒号左边是迭代变量右边是被迭代的范围。坑点提醒用范围 for 遍历时如果不用auto即不用引用那么拿到的是元素的拷贝无法实际修改原容器中的元素。需要修改时一定要加引用。范围 for 的底层就是迭代器编译器会把它替换成迭代器的写法。对于数组底层是数组的迭代器模仿指针行为而对于string它的迭代器本质上就是原生指针的重命名。五、容量与大小注意增缩容的理解这一块是很多初学者的重灾区size()、length()、capacity()这几个函数经常搞混。咱们一个个来理清楚。函数功能说明size()返回有效字符长度不包括\0length()和size()完全相同以后推荐只用size()capacity()返回当前空间总大小包括\0empty()检测字符串是否为空为空返回 trueclear()清空有效字符size 变为 0capacity 不变reserve(n)为字符串预开空间不包括\0resize(n, c)将有效字符个数改成 n 个多出的空间用字符 c 填充官方文档sizecapacityreserveresizesize vs capacitysize()是实际存了多少字符capacity()是总共能存多少字符包括已经用了的打个比方你买了一个 100ml 的杯子倒了 30ml 水。那么capacity就是 100ml杯子容量size就是 30ml实际水量。reserve(n) 的细节reserve(n)的作用是预开空间但它有一些微妙的行为当n capacity时扩容capacity 会增大到至少 n当n capacity时发出一个不具有约束力的请求去缩小容量是否生效取决于编译器实现VS 和 G 的差异坑点VS当n size时缩小请求不被允许生效G当n size时10版本以前请求会生效发生capacity size字符串长度不变但空间缩小二者在新版本ncapacity 时都直接return不再缩容了所以写跨平台代码时不要依赖reserve的缩容行为另外reserve有一个保证机制n 必须比 size 大才有可能生效也就是说reserve永远不会改变 string 的size它只影响capacity。resize(n, c) 的细节resize改变的是有效字符的个数size如果n size多出的空间用字符c填充默认\0可能触发扩容如果n size字符串会被截断但不会缩容capacity 不变总结一下reserve管的是 capacity容量resize管的是 size有效长度。别搞反了六、字符串修改操作尾插很香中间插入慎用6.1 尾插操作push_back / append / 往字符串末尾添加内容有三种方式函数功能push_back(char c)在字符串末尾插入单个字符不太常用append(const char* s)在字符串末尾追加字符串底层调用push_back或append最常用string shello;s.push_back(!);// 尾插单个字符s.append( world);// 尾插字符串s!!!;// 最常用的写法使用建议日常开发中是最常用的写法简洁且功能强大。这三个操作都可以自动扩容不用担心空间不够。6.2 中间插入insertinsert可以在任意位置插入字符或字符串支持头插和指定位置插入。string shello;s.insert(0,AAA);// 在位置 0 插入结果为 AAAhello但是insert 不建议频繁使用原因有二插入操作需要挪动数据类似顺序表插入一位后面的都要往后搬可能触发扩容进一步影响效率官方文档insert - C Reference6.3 删除eraseerase用于删除字符串中的字符同样涉及挪动数据效率不高。string shello world;s.erase(5,6);// 从位置 5 开始删除 6 个字符结果为 hello官方文档erase - C Reference6.4 替换replacereplace可以把字符串中的某一段替换成另一段底层就是删除 插入的组合所以效率也不高。string shello world;s.replace(6,5,bit);// 把位置 6 开始的 5 个字符替换为 bit结果为 hello bit官方文档replace - C Reference效率总结尾插操作效率最高因为不需要挪动数据中间插入、删除、替换都涉及数据挪动效率较低不建议在循环中频繁使用。七、查找与子串find 家族大盘点string 提供了一整套查找接口咱们来挨个认识一下。7.1 find 和 rfind函数功能find从前往后查找字符或字符串找到第一次出现就停一次找一个rfind从后往前查找字符或字符串找到第一次出现就停即最后一次出现的位置string shello world hello;size_t pos1s.find(hello);// 返回 0第一次出现的位置size_t pos2s.rfind(hello);// 返回 12最后一次出现的位置7.2 find_first_of 家族这几个函数名字长得很像咱们对比着记函数功能find_first_of查找给定字符串里的任意字符第一次出现就停find_last_of逻辑上倒着执行find_first_offind_first_not_of查找不是给定字符串内字符的第一个位置find_first_of的逻辑反find_last_not_offind_last_of的逻辑反string shello world;// 查找 aeiou 中任意字符第一次出现的位置size_t poss.find_first_of(aeiou);// 返回 1e 的位置官方文档find_first_of - C Reference其他查找函数可以在左侧目录中找到。7.3 find nposfind函数如果找不到目标会返回string::npos这是一个特殊的静态成员常量表示无效位置。string shello;size_t poss.find(world);if(posstring::npos)cout没找到endl;官方文档npos - C Reference7.4 c_str和 C 语言兼容的桥梁c_str()返回一个const char*指针指向一个 C 风格字符串以\0结尾。string shello;constchar*strs.c_str();// 得到 C 风格字符串printf(%s\n,str);// 可以直接用 C 语言的函数操作还记得咱们前面说的吗\0是std::string额外为c_str()单独维护的产物。也就是说string 底层为了兼容 C 语言专门在有效字符后面维护了一个\0但这个\0不计入size()只计入capacity()。官方文档c_str - C Reference7.5 substr获取子串substr(pos, n)从字符串的 pos 位置开始截取 n 个字符然后将其返回。string shello world;string subs.substr(6,5);// 从位置 6 开始截取 5 个字符结果为 world官方文档substr - C Reference八、那些好用的外部函数operator 和 getline8.1 operator字符串拼接operator被重载为外部函数而不是成员函数这是一个很有意思的设计。为啥要做成外部函数呢因为这样可以实现一个包袱对称支持支持string 字符串也支持字符串 string如果做成成员函数那么字符串 string这种写法就不支持了因为左边必须是 string 对象。string s1hello;string s2world;string s3s1 s2;// 支持 string C字符串string s4hello s2;// 也支持 C字符串 string官方文档operator - C Reference8.2 getline按行读取getline用于从输入流中读取一行字符串它可以自己控制终止符。默认终止符是换行符\n或\0可以自定义终止符自定义后只以自定义符号作为终止符string s;// 默认按换行符读取getline(cin,s);// 自定义终止符为 #getline(cin,s,#);为啥需要 getline因为cin s遇到空格就会停止而getline可以读取整行包括空格非常实用。官方文档getline - C Reference九、迭代器到底是个啥聊聊它的设计哲学文章最后咱们来升华一下聊聊迭代器这个 STL 的灵魂设计。9.1 迭代器的本质迭代器iterator的设计本质是一种封装。每个容器都有自己的迭代器它的作用是屏蔽底层的实现细节——你不用管容器底层是数组、链表还是树提供统一的接口类型——对不同容器可以用迭代器实现相似的访问方式说白了迭代器就是容器和算法之间的桥梁。有了迭代器sort、find这些算法就不用为每个容器单独写一份实现了只要操作迭代器就行。9.2 string 里的迭代器在string类里因为底层是数组所以迭代器就是原生指针只不过换了个名字而已。// string 的迭代器本质上就是 char*string::iterator its.begin();// 等价于 char* it s._str;9.3 迭代器的分类迭代器类型读写权限iterator普通迭代器可读可写const_iteratorconst 迭代器只读不能写reverse_iterator反向迭代器反向遍历const_reverse_iteratorconst 反向迭代器只读反向遍历反向迭代器的底层实现比较巧妙涉及到适配器的设计思想咱们这里先不展开后面学习其他容器时再细说。9.4 为什么迭代器这么重要因为迭代器是 STL泛型编程思想的核心体现。它让数据结构和算法解耦容器负责存数据算法负责操作数据迭代器负责连接两者这种设计使得 STL 的扩展性极强——你新增一个容器只要提供符合规范的迭代器所有 STL 算法立刻就能用在你的容器上不用改任何算法代码。这就是 STL 的魅力所在~十、总结好啦这篇文章到这里就接近尾声了咱们来快速回顾一下重点STL 六大组件容器、算法、迭代器、仿函数、配接器、空间配置器string 不算严格的 STL但使用方式和 STL 容器高度一致是学习 STL 的最佳入门四种构造函数空构造、C字符串构造、n个字符构造、拷贝构造三种遍历方式下标、迭代器、范围 for性能基本一致size vs capacitysize 是有效长度capacity 是总容量reserve管容量resize管长度修改操作尾插效率最高中间插入/删除/替换涉及数据挪动慎用查找家族find、rfind、find_first_of等找不到返回nposc_str返回 C 风格字符串\0是为它单独维护的迭代器STL 的灵魂屏蔽底层细节提供统一接口是容器和算法之间的桥梁学习 string 只是 STL 的第一步后面还有 vector、list、map、set 等一大堆容器等着咱们。但只要把 string 吃透了理解了迭代器的设计思想后面学其他容器就会事半功倍。如果这篇文章对你有帮助别忘了点赞收藏关注三连~ 咱们下篇文章见参考文档cplusplus.com - string