C++ string 模板入门

🔥 星恒随风: 个人主页 ❄️ 个人专栏: 《指针合集》 | 《C语言基础》 | 《数据结构》 | 《机器学习导论》 | 《前端基础》 | 《python基础》 ✨ 数据即知识,压缩即智能
目录
- [C++ string 模板入门](#C++ string 模板入门)
- [一、什么是 STL?](#一、什么是 STL?)
-
- [1.1 STL 的全称](#1.1 STL 的全称)
- [1.2 STL 解决了什么问题?](#1.2 STL 解决了什么问题?)
- [二、STL 的版本演变](#二、STL 的版本演变)
-
- [2.1 原始版本:HP STL](#2.1 原始版本:HP STL)
- [2.2 P.J. 版本](#2.2 P.J. 版本)
- [2.3 RW 版本](#2.3 RW 版本)
- [2.4 SGI 版本](#2.4 SGI 版本)
- [三、STL 的六大组件](#三、STL 的六大组件)
-
- [3.1 容器:用来存数据](#3.1 容器:用来存数据)
- [3.2 算法:用来处理数据](#3.2 算法:用来处理数据)
- [3.3 迭代器:连接容器和算法](#3.3 迭代器:连接容器和算法)
- [3.4 仿函数:像函数一样使用的对象](#3.4 仿函数:像函数一样使用的对象)
- [3.5 适配器:换一种接口包装已有组件](#3.5 适配器:换一种接口包装已有组件)
- [3.6 空间配置器:负责内存管理](#3.6 空间配置器:负责内存管理)
- [四、STL 为什么重要?](#四、STL 为什么重要?)
-
- [4.1 刷题中很重要](#4.1 刷题中很重要)
- [4.2 面试中很重要](#4.2 面试中很重要)
- [4.3 工作中很重要](#4.3 工作中很重要)
- [五、如何学习 STL?](#五、如何学习 STL?)
-
- [5.1 第一层:能用](#5.1 第一层:能用)
- [5.2 第二层:明理](#5.2 第二层:明理)
- [5.3 第三层:能扩展](#5.3 第三层:能扩展)
- [六、为什么先学 string?](#六、为什么先学 string?)
-
- [6.1 C 语言字符串的问题](#6.1 C 语言字符串的问题)
- [6.2 string 解决了什么问题?](#6.2 string 解决了什么问题?)
- [七、string 其实是什么?](#七、string 其实是什么?)
-
- [7.1 string 是 basic_string<char>](#7.1 string 是 basic_string
) - [7.2 为什么说 string 是 STL 学习入口?](#7.2 为什么说 string 是 STL 学习入口?)
- [7.1 string 是 basic_string<char>](#7.1 string 是 basic_string
- [八、使用 string 前要包含什么?](#八、使用 string 前要包含什么?)
- 九、auto:让类型推导更轻松
-
- [9.1 auto 的新含义](#9.1 auto 的新含义)
- [9.2 auto 必须初始化](#9.2 auto 必须初始化)
- [9.3 auto 和指针、引用](#9.3 auto 和指针、引用)
- [9.4 auto 在 STL 中很好用](#9.4 auto 在 STL 中很好用)
- [十、范围 for:更自然地遍历容器](#十、范围 for:更自然地遍历容器)
-
- [10.1 普通遍历](#10.1 普通遍历)
- [10.2 范围 for 写法](#10.2 范围 for 写法)
- [10.3 修改元素时要用引用](#10.3 修改元素时要用引用)
- [十一、string 的常见构造方式](#十一、string 的常见构造方式)
-
- [11.1 构造空字符串](#11.1 构造空字符串)
- [11.2 用 C 字符串构造](#11.2 用 C 字符串构造)
- [11.3 拷贝构造](#11.3 拷贝构造)
- [11.4 构造 n 个相同字符](#11.4 构造 n 个相同字符)
一、什么是 STL?
1.1 STL 的全称
STL 的全称是:
cpp
Standard Template Library
中文一般叫:
标准模板库。
从名字上看,它有三个关键词:
- Standard:标准
- Template:模板
- Library:库
简单理解:
STL 是 C++ 标准库的重要组成部分,是一套基于泛型编程思想的数据结构与算法框架。
它不是只提供"容器",也不是只提供"算法",而是把容器、算法、迭代器、函数对象、适配器、空间配置器等组件组合成了一套完整体系。
1.2 STL 解决了什么问题?
在没有 STL 的情况下,我们经常要自己写很多基础设施。
比如:
- 动态数组
- 链表
- 栈
- 队列
- 哈希表
- 二叉搜索树
这些东西当然可以自己写。
而且在学习数据结构时,确实应该自己手写一遍。
但是在真实开发中,如果每做一个项目都从零写一套数据结构,那效率会非常低,而且还容易出 bug。
STL 解决的问题就是:
把常见的数据结构和算法标准化、模板化、通用化。
比如你想存一组整数,可以用:
cpp
vector<int> v;
想存一组字符串,可以用:
cpp
vector<string> v;
想维护一组自动排序且不重复的数据,可以用:
cpp
set<int> s;
想建立键值映射,可以用:
cpp
map<string, int> dict;
你不用关心底层每一行内存管理怎么写,先把问题解决出来。
这就是 STL 的价值。
二、STL 的版本演变
2.1 原始版本:HP STL
STL 的原始版本由 Alexander Stepanov 和 Meng Lee 在惠普实验室完成。
这个版本可以理解为 STL 的源头。
它最重要的贡献不是某几个具体容器,而是提出了一套非常优雅的泛型编程框架:
用模板把数据结构和算法解耦,用迭代器把容器和算法连接起来。
这也是 STL 后来能成为 C++ 标准库核心组成部分的重要原因。
2.2 P.J. 版本
P.J. 版本由 P.J. Plauger 开发,被 Windows Visual C++ 采用。
这个版本继承自 HP STL,但代码可读性相对一般,符号命名比较复杂。
初学者如果直接阅读这类源码,可能会觉得压力比较大。
2.3 RW 版本
RW 版本由 Rogue Wave 公司开发,也继承自 HP STL。
它曾被一些 C++ 编译环境采用。
这个版本在历史上也有影响,但对我们现在学习 STL 来说,不是最主要的阅读对象。
2.4 SGI 版本
SGI STL 由 Silicon Graphics Computer Systems 开发,也继承自 HP STL。
它被 GCC 采用过,可移植性好,风格相对清晰,源码阅读价值比较高。
很多 C++ 学习资料在讲 STL 源码时,会以 SGI STL 作为主要参考版本。
对于初学者来说,先不急着直接啃源码,但可以知道:
STL 不只是会用接口,背后还有非常经典的泛型编程设计思想。
三、STL 的六大组件
STL 通常可以分为六大组件:
- 容器
- 算法
- 迭代器
- 仿函数
- 适配器
- 空间配置器

3.1 容器:用来存数据
容器就是用来存放数据的类模板。
常见容器有:
cpp
string
vector
list
deque
map
set
还有一些容器适配器:
cpp
stack
queue
priority_queue
不同容器适合不同场景。
3.2 算法:用来处理数据
算法是对数据进行处理的一组函数模板。
常见算法有:
cpp
find
sort
reverse
swap
merge
count
copy
这些算法通常不直接依赖某一种具体容器,而是通过迭代器访问容器中的元素。
比如排序:
cpp
sort(v.begin(), v.end());
这里 sort 不直接关心 v 的内部结构,它只需要一段可操作的区间。
STL 算法的一个重要特点是:
算法尽量和容器解耦。
这就是泛型编程的体现。
3.3 迭代器:连接容器和算法
迭代器可以理解成一种"泛化指针"。
它的作用是:
让算法能够以统一方式访问不同容器中的元素。
比如:
cpp
vector<int> v = {1, 2, 3, 4};
auto it = v.begin();
while (it != v.end())
{
cout << *it << " ";
++it;
}
这里的 it 就是迭代器。
对算法来说,只要能通过迭代器拿到元素,就不必关心底层容器到底是数组、链表还是树。

3.4 仿函数:像函数一样使用的对象
仿函数也叫函数对象。
它本质上是重载了 operator() 的类对象。
比如:
cpp
struct Greater
{
bool operator()(int x, int y)
{
return x > y;
}
};
这个对象可以像函数一样调用:
cpp
Greater greater;
cout << greater(3, 2) << endl;
在 STL 中,仿函数经常用于控制算法行为。
比如排序时指定升序或降序:
cpp
sort(v.begin(), v.end(), greater<int>());
仿函数让算法更灵活。
3.5 适配器:换一种接口包装已有组件
适配器可以理解成"包装器"。
它会把已有组件包装成另一种使用方式。
常见的容器适配器有:
cpp
stack
queue
priority_queue
比如 stack 底层可以用 deque 实现。
但是对外只暴露栈应该有的接口:
cpp
push
pop
top
empty
size
你不能随便遍历它,也不能随便访问中间元素。
这就是适配器的特点:
底层借用已有容器,对外提供特定接口。
3.6 空间配置器:负责内存管理
空间配置器,也就是 allocator,用来负责 STL 容器中的内存分配和释放。
平时写代码时,我们很少直接操作 allocator。
但它是 STL 底层非常重要的一部分。
比如 vector 扩容时,底层需要申请更大的空间、搬移旧数据、释放旧空间。
这些和内存相关的动作,都会涉及空间配置器的思想。
入门阶段先知道它存在即可
四、STL 为什么重要?
4.1 刷题中很重要
在笔试和 OJ 中,STL 几乎是 C++ 选手的基础工具箱。
比如:
二叉树层序遍历可以用:
cpp
queue<TreeNode*> q;
判断括号匹配可以用:
cpp
stack<char> st;
统计字符出现次数可以用:
cpp
unordered_map<char, int> count;
字符串题基本离不开:
cpp
string
如果完全不用 STL,很多题的代码量会明显增加。
而且自己写数据结构还容易引入额外 bug。
4.2 面试中很重要
面试中经常会问 STL 相关问题,比如:
vector和list的区别map和unordered_map的区别set的底层结构vector扩容机制- 迭代器失效问题
string的常见接口stack、queue、priority_queue的使用场景
这些问题不是单纯考背诵,而是在考你是否理解容器背后的数据结构。
所以学 STL 不能只停留在"会调接口"。
至少要知道:
这个容器适合什么场景,不适合什么场景,底层大概是什么结构。
4.3 工作中很重要
真实开发中,STL 的价值更明显。
比如你要管理一组对象:
cpp
vector<User> users;
要根据用户 ID 查询对象:
cpp
unordered_map<int, User> userMap;
要保存自动排序的数据:
cpp
set<int> nums;
要处理字符串:
cpp
string name;
这些都是高频场景。
STL 的意义在于:
让我们少重复造底层轮子,把精力放在业务逻辑和问题建模上。
当然,这不意味着底层不重要。
恰恰相反,只有理解底层,才能更好地选择正确的 STL 组件。
五、如何学习 STL?
5.1 第一层:能用
第一层目标是能用。
也就是看到题目或项目需求时,知道应该选哪个容器。
比如:
- 需要动态数组:
vector - 需要栈:
stack - 需要队列:
queue - 需要优先级队列:
priority_queue - 需要字符串:
string
这一阶段重点是接口熟练。
5.2 第二层:明理
第二层目标是明理。
也就是理解容器背后的结构和适用场景。
比如:
vector 为什么随机访问快?
因为它底层是连续空间。
list 为什么中间插入删除方便?
map 为什么有序?
unordered_map 为什么平均查询快?
5.3 第三层:能扩展
第三层目标是能扩展。
比如:
- 能模拟实现简单的
vector - 能模拟实现简单的
string - 能理解迭代器的基本设计
- 能读懂部分 STL 源码
- 能写出自己的容器或适配器
- 能理解内存配置和对象构造销毁的配合
这一层更偏向进阶学习。
对初学者来说,不必一开始就追求源码级掌握,但要知道未来可以往这个方向走。

六、为什么先学 string?
6.1 C 语言字符串的问题
在 C 语言中,字符串本质上是以 '\0' 结尾的字符数组。
比如:
cpp
char str[] = "hello";
它在内存中大致是:
cpp
'h' 'e' 'l' 'l' 'o' '\0'
C 标准库提供了一些字符串处理函数:
cpp
strlen
strcpy
strcat
strcmp
strstr
这些函数能用,但也有明显问题:
第一,字符串和操作函数是分离的。
数据是字符数组,函数在外面。
第二,空间需要程序员自己管理。
如果空间不够,容易越界。
第三,很多函数对 '\0' 非常敏感。
一旦字符串没有正确结尾,就可能出问题。
第四,不符合 C++ 面向对象思想。
字符串作为一种对象,理应自己管理自己的空间和行为。
6.2 string 解决了什么问题?
std::string 把字符串封装成了一个类。
它可以自己管理底层空间,并且提供大量成员函数。
比如:
cpp
string s = "hello";
s += " world";
cout << s.size() << endl;
cout << s[0] << endl;
你不需要自己手动计算空间,也不需要频繁调用 strlen、strcpy。
相比 C 字符串,string 更符合 C++ 的写法:
字符串对象自己管理自己的数据,并通过成员函数提供操作接口。

七、string 其实是什么?
7.1 string 是 basic_string
严格来说,string 不是凭空出现的特殊类型。
它通常是下面这个类模板的类型别名:
cpp
std::basic_string<char>
也就是说,标准库真正提供的是一个更通用的字符串模板:
cpp
template<
class CharT,
class Traits = std::char_traits<CharT>,
class Allocator = std::allocator<CharT>
>
class basic_string;
而 string 可以理解成:
cpp
using string = basic_string<char>;
所以从模板角度看:
basic_string<char> 用来表示普通窄字符字符串。
类似地,还有:
cpp
wstring
u16string
u32string
它们对应不同字符类型。

7.2 为什么说 string 是 STL 学习入口?
string 很适合作为 STL 的入口,因为它有几个特点:
第一,它是类。
它封装了数据和操作。
第二,它是模板的实例。
它背后体现了 basic_string 的模板设计。
第三,它有容器特征。
它可以存储一组字符,支持下标访问、迭代器、范围 for。
第四,它在刷题和工程中都非常常用。
字符串处理题、输入输出、文本解析、日志处理,都离不开它。
所以学 string,不是只学字符串,而是在为后面的 vector、list、map 等 STL 容器打基础。
八、使用 string 前要包含什么?
使用 string 时,需要包含头文件:
cpp
#include <string>
如果要输出,还需要:
cpp
#include <iostream>
常见写法:
cpp
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s = "hello";
cout << s << endl;
return 0;
}
如果不写:
cpp
using namespace std;
就需要写完整命名空间:
cpp
std::string s = "hello";
std::cout << s << std::endl;
入门练习可以使用 using namespace std;。
但大型项目和头文件中,更建议避免直接展开整个 std 命名空间。
九、auto:让类型推导更轻松
9.1 auto 的新含义
C++11 之后,auto 的含义发生了重要变化。
它不再主要表示自动存储类型,而是表示:
让编译器根据初始化表达式自动推导变量类型。
比如:
cpp
int a = 10;
auto b = a;
这里 b 会被推导成 int。
再比如:
cpp
auto c = 'a';
auto d = 3.14;
c 是 char。
d 是 double。

9.2 auto 必须初始化
因为 auto 需要根据右边的值推导类型,所以必须初始化。
错误写法:
cpp
auto x;
编译器不知道 x 应该是什么类型。
正确写法:
cpp
auto x = 10;
9.3 auto 和指针、引用
指针可以这样写:
cpp
int x = 10;
auto p1 = &x;
auto* p2 = &x;
这里 p1 和 p2 都是 int*。
但是引用要显式写 &:
cpp
auto& r = x;
如果写:
cpp
auto r = x;
那么 r 是一个新的 int 变量,不是引用。
这点在范围 for 中尤其重要。
9.4 auto 在 STL 中很好用
STL 中很多类型写起来很长。
比如:
cpp
std::map<std::string, std::string>::iterator it = dict.begin();
可以简化成:
cpp
auto it = dict.begin();
这不是偷懒,而是合理使用类型推导。
尤其是迭代器类型很长时,auto 能明显减少代码噪音。
十、范围 for:更自然地遍历容器
10.1 普通遍历
以前遍历数组可能会写:
cpp
int arr[] = {1, 2, 3, 4, 5};
for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); ++i)
{
cout << arr[i] << " ";
}
这种写法没问题,但比较啰嗦,而且边界容易写错。
10.2 范围 for 写法
C++11 引入了范围 for:
cpp
for (auto e : arr)
{
cout << e << " ";
}
对 string 也可以这样遍历:
cpp
string s = "hello";
for (auto ch : s)
{
cout << ch << " ";
}
这段代码会依次取出字符串中的每个字符。

10.3 修改元素时要用引用
如果只是读取,可以写:
cpp
for (auto ch : s)
{
cout << ch << " ";
}
如果要修改字符串中的字符,就要写引用:
cpp
for (auto& ch : s)
{
ch = toupper(ch);
}
因为:
cpp
auto ch
拿到的是元素副本。
cpp
auto& ch
拿到的才是原元素引用。
这点非常关键。
十一、string 的常见构造方式
11.1 构造空字符串
cpp
string s1;
这会构造一个空字符串。
可以理解为:
cpp
""
11.2 用 C 字符串构造
cpp
string s2("hello");
或者:
cpp
string s2 = "hello";
这是最常见的写法。
它会用 C 风格字符串构造一个 string 对象。
11.3 拷贝构造
cpp
string s3(s2);
或者:
cpp
string s3 = s2;
这里用已有的 string 对象构造新对象。
11.4 构造 n 个相同字符
cpp
string s4(5, 'x');
结果是:
cpp
"xxxxx"
这个构造方式在刷题中偶尔很有用。
比如需要创建一段固定长度的字符串。
