在 C++ 中,标准库提供了丰富的现成类型与函数,它们按功能存放在不同的头文件中。平时写代码时,很多小伙伴常会遇到一个很实际的问题:明明知道要使用哪个工具,却拿不准顶部应该包含哪个头文件。
其实,数量众多的标准库头文件完全不需要死记硬背。下面我们将日常开发最常用的头文件按功能整理成了分类速查表,大家写代码拿不准时随时回来按表查阅即可。
包含头文件的 2 个基本原则
在查阅具体的分类表格之前,我们先明确关于 #include 预处理命令的 2 个基本原则:
- 用到什么,就显式包含什么:有时只写了
#include <iostream>没写#include <string>,std::string碰巧也能编译通过,这是因为部分编译器的头文件内部存在间接包含。但不同编译环境的内部依赖随时可能变化,我们用到了哪个工具,就应该显式包含它对应的头文件。 - 包含头文件不等于省掉 std:: 前缀:比如
#include <vector>只是把相关声明引入当前源文件,而标准库的名称依然定义在std这个独立的命名空间中,我们使用时仍然要写全std::vector。
下面我们来看一个组合包含多个标准库头文件的例子。
示例:组合包含多个头文件
#include <algorithm>
#include <iostream>
#include <vector>
int main() {
std::vector<int> nums = {30, 10, 20}; // 来自 <vector>
std::sort(nums.begin(), nums.end()); // 来自 <algorithm>
for (int n : nums) {
std::cout << n << ' '; // 来自 <iostream>
}
std::cout << '\n';
return 0;
}运行结果如下。
10 20 30分析:
在这个例子中,#include <algorithm>、#include <iostream> 和 #include <vector> 分别引入了排序函数 std::sort()、输出流 std::cout 以及动态数组 std::vector 所在的头文件。
平时如果我们写对了 std::sort 或 std::vector,编译时却报错提示 'xxx' is not a member of 'std',意思是在 std 中找不到该名称,多半就是顶部漏写了对应的头文件。养成按需包含并按字母顺序排列头文件的习惯,能让代码的依赖关系一目了然。
输入输出与字符串头文件
控制台输入输出、本地文件读写以及字符串处理是日常编程中出现频率最高的操作,对应的标准库头文件如下表所示。
| 头文件 | 常用类型与函数 | 主要用途与说明 |
|---|---|---|
| <iostream> | std::cin、std::cout、std::cerr | 标准控制台输入与输出流 |
| <iomanip> | std::setw()、std::setprecision()、std::fixed | 控制输出宽度、对齐方式与小数位数 |
| <fstream> | std::ifstream、std::ofstream、std::fstream | 本地普通文件与二进制文件的读写 |
| <sstream> | std::stringstream、std::istringstream | 在内存中进行字符串流的拼接与解析 |
| <string> | std::string、std::getline()、std::to_string() | 现代字符串类、整行读取与数值互转 |
| <string_view> | std::string_view | 轻量只读字符串视图(C++17 起) |
| <format> | std::format() | 使用 {} 占位符格式化字符串(C++20 起) |
| <print> | std::print()、std::println() | 直接向控制台格式化输出文本(C++23 起) |
| <cctype> | std::isalpha()、std::isdigit()、std::toupper() | 单个字符的类别判断与大小写转换 |
| <cstring> | std::strlen()、std::strcpy()、std::strcmp() | 操作以 '\0' 结尾的 C 风格字符数组 |
在这组头文件中,初学的小伙伴最容易搞混的是 <string> 和 <cstring>。在现代 C++ 开发中,处理日常文本优先使用 <string> 提供的 std::string 类,而 <cstring> 则是为了兼容 C 风格字符串准备的工具头文件。
STL 容器、迭代器与算法头文件
STL 容器负责在内存中组织和保存数据,迭代器与算法则负责获取序列范围并执行排序、查找、去重或累加等操作。对应的标准库头文件如下表所示。
| 头文件 | 常用类型与函数 | 主要用途与说明 |
|---|---|---|
| <vector> | std::vector | 支持在尾部快速动态增删的连续数组容器 |
| <array> | std::array | 长度在编译期固定的现代数组容器(C++11 起) |
| <deque> | std::deque | 支持首尾两端快速增删的双端队列容器 |
| <list> | std::list | 支持任意位置快速插删的双向链表容器 |
| <set> / <unordered_set> | std::set、std::unordered_set | 自动去重的有序集合与哈希集合 |
| <map> / <unordered_map> | std::map、std::unordered_map | 存放键值对的有序映射表与哈希表 |
| <stack> | std::stack | 遵循后进先出(LIFO)规则的栈容器适配器 |
| <queue> | std::queue、std::priority_queue | 先进先出普通队列与按优先级出队的优先队列 |
| <iterator> | std::size()、std::begin()、std::end() | 获取数组元素个数及通用的容器首尾迭代器 |
| <algorithm> | std::sort()、std::find()、std::reverse() 等 | 排序、查找、二分、反转与去重等通用算法 |
| <numeric> | std::accumulate()、std::gcd()、std::lcm() | 序列累加求和、最大公约数与最小公倍数 |
| <ranges> | std::views::filter、std::views::transform | 管道式范围视图与按需序列转换(C++20 起) |
在 C++ 中,STL 容器的头文件非常好记,基本都与容器本身同名。比如使用 std::vector 就包含 <vector>,使用 std::map 就包含 <map>。只有优先队列 std::priority_queue 是个例外,它和普通队列一起存放在 <queue> 头文件中。
而在遍历与算法方面,查询原生数组长度的 std::size() 位于 <iterator> 中。像排序、查找这类常用的 STL 算法以及 std::ranges::sort 等范围算法都集中在 <algorithm> 中,只有 std::accumulate()、std::gcd() 等偏数值计算的算法放在 <numeric> 头文件中。
内存管理与实用工具头文件
当我们需要使用智能指针管理动态内存,或者使用键值对、函数包装器以及现代可选值类型时,对应的标准库头文件如下表所示。
| 头文件 | 常用类型与函数 | 主要用途与说明 |
|---|---|---|
| <cstddef> | std::size_t、std::nullptr_t | 表示大小或下标的无符号整型与空指针类型 |
| <memory> | std::unique_ptr、std::shared_ptr、std::make_unique() | 智能指针与动态内存自动管理 |
| <utility> | std::pair、std::move()、std::swap() | 键值对结构、移动语义转换与变量交换 |
| <tuple> | std::tuple、std::make_tuple()、std::get() | 可打包任意数量、不同类型元素的元组 |
| <functional> | std::function、std::greater<> | 通用函数包装器与标准比较函数对象 |
| <optional> | std::optional、std::nullopt | 表达 “有值” 或 “无值” 的可选值包装(C++17 起) |
| <variant> | std::variant、std::get()、std::visit() | 类型安全的现代联合体容器(C++17 起) |
| <span> | std::span | 连续内存序列的观察视图(C++20 起) |
| <concepts> | std::integral、std::floating_point 等 | 模板参数的编译期概念约束(C++20 起) |
| <compare> | std::strong_ordering、operator<=> | 三路比较运算符与比较结果类型(C++20 起) |
在这一组头文件中,小伙伴们最容易找错位置的是 std::pair、std::move() 和 std::swap()。这三个高频工具既不在提供 std::unique_ptr 等智能指针的 <memory> 里,也不在 <algorithm> 里,而是统一存放在 <utility> 通用工具头文件中。
此外,日常表示容器长度或数组下标的 std::size_t 正式定义在 <cstddef> 中,而用来包装普通函数与 Lambda 表达式的 std::function 则位于 <functional> 头文件中。
文件、时间、数学与异常头文件
在涉及目录路径操作、高精度时间测量、常见数学计算、随机数生成以及异常错误处理时,对应的标准库头文件如下表所示。
| 头文件 | 常用类型与函数 | 主要用途与说明 |
|---|---|---|
| <filesystem> | std::filesystem::path、exists()、copy_file() | 跨平台文件路径解析与目录操作(C++17 起) |
| <chrono> | std::chrono::system_clock、steady_clock、duration | 高精度时钟、时间戳获取与耗时计算 |
| <cmath> | std::abs()、std::pow()、std::sqrt()、std::round() | 绝对值、幂运算、开方与取整等数学函数 |
| <random> | std::mt19937、std::uniform_int_distribution | 现代随机数生成引擎与概率分布器 |
| <limits> | std::numeric_limits | 查询各种数值类型的最大值与最小值范围 |
| <exception> | std::exception | 所有标准库异常类的共同基类 |
| <stdexcept> | std::runtime_error、std::out_of_range 等 | 日常可直接抛出或捕获的具体标准异常类 |
| <cassert> | assert() | 运行期调试断言宏,条件不成立时终止程序 |
在使用标准异常时,我们要留意区分两个容易混淆的头文件:顶层基类 std::exception 位于 <exception> 中,而日常直接用来抛出或捕获具体错误的 std::runtime_error、std::out_of_range 等异常类则位于 <stdexcept> 头文件中。
C 兼容头文件的命名规律
细心的小伙伴可能会发现,C++ 标准库头文件都不带 .h 后缀,但其中有一小部分专门以字母 c 开头,比如 <cmath>、<cstring>、<cctype>、<cassert> 和 <cstddef>。
其实,它们都是从 C 语言的 <math.h>、<string.h> 等头文件改造而来的兼容版本:去掉末尾的 .h,在最前面加上字母 c,并将里面的函数统一放进 std 命名空间。编写现代 C++ 代码时,我们推荐统一包含 <cmath>、<cstring>,不要再混用旧式的 .h 头文件。
多线程与并发编程头文件
如果我们需要在程序中创建并发线程、执行互斥加锁、条件等待或异步任务,对应的 5 个标准并发头文件如下表所示。
| 头文件 | 常用类型与函数 | 主要用途与说明 |
|---|---|---|
| <thread> | std::thread、std::this_thread::sleep_for() | 创建并管理并发执行线程及线程休眠延时 |
| <mutex> | std::mutex、std::lock_guard、std::unique_lock | 互斥锁及基于 RAII 的自动加锁与解锁守卫 |
| <condition_variable> | std::condition_variable | 实现多线程之间的条件等待与通知唤醒 |
| <atomic> | std::atomic | 提供无锁、线程安全的原子变量读写操作 |
| <future> | std::async()、std::future、std::promise | 启动异步任务并获取异步计算的返回值 |
这 5 个头文件都是从 C++11 起正式加入标准库的,我们使用多线程开发并发功能时可以直接跨平台编译。如果在 Linux 系统下使用 g++ 编译包含 <thread> 或 <future> 的代码时遇到链接报错,记得在编译命令中加上 -pthread 参数。
常见问题
1. 刷算法题时常说的 “万能头文件” bits/stdc++.h,在实际项目开发中能用吗?
不推荐在实际项目中使用。<bits/stdc++.h> 并不是 C++ 官方标准规定的头文件,而是 GCC 编译器内部提供的一个非标准文件,它会一次性把几乎所有标准库头文件全部包含进来。
如果我们在代码里写了它,切换到 Visual Studio(MSVC)等环境就会直接报 “找不到头文件” 错误,无法跨平台编译,而且还会明显拖慢项目的编译速度。在正式项目开发中,大家都应该养成 “用到哪个就包含哪个” 的规范习惯。
2. #include 后面的尖括号 <> 和双引号 "" 有什么区别?
它们决定了编译器去哪里查找头文件,平时遵循下面两条规则即可:
尖括号 <>:直接去系统标准库目录和已配置的第三方库目录查找,所有 C++ 标准库头文件统一使用尖括号,比如 #include <iostream>。
双引号 "":优先在当前源文件所在的项目文件夹中查找,专门用来包含我们自己编写的项目头文件,比如 #include "student.h"。
