本文介绍如何使用 C++ 标准库,也就是怎么在你的项目中应用 STL 。
目录
- [1 概述](#1 概述)
- [2 标头导入方式](#2 标头导入方式)
-
- [2.1 通用方式](#2.1 通用方式)
- [2.2 使用模块导入标准库(C++20/C++23)](#2.2 使用模块导入标准库(C++20/C++23))
- [3 使用规则](#3 使用规则)
1 概述
C++ 标准库是 C++ 语言的一部分,它包含了一系列的类和函数,用于处理常见的编程任务,如输入输出、字符串操作、数学计算、容器管理等。
标准库的设计目标是提供高效、可移植和易于使用的工具,以帮助开发者快速构建应用程序。
C++ 标准库依次发布了几个正式的 C++ 标准,依次为
C++98、C++03、C++11、C++14、C++17、C++20、C++23,随着版本的迭代,导入的方式也是有两种。
2 标头导入方式
2.1 通用方式
通过在
Include指令中为其命名,可包括标准标头的内容。
#include指令告诉编译器在编译时将指定的头文件内容插入到当前文件中,头文件通常包含函数声明、类定义、宏定义等内容。
cpp//基本语法 #include <iostream> //导入输入输出流库
2.2 使用模块导入标准库(C++20/C++23)
从 C++20 开始,C++ 引入了模块(Modules),并在 C++23 中进一步完善了对标准库模块的支持。
模块提供了一种更高效、更安全的方式来导入标准库。
- 编译速度更快:模块只编译一次,后续导入时直接使用编译好的二进制接口;
- 隔离性更好:模块不会泄露宏定义和私有符号,避免了命名冲突;
- 依赖关系清晰:模块的导入和导出机制使得代码的依赖关系更加清晰;
cpp#基本语法 import std; //导入整个标准库(C++23特性) import std.core; //导入核心库 import std.iostream; //导入输入输出流库
3 使用规则
- 头文件可以按任意顺序包含;
- 标准库头文件之间没有严格的先后依赖关系(除了极少数特殊情况,如 <cstddef> 等基础头文件通常被其他头文件隐式包含)。你可以随意调换 #include 的顺序,不会影响编译。
- 同一个头文件可以被包含多次;
- 头文件内部使用了"包含保护"(#pragma once 或 #ifndef),所以多次 #include 同一个标准头文件是允许的,不会产生重复定义错误。
- 可以包含多个定义相同宏或相同类型的头文件;
- 某些宏或类型可能在多个标准头文件中都有定义(例如 NULL、size_t 等),只要它们的定义一直,同时包含这些头文件是合法的,编译器不会报错。
- 不能在声明语句内部包含头文件;
cpp#错误示例 void func() { #include <vector> //不能在函数体内包含头文件 }
- 包含标准头文件之前,不能定义与关键字同名的宏;
- 如果程序员自己定义了一个与 C++ 关键字同名的宏(比如 #define delete),再包含标准头文件,会导致头文件中的代码被错误替换,引发编译错误。标准规定:在包含任何标准头文件之前,不得定于与关键字(如 new、delete、class、virtual 等)同名的宏。
- 如果 C++ 库标头包括任何其他 C++ 库标头,则需要定义所需的类型;
- 如果一个 C++ 头文件(比如 <vector>)内部包含了另一个头文件(比如 <memory>),那是为了实现它自己的功能,从而把一些类型定义好(比如 std::allocator)。
- 始终显示包括翻译单元中所需的任何 C++ 库标头,以免弄错其实际依赖项;
- 不要依赖"间接包含"。也就是说,你代码里用到了 std::vector,就应该在文件开头直接写 #include <vector>,而不是期望其他头文件帮你包含它。因为不同编译器或标准库版本可能改变内部包含关系,直接写你需要的头文件,代码才最可靠、最可移植。
- 标准 C 标头从不包含其他标准标头;
- C 语言的标准头文件(如 <stdio.h>、<stdlib.h>)是独立的,它们不会再去包含别的 C 标准头文件。这在 C++ 中兼容的 C 头文件(如 <cstdio>)也同样适用。所以你需要什么 C 函数,就显式包含对应的头文件。
- 标准标头仅声明或定义此文档中描述的相关实体;
- 库中的每个函数都在标准标头中声明。与在标准 C 中不同,如果一个屏蔽宏包含与屏蔽函数声明及实现相同效果的函数相同的名称,则标准标头不提供此屏蔽宏。
- C++ 标准库里的每一个函数(比如 std::abs、std::sin)都只以真正的函数的形式在头文件里声明。它们不会同时提供一个同名的宏来替代这个函数。
- 在 C 标准库中,有些函数同时也会被实现为宏(例如 getchar、tolower 等)。宏会在预处理阶段直接展开,可能会"屏蔽"(隐藏)掉真正的函数。这种设计在 C 中是为了性能,但也带来了一些副作用(比如无法取函数地址、容易产生意外替换)。
- 除 C++ 库标头中的 operator delete 和 operator new 以外的所有名称都在 std 命名空间中定义,或者在 std 命名空间内的嵌套命名空间中定义,宏不受命名空间约束。
- 宏是预处理器的文本替换工具,不遵循 C++ 的命名空间规则。无论在哪个命名空间里,宏的名字始终是全局可见的,直接写名字即可,绝对不能加 std:: 前缀。
- 当你 #include 一个标准库头文件时,有些实现会额外把这些名字也放到全局命名空间中,而另一些实现则不会;
- 某些编译环境:包含一个 C++ 头文件(比如<iostream>)后,不仅 std::cout 可用,还可以直接写 cout (不带 std::),因为编译器悄悄地在全局作用域添加了类似于 using std::cout; 的声明。
- 其他环境:头文件不会把任何名字引入当前命名空间。你仍然必须使用完整的 std:: 限定名,否则编译报错。
- 用于处理命名空间的可移植性最高的方法将遵循以下两种规则:
- 如果你想确保名字在 std 命名空间中(例如 std::abort),就包含 <cstdlib>。
- 如果你想确保名字在全局命名空间中(例如直接写 abort),就包含<stdlib.h>。
- 如果在所有 Include 指令之后立即编写此声明,则会将这些名称提升至全局命名空间中。这会将所有库名称引入当前命名空间。
cppusing namespace std;
- 禁止用户向 std 命名空间或其内部的嵌套命名空间添加任何声明,除非标准明确允许了某些特例;
- 如果违反了此规则(例如在 std 中添加一个完全新的函数),你的程序没有定义行为,可能在某编译器下正常,换一个就崩溃。