STL string 实用攻略:打造优质 C++ 代码
引言:STL
C++ 标准模板库:STL 是 C++ 编程语言中的一个重要库,提供了一系列通用的模板类和函数,用于实现常见的数据结构和算法。
STL中的容器可以分为三类: 序列容器:
vector
:动态数组,支持快速随机访问。list
:双向链表,支持快速插入和删除。deque
:双端队列,支持在两端快速插入和删除。array
:固定大小的数组(C++11 引入)。forward_list
:单向链表(C++11 引入)。
关联容器:
set
:存储唯一元素的集合,按值排序。map
:存储键值对的集合,按键排序。multiset
:允许重复元素的集合,按值排序。multimap
:允许重复键的键值对集合,按键排序。
无序关联容器:
unordered_set
:存储唯一元素的集合,基于哈希表。unordered_map
:存储键值对的集合,基于哈希表。unordered_multiset
:允许重复元素的集合,基于哈希表。unordered_multimap
:允许重复键的键值对集合,基于哈希表。
特殊的容器适配器:
stack
:后进先出(LIFO)的栈。queue
:先进先出(FIFO)的队列。priority_queue
:优先级队列,元素按优先级排序。(默认小堆)。
1.0.string
1.1.c语言字符串的局限性
char数组的局限性,如固定大小、手动管理内存等。 1.1.1固定大小 char数组在声明时需要指定固定大小,且大小在编译时确定。如果字符串长度超过数组大小,会导致缓冲区溢出,引发未定义行为或安全问题。
代码语言:javascript代码运行次数:0运行复制char str[10] = "Hello"; // 只能容纳最多9个字符 + '\0'
1.1.2手动分配内存 char数组需要开发者手动分配和释放内存,尤其是在动态分配时(如使用malloc和free)忘记释放内存会导致内存泄漏,而重复释放则可能引发程序崩溃。
代码语言:javascript代码运行次数:0运行复制char *str = (char *)malloc(20 * sizeof(char));
strcpy(str, "Dynamic memory");
free(str); // 必须手动释放
1.1.3手动扩容 char数组的大小是固定的,无法动态扩展。如果需要存储更长的字符串,必须手动重新分配内存(如使用realloc),这增加了代码的复杂性。
代码语言:javascript代码运行次数:0运行复制char *str = (char *)malloc(10 * sizeof(char));
str = (char *)realloc(str, 20 * sizeof(char)); // 手动扩展
这时候C++就有了string,string容器可以解决上面的各种问题。
2.0.string
string为我们提供了很多的接口。
2.1.迭代器
STL给容器提供了迭代器(iterator)
,使得开发者可以像操作容器一样遍历和操作字符串。迭代器是一种抽象的概念,它提供了对字符串中字符的访问方式,类似于指针
的行为(但并不是所有的容器都是指针)。
2.1.1.begin()和end()函数
在 string 中,begin()
和 end()
函数是获取迭代器的基本方式。begin() 函数返回一个指向字符串第一个字符的迭代器
,而 end() 函数返回的迭代器则指向字符串末尾的下一个位置
(在 C 风格字符串中对应于 ‘\0’ 的位置,但在 string 中并不实际存储 ‘\0’)。
使用:
int main() {
string s = "hello";
// 获取字符串的起始迭代器
string::iterator it = s.begin();
// 使用 for 循环通过迭代器遍历字符串
for (it = s.begin(); it != s.end(); it++)
{
cout << *it;
}
cout << std::endl;
// 使用 while 循环通过迭代器遍历字符串
it = s.begin();
while (it != s.end())
{
cout << *it;
it++;
}
cout << std::endl;
return 0;
}
2.1.2.rbegin()和end()
除了正向遍历,string 还提供了反向遍历
的方式,即通过 rbegin()
和rend()
函数。rbegin() 函数返回一个指向字符串最后一个字符的反向迭代器,而 rend() 函数返回的反向迭代器则指向字符串第一个字符的前一个位置。
int main() {
string s = "hello";
string::reverse_iterator it = s.rbegin();
// 使用 for 循环通过迭代器反向遍历字符串
for (it = s.rbegin(); it != s.rend(); it++)
{
cout << *it;
}
cout << endl;
// 使用 while 循环通过迭代反向器遍历字符串
it = s.rbegin();
while (it != s.rend())
{
cout << *it;
it++;
}
cout << endl;
return 0;
}
2.2.string的构造函数和析构函数
2.2.1.构造函数
代码语言:javascript代码运行次数:0运行复制1. 默认构造函数:构造一个为空的字符串。 2. 拷贝构造函数:拷贝一个str的副本。 3. 子字符串构造函数:创建一个新字符串,该字符串是现有字符串(str)的子字符串,从位置pos开始,长度为len。如果未指定len,则默认为npos,通常表示“直到字符串的末尾”。 4. 从C字符串构造函数:用C-string来构造string类对象 5. 从序列构造函数 :字符数组(s)的前n个字符创建一个新字符串。 6. 填充构造函数:创建一个包含n个字符c的新字符串。 7. 范围构造函数:从由迭代器first和last定义的字符范围创建一个新字符串。
#include<string> //需要包含头文件才可以使用string
int main()
{
string s("hello world!");
//string s= "hello world!";
const char* cstr = "Hello, C++!";
string str1; // 使用默认构造函数创建一个空字符串
string str2(s);// 使用拷贝构造函数
string str3(s,6,6);// 从位置6开始,长度为6的子字符串
string str4(cstr);// 从C字符串创建
string str5(cstr, 5); // 从C字符串的前5个字符创建
string str6(10, 'x'); // 创建包含10个'x'字符的字符串
string str7(str6.begin(), str6.end()); // 从string str6的范围创建字符串
cout << "str1: \"" << str1 << "\"" << endl;
cout << "str2: \"" << str2 << "\"" << endl;
cout << "str3: \"" << str3 << "\"" << endl;
cout << "str4: \"" << str4 << "\"" << endl;
cout << "str5: \"" << str5 << "\"" << endl;
cout << "str6: \"" << str6 << "\"" << endl;
cout << "str7: \"" << str7 << "\"" << endl;
return 0;
}
2.2.2析构函数
当调用结束后,sting会自动调用~string析构函数
2.3.string类对象的容量操作
下面是一些容量接口
函数名 | 功能描述 |
---|---|
size | 返回字符串的长度 |
length | 返回字符串的长度 |
max_size | 返回字符串的最大尺寸 |
resize | 调整字符串的大小 |
capacity | 返回已分配存储的大小 |
clear | 清空字符串 |
empty | 测试字符串是否为空 |
shrink_to_fit(C++11) | 收缩以匹配实际使用的大小 |
int main()
{
string s("Hello World");
cout << s.size() << endl;//字符串长度
cout << s.length() << endl;//字符串长度
cout << s.capacity() << endl;//空间大小
cout << s.max_size() << endl;//最大容量
return 0;
}
2.3.1.string的扩容函数capacity
capacity在不同的编译器下扩容方式不同。
代码语言:javascript代码运行次数:0运行复制int main()
{
string s;
size_t sz = s.capacity();//默认开十六
cout << "Initial capacity: " << sz << '\n';
cout << "making s grow:\n";
for (int i = 0; i < 100; ++i)
{
s.push_back('c');
if (sz != s.capacity())
{
sz = s.capacity();
cout << "capacity changed: " << sz << '\n';
}
}
return 0;
}
在vs2022下。
可以看到,初始容量为15
,加上终止符\0
,实际分配了16个字符的空间
。,当字符串长度超过当前容量时,容量会按一定比例增加。观察到的容量变化是:15 -> 31 -> 47 -> 70 -> 105。这些变化表明,容量是按大约1.5倍
的比例增长的。
在Linux的g++下。
因编译器的不同,Linux下是呈指数增长。
代码语言:javascript代码运行次数:0运行复制扩容的两种方式 第一种:n > capacity 第二种:n < capacity 在不同的编译器下还是不一样的。
int main()
{
string s("Hello world! Hello C++");
cout << s.size() << endl;
cout << s.capacity() << endl << endl;
s.reserve(28);//n > capacity
cout << s.size() << endl;
cout << s.capacity() << endl << endl;
s.reserve(40);//n < capacity
cout << s.size() << endl;
cout << s.capacity() << endl << endl;
s.clear();
cout << s.size() << endl;
cout << s.capacity() << endl << endl;
return 0;
}
clear() 只会将字符串的长度 size() 设置为 0,不会改变 capacity()。
vs2022下。
当s.size() < capacity时,进行1.5倍扩容。 当s.size() > capacity时,不会进行任何操作。
在Linux下。
当s.size() < capacity时,进行两倍扩容。 当s.size() > capacity时,会进行缩容,但不会影响字符串。
2.3.2reserve和resize 虽然reserve(),resize()都有扩容的意思,但整体的使用差别还是有点打的。
resize resize的核心功能是改变字符串的实际长度。当新长度比原长度大时,它会在字符串末尾填充指定字符(默认是’\0’);若新长度小于原长度,字符串就会被截断。 reserve reserve主要负责为字符串预先分配内存空间,设定字符串的最小容量。它不会改变字符串的长度和内容,只是给后续添加字符预留足够空间,减少内存重新分配的开销。
代码语言:javascript代码运行次数:0运行复制void StringTest12()
{
string str = "example";
// reserve操作
cout << "reserve操作前:" << endl;
cout << "字符串内容: " << str << endl;
cout << "字符串长度: " << str.size() << endl;
cout << "字符串容量: " << str.capacity() << endl;
str.reserve(15);
cout << "reserve操作后:" << endl;
cout << "字符串内容: " << str << endl;
cout << "字符串长度: " << str.size() << endl;
cout << "字符串容量: " << str.capacity() << endl;
// resize操作
cout << "\nresize操作前:" << endl;
cout << "字符串内容: " << str << endl;
cout << "字符串长度: " << str.size() << endl;
cout << "字符串容量: " << str.capacity() << endl;
str.resize(10, '!');
cout << "resize操作后:" << endl;
cout << "字符串内容: " << str << endl;
cout << "字符串长度: " << str.size() << endl;
cout << "字符串容量: " << str.capacity() << endl;
}
resize 能改变 std::string 实际长度,内容也会随之变动 。比如变长就补字符,变短则截断。而 reserve 主要用来规划容量,优化内存,不影响字符串内容和长度。
2.4.string访问操作
函数名 | 功能描述 |
---|---|
operator | 获取字符串中的字符 |
at | 获取字符串中的字符 |
back | 访问字符串的最后一个字符 |
front | 访问字符串的第一个字符 |
2.4.1.operator[]和at
这两个函数都是返回pos位置的字符,类型是const char&,这也方便了修改。
代码语言:javascript代码运行次数:0运行复制void StringTest1()
{
string s("Hello World!");
for (int i = 0; i < s.size(); i++)
{
cout << s[i] << " ";
//s[i] = 'a';可以进行修改
}
cout << endl;
for (int i = 0; i < s.size(); i++)
{
cout << s.at(i) << " ";
//s.at(i);可以进行修改
}
}
输出结果: H e l l o W o r l d ! H e l l o W o r l d !
operator[]和at函数几乎一模一样,唯一的不同点就是,at()函数会进行边界检查
。
2.4.2.back和front
back放回字符串的最后一个字符,front放回字符串的第一个字符。
代码语言:javascript代码运行次数:0运行复制void StringTest2()
{
string s("Hello World");
cout << s.front() << " ";
cout << s.back() << " ";
}
输出结果: H d
2.5.string的修改操作
函数名 | 功能描述 |
---|---|
operator+= | 向字符串追加内容 |
append | 向字符串追加内容 |
push_back | 向字符串追加一个字符 |
insert | 向字符串中插入内容 |
assign | 为字符串分配内容 |
swap | 交换两个字符串的值 |
copy | 从字符串中复制字符序列 |
replace | 替换字符串的一部分 |
erase | 从字符串中删除字符 |
pop_back(C++11) | 删除字符串的最后一个字符 |
2.5.1.operator+=
void StringTest3()
{
string s1("This");
string s2("is");
s1 += s2; //追加一个string类型的字符串
s1 += "cpp.exe";//追加C风格字符串
s1 += '!';//追加一个字符
}
operator+= 是string 中用于追加内容的核心操作符,支持追加字符串
、C 风格字符
串和单个字符
。
2.5.2.append和push_back
void StringTest4()
{
string s;
s.append("Hello"); // "Hello"
s.append(", "); // "Hello, "
s.append("World"); // "Hello, World"
s.append(1, '!'); // "Hello, World!"
s.append(s);
s.append(" Have a nice day!"); // "Hello, World!Hello, World! Have a nice day!"
s.push_back('!'); //尾插一个字符!
}
insert
void StringTest7()
{
string str = "Hello, World!";
string str1 = "C++ ";
string str2 = "12345";
str.insert(7, str2); // 在位置 7 插入 "C++ "
str.insert(11, "haha", 0, 5); // 在位置 11 插入 "haha"
str.insert(16, " good!"); // 在位置 16 插入 "good"
str.insert(16, "one", 8); // 在位置 16 插入 "one "
str.insert(24, 3, '!'); // 在位置 24 插入 3 个 '!'
str.insert(str.begin() + 5, '!'); // 在位置 5 插入 '!'
str.insert(str.begin() + 10, str2.begin(), str2.end());
cout << str << endl;
}
2.5.3.assign和replace
void StringTest8()
{
string str = "Hello, World!";
// 使用 assign 赋值
str.assign("C++ Programming"); // 用 C 风格字符串赋值
cout << "After assign: " << str << endl; // 输出 "C++ Programming"
// 使用 replace 替换
str.replace(4, 11, "is fun!"); // 从位置 4 开始,替换 11 个字符为 "is fun!"
cout << "After replace: " << str << endl; // 输出 "C++ is fun!"
// 再次使用 assign 赋值
str.assign(10, '*'); // 用 10 个 '*' 赋值
cout << "After assign with fill: " << str << endl; // 输出 "**********"
// 再次使用 replace 替换
str.replace(5, 2, "C++"); // 从位置 5 开始,替换 2 个字符为 "C++"
cout << "After replace: " << str << endl; // 输出 "*****C++**"
}
2.5.4.swap和copy
void StringTest5()
{
string s1("Hello World!");
string s2("Hello Shawn!");
cout << "交换前:" << endl;
cout << "s1:" << s1 << endl;
cout << "s2:" << s2 << endl;
s1.swap(s2);
cout << "交换后:" << endl;
cout <<"s1:" << s1 << endl;
cout << "s2:" << s2 << endl;
// 目标字符数组
char buffer[20];
// 从s2的第 6 个字符开始,复制 5 个字符到 buffer
size_t sz = s2.copy(buffer, 5, 6);
buffer[sz] = '\0';
cout << buffer << endl;
}
string里的swap
和算法库里的swap
交换逻辑是不一样的,库里面的是一个值一个值的进行交换,string::swap 是成员函数,直接交换
两个字符串的内部指针
和数据
,效率非常高(常数时间复杂度)。
2.5.5.erase和pop_back
void StringTest6()
{
string s ("Hello, World!");
// 序列删除,删除"World",结果为"Hello, !"。
s.erase(7, 5);
// 删除单个字符,删除',',结果为"Hello
s.erase(s.begin() + 5);
// 删除字符范围,删除' '后的所有字符,结果为"Hello"
s.erase(s.begin() + 5, s.end());
s.pop_back();//尾删
}
2.6.string的其他操作
函数名 | 功能描述 |
---|---|
find | 在字符串中查找内容 |
rfind | 在字符串中查找内容 |
find_first_of | 在字符串中查找指定字符 |
find_last_of | 从字符串末尾开始查找指定字符 |
find_first_not_of | 在字符串中查找不存在的指定字符 |
find_last_not_of | 从字符串末尾开始查找不匹配的字符 |
substr | 生成子字符串 |
compare | 比较字符串 |
2.6.1.find和rfind
void StringTest8()
{
string s1("Hello world!");
string s2("ll");
size_t a = s1.find(s1,0);//找单个字符,不传默认从0开始找。
a = s1.find("Hello");//找字符串,不传默认从0开始找
a = s1.find("world!", 4, 6);//找字符串,从4开始找,最多6个字符
a = s1.find('H', 0);//找单个字符,不传默认从0开始找。
}
find
和 rfind
的功能非常相似,主要区别在于查找方向:find
从字符串开头向末尾查找(从左到右
),而 rfind
从字符串末尾向开头查找(从右到左
。如果找到目标
,它们返回
匹配的子字符串或字符的起始位置
;如果未找到
,则返回 string::npos
。find 返回第一次匹配的位置,而 rfind 返回最后一次匹配的位置。这两个函数非常适合用于查找子字符串或字符的位置。
2.6.2.find_first_not_of、find_first_of、find_last_not_of和find_last_of 下面这四个函数用法几乎一样,只解释一种。
void StringTest10()
{
string str("Please, replace the vowels in this sentence by asterisks.");
cout << str << '\n';
size_t found = str.find_first_not_of("abcdef");
while (found != string::npos)
{
str[found] = '*';
found = str.find_first_not_of("abcdef", found + 1);
}
cout << str << '\n';
}
find_first_not_of放回不是子字符串中的字符,而find_first_of则是相反的,放回第一个是子字符串的第一个位置。而find_last_not_of是从右到左。
2.6.3.substr
void StringTest11()
{
string s("hello world!");
size_t pos = s.find('w');
//从下标6开始截取长度为6的字符串
string str = s.substr(pos, 6);
cout << str << endl;
}
3.0.auto和范围for
auto:是c++11重新引入的,用来自动识别变量的类型。 而范围for是一种新的东西
代码语言:javascript代码运行次数:0运行复制语法:
for (auto element : container) {
// 对element进行操作
}
这个范围的for循环遍历string的底层原理是基于迭代器实现的。 当使用基于范围的for循环遍历string时,编译器会将其转换为等价的传统for循环,并在幕后调用string类的begin()和end()成员函数。begin()函数返回一个指向string中第一个字符的迭代器,end()函数返回一个指向字符串末尾字符下一个位置的迭代器,这个迭代器通常被称为 “超出末端迭代器”,用于标记字符串的结束位置。
代码语言:javascript代码运行次数:0运行复制void StringTest13()
{
string s("Hello World!");
for (auto x : s)
{
cout << x;
}
cout << endl;
}
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。 原始发表:2025-04-24,如有侵权请联系 cloudcommunity@tencent 删除stlstring函数字符串c++