跳到主要内容C++ STL 与 string 类详解:从基础概念到增删查改操作 | 极客日志C++算法
C++ STL 与 string 类详解:从基础概念到增删查改操作
系统梳理了 C++ STL 标准模板库的基础概念及 string 类的核心用法。内容涵盖 STL 的定义、版本历史及六大组件,重点解析了 string 类的构造函数、析构函数、迭代器遍历、容量管理、数据访问及增删查改操作。通过代码示例展示了字符串分割、比较、赋值、替换及查找等功能,并对比了 operator[] 与 at 方法的越界处理差异。旨在帮助开发者掌握高效安全的字符串处理技巧,优化内存管理与代码效率。
FlinkHero0 浏览 C++ STL 与 string 类详解
在 C++ 编程中,对字符串的处理是极为常见的需求。STL(标准模板库)中的 string 类作为专门用于字符串管理的工具,封装了丰富的功能,能高效实现字符串的创建、遍历、增删查改等操作。本文将从 STL 的基础概念入手,深入剖析 string 类的使用细节,助力开发者熟练掌握这一实用工具。
STL
定义
STL(Standard Template Library),即标准模板库的一部分,IO 流不包含 STL。STL 是 C++ 标准库的重要组成部分,不仅是一个可复用的组件库,而且是一个包含数据结构与算法的软件框架。
版本
原始版本
Alexander Stepanov、Meng Lee 在惠普实验室完成的原始版本,本着开源精神,他们声明允许任何人任意运用、拷贝、修改、传播、商业使用这些代码,无需付费。唯一的条件就是也需要向原始版本一样做开源使用。HP 版本 - 所有 STL 实现版本的始祖。
P. J. 版本
由 P. J. Plauger 开发,继承自 HP 版本,被 Windows Visual C++ 采用,不能公开或修改。
缺陷:可读性比较低,符号命名比较怪异。
RW 版本
由 Rouge Wage 公司开发,继承自 HP 版本,被 C++ Builder 采用,不能公开或修改,可读性一般。
SGI 版本
由 Silicon Graphics Computer Systems, Inc 公司开发,继承自 HP 版本。被 GCC(Linux) 采用,可移植性好,可公开、修改甚至贩卖,从命名风格和编程风格上看,阅读性非常高。
六大组件
- 容器
容器是用于存储和管理数据的类模板,也就是数据结构,分为顺序容器、关联容器和容器适配器。
顺序容器
vector:动态数组,支持随机访问,尾部插入/删除效率高。
deqque:双端队列,支持头尾高效插入/删除。
list:双向链表,支持任意位置高效插入/删除。
关联容器
set/multiset:基于红黑树实现,元素自动排序,set 不允许重复,multiset 允许。
map/multimap:键值对存储,基于红黑树,map 键唯一,multimap 键可重复。
- 算法
STL 提供了超过 100 种算法,涵盖排序、查找、遍历、修改等操作,通过迭代器与容器解耦。
- 迭代器
迭代器是一种抽象的指针,用于遍历容器中的元素,提供统一的访问接口。
- 仿函数
仿函数(也称函数对象)是重载了 operator() 的类或结构体,可作为算法的参数。
- 配接器
配接器是一种特殊的函数对象或容器,用于修改其他组件的接口。
空间配接器
空间配接器是负责管理容器内存的组件,允许自定义内存分配策略。
string 库
C++ 官网
定义
string 是 C++ 标准库(std)中用于处理字符串的类,封装了字符串的存储、操作等功能,string 类中含有字符串的增删查改和算法,用来管理字符数组。类似的,string 不带 .h。
构造函数
常用的定义方式
#include <iostream>
#include <string>
using namespace std;
int {
string s1;
;
;
;
;
}
微信扫一扫,关注极客日志
微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
相关免费在线工具
- 加密/解密文本
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
- Base64 文件转换器
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
- Markdown 转 HTML
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML 转 Markdown 互为补充。 在线工具,Markdown 转 HTML在线工具,online
- HTML 转 Markdown
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML 转 Markdown在线工具,online
- JSON 压缩
通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online
main
()
string s2("hello world")
string s3(10, '*')
string s4(s2)
return
0
这些是经常要用到的定义方式,如果遇到不会的,建议读者多查文档。
#include <iostream>
#include <string>
using namespace std;
int main(){
string s1("hello world");
string s2(s1, 6, 5);
cout << s2 << endl;
return 0;
}
npos
npos 是缺省参数,也是 string 类中的静态成员变量,它的值是无符号整型的最大值。如果 npos 的值比较大或者没有,则字符串取到结尾。
#include <iostream>
#include <string>
using namespace std;
int main(){
string s1;
string s2("张三");
cout << (s1 == s2) << endl;
cout << (s1 < s2) << endl;
cout << (s1 > s2) << endl;
return 0;
}
0 表示 false,即条件不成立,1 表示 true,即条件成立。
比较时要带上括号,原因是流插入运算符的优先级比比较运算符优先级高。
#include <iostream>
#include <string>
using namespace std;
int main(){
string url("https://legacy.cplusplus.com/reference");
string sub1(url, 0, 5);
string sub2(url, 8, 20);
string sub3(url, 29, url.size() - 29);
cout << sub1 << endl;
cout << sub2 << endl;
cout << sub3 << endl;
return 0;
}
sub1 从索引 0 开始提取 5 个字符。
sub2 从索引 8 开始(跳过 https://),提取 20 个字符。
sub3 从索引 29 开始,提取到字符串末尾(url.size() 是字符串的长度,减去前面的所有字符个数)。
#include <iostream>
#include <string>
using namespace std;
int main(){
string s1;
string s2("张三");
s1 = s2;
cout << s1 << endl;
s1 = "1111";
cout << s1 << endl;
s1 = '*';
cout << s1 << endl;
return 0;
}
析构函数
对象出了作用域会自动调用析构函数,我们不用写,编译器会自动生成。
迭代器
迭代器是像指针一样的类型,迭代器不能修改。
任何容器都支持迭代器,并且用法是类似的。
#include <iostream>
#include <string>
using namespace std;
int main(){
string s1("hello world");
string::iterator it = s1.begin();
while(it != s1.end()){
cout << *it << " ";
it++;
}
cout << endl;
return 0;
}
begin 和 end 是左闭右开的关系,即 [begin, end)。
应用场景
#include <iostream>
#include <string>
using namespace std;
int main(){
string s1("hello world");
string::iterator it = s1.begin();
while(it != s1.end()){
(*it)--;
it++;
}
it = s1.begin();
while(it != s1.end()){
cout << *it << " ";
it++;
}
cout << endl;
return 0;
}
范围 for
底层是迭代器,范围 for 只能正着遍历,不能倒着遍历。
#include <iostream>
#include <string>
using namespace std;
int main(){
string s1("hello world");
for(auto& ch : s1){
ch++;
}
for(auto ch : s1){
cout << ch << " ";
}
cout << endl;
return 0;
}
逆置
reverse 是一个函数模板。迭代器可以跟容器配合。
#include <iostream>
#include <string>
using namespace std;
int main(){
string s1("hello world");
reverse(s1.begin(), s1.end());
for(auto ch : s1){
cout << ch << " ";
}
cout << endl;
return 0;
}
#include <iostream>
#include <string>
using namespace std;
int main(){
string s1("hello world");
auto rit = s1.rbegin();
while(rit != s1.rend()){
cout << *rit << " ";
rit++;
}
cout << endl;
return 0;
}
const 迭代器
当函数传参时使用 const 应该使用 const_iterator。
普通迭代器可以读和写,const 迭代器只能读。
类似的,反向迭代器使用 const 也需要使用 const 迭代器。
#include <iostream>
#include <string>
using namespace std;
void Func(const string& s){
string::const_iterator it = s.begin();
while(it != s.end()){
cout << *it << " ";
it++;
}
}
int main(){
string s1("hello world");
Func(s1);
return 0;
}
方法 2
传参没有使用 const,则可以使用 const 也可以不使用 const 迭代器。这里是权限的缩小。
#include <iostream>
#include <string>
using namespace std;
void Func(string& s){
string::const_iterator it = s.begin();
while(it != s.end()){
cout << *it << " ";
it++;
}
}
int main(){
string s1("hello world");
Func(s1);
return 0;
}
总结
iterator 提供一种统一的方式访问和修改容器中的数据,算法就可以通过迭代器,去处理容器中的数据。
容量
size
size 用来求出字符串长度,不包括 '\0'。
\0 是表示字符串的特殊字符并且不会被打印出来。
#include <iostream>
#include <string>
using namespace std;
int main(){
string s1("hello world");
cout << s1.size() << endl;
cout << s1.length() << endl;
return 0;
}
size 和 length 的结果相同,但在 string 里面建议使用 size。
max_size
字符串可以达到的最大长度,不同编译器的结果是不一样的。
#include <iostream>
#include <string>
using namespace std;
int main(){
string s1("hello world");
cout << s1.max_size() << endl;
return 0;
}
capacity
返回当前为字符串分配的存储空间的大小,以字节表示。在不同版本下扩容的结果是不一样的。
#include <iostream>
#include <string>
using namespace std;
int main(){
string s1("hello world");
cout << s1.capacity() << endl;
size_t old = s1.capacity();
for(size_t i = 0; i < 100; i++){
s1 += 'x';
if(old != s1.capacity()){
cout << "扩容" << s1.capacity() << endl;
old = s1.capacity();
}
}
return 0;
}
clear 可以清除 size 的数据,无法清除 capacity 的数据。
reserve
开辟已知的空间,可能会开辟更大的空间,但不会比这个空间小,不会扩容。
注意
如果开辟的空间没有存储数据,可以缩小空间,缩小空间是将剩余的数据拷贝到另一份空间,然后释放掉原来所有的空间,它是以时间换空间的做法。
resize
resize 可以开空间和初始化填充数据。
访问数据
operator[]
[] 本质上是解引用,是数组用来访问元素的。
#include <iostream>
using namespace std;
int main(){
string s1;
string s2("hello world");
cout << s2 << endl;
return 0;
}
#include <iostream>
using namespace std;
int main(){
string s1;
string s2("hello world");
int i = 0;
for(i = 0; i < s2.size(); i++){
s2[i]++;
}
for(i = 0; i < s2.size(); i++){
cout << s2[i];
}
cout << endl;
return 0;
}
#include <iostream>
using namespace std;
int main(){
string s2("hello world");
char s3[]="hello world";
s2[1];
s3[1];
return 0;
}
s2[1] 是 C++ string 类的重载运算符调用,实际执行(s2.operator)。
s3[1] 是数组访问,等价于指针运算 *(s3 + 1),其中 s3 退化为指向首元素的指针。
注意
operator[] 越界会断言,at 越界是抛出异常。
#include <iostream>
#include <string>
using namespace std;
int main(){
string s1("hello world");
s1.at(15)='x';
cout << s1 << endl;
return 0;
}
#include <iostream>
#include <string>
using namespace std;
int main(){
string s1("hello world");
s1[15]='x';
cout << s1 << endl;
return 0;
}
增删查改
#include <iostream>
using namespace std;
int main(){
string s1("hello");
s1 += ' ';
s1 += "world";
cout << s1 << endl;
return 0;
}
operator+= 字符调用 push_back,operator+= 字符串调用 append。
#include <iostream>
using namespace std;
int main(){
int x = 0;
cin >> x;
string xstr;
while(x){
size_t val = x % 10;
xstr += ('0' + val);
x /= 10;
}
return 0;
}
push_back(增)
push_back 可以在字符串后面插入一个字符。
append
append 可以在字符串后面插入一个字符串。
如果空间不够,会自动扩容。
#include <iostream>
using namespace std;
int main(){
string s1("hello");
s1.push_back(' ');
s1.append("world");
cout << s1 << endl;
return 0;
}
assign
append 是在字符串后面追加,assign 是覆盖掉原来的字符串。赋值也可以覆盖掉原来的字符串。
#include <iostream>
#include <string>
using namespace std;
int main(){
string s1("hello ");
s1.append("world");
cout << s1 << endl;
s1.assign("111111111");
cout << s1 << endl;
return 0;
}
#include <iostream>
using namespace std;
int main(){
string s1("world");
s1.insert(0, "hello ");
cout << s1 << endl;
s1.insert(s1.begin() + 3, 10, 'z');
cout << s1 << endl;
return 0;
}
#include <string>
#include <iostream>
using namespace std;
int main(){
string s1("hello world");
s1.erase(5, 1);
cout << s1 << endl;
s1.erase(5);
cout << s1 << endl;
return 0;
}
#include <string>
#include <iostream>
using namespace std;
int main(){
string s1("hello world");
s1.replace(6, 5, "xxxxxxxxxx");
cout << s1 << endl;
return 0;
}
#include <string>
#include <iostream>
using namespace std;
int main(){
string s2("hello world hello world");
string s3;
for(auto ch : s2){
if(ch == ' '){
s3 += "20%";
} else {
s3 += ch;
}
}
cout << s3 << endl;
return 0;
}
查找
#include <iostream>
using namespace std;
int main(){
string url = "https://legacy.cplusplus.com/reference/string";
int ret1 = url.find("://");
if(ret1 != string::npos){
string s1(url, 0, ret1);
cout << s1 << endl;
}
int ret2 = url.find('/', ret1 + 3);
if(ret1 != string::npos){
string s2(url, ret1 + 3, ret2 - (ret1 + 3));
cout << s2 << endl;
}
string s3(url, ret2 + 1, url.length());
cout << s3 << endl;
return 0;
}
总结
通过对 STL 及 string 类的系统梳理,我们不仅掌握了容器、迭代器等核心概念,更熟悉了 string 类从构造、遍历到增删查改的全流程操作。string 类凭借封装性和便捷接口,极大简化了字符串处理工作。后续使用中,可结合实际场景灵活调用相关接口,同时注意内存管理与效率优化,让 STL 工具真正成为编程助力。