C++标准库包含函数、类、类模板和其他工具,不是只有从C继承的独立函数。“STL”通常指容器、迭代器、算法及相关泛型组件,是理解标准库的一条主线;输入输出、字符串、并发等也属于标准库。
本页采用C++17,提供完整导航,并重点整理原稿中的流、内存管理和字符串接口。容器、算法等已有独立笔记,不在这里重复一套教程。
1. 从需求找到头文件
跳转到“1. 从需求找到头文件”| 需求 | 主要头文件 | 入口 |
|---|---|---|
| 容器与泛型算法 | <array>、<vector>、<map>等,<algorithm>、<iterator>、<functional> | 容器、迭代器、算法、适配器。 |
| 输入输出 | <iostream>、<fstream>、<sstream>、<iomanip> | 控制台、文件、内存字符串和格式控制。 |
| 数学和数值 | <cmath>、<complex>、<numeric>、<valarray> | 数学函数、复数、累计等数值算法和数值数组。 |
| 时间 | <chrono>、<ctime> | 时钟、时间点、时长和C兼容时间接口。 |
| 并发 | <thread>、<mutex>、<shared_mutex>、<condition_variable>、<future>、<atomic> | 线程、互斥量、原子内存序。 |
| 资源与内存 | <memory>、<new>、<scoped_allocator>、<cstdlib> | 智能指针、分配工具、分配器传递和C兼容分配。 |
| 文件系统、正则 | <filesystem>、<regex> | 路径/目录/文件属性与正则匹配。 |
| 错误处理 | <exception>、<stdexcept>、<system_error> | 异常、常见异常类型和错误码。 |
| 类型支持 | <typeinfo>、<type_traits>、<typeindex>、<cstddef>、<limits> | 类型信息、编译期特征、基础类型和数值限制。 |
| 通用值工具 | <utility>、<tuple>、<any>、<optional>、<variant>、<bitset> | 移动/交换、组合值、可选值、有限候选类型与位集合。 |
| 字符与字符串 | <string>、<string_view>、<cstring>、<cwchar>、<cwctype>、<clocale>、<locale> | 拥有/借用字符串、C字符串、宽字符与locale。 |
| 诊断与C兼容 | <cassert>、<cerrno>、<csignal>、<csetjmp>、<cstdarg>、<cstdio>、<cctype>等 | 断言、错误状态、信号、可变参数等;各自有严格使用条件。 |
包含自己实际使用的接口对应头文件,不依赖别的头文件间接把它带进来。<cstdbool>在C++17已弃用,C++本来就有 bool/true/false,普通C++代码不需要它。C兼容头文件不代表相关接口适合任意C++对象,例如跨越非平凡对象寿命的跳转有额外限制。
2. 输入输出流的层次
跳转到“2. 输入输出流的层次”
图保留原资料的类关系,实际常用名字通常是模板对 char的别名,例如 ifstream对应 basic_ifstream<char>。fstream同时具备输入和输出功能,但不是通过继承 ifstream和 ofstream实现的。
| 头文件 | 常用对象/类型 |
|---|---|
<ios> | ios_base、格式与状态基础设施。 |
<istream> / <ostream> | istream / ostream输入输出接口。 |
<iostream> | cin、cout、cerr、clog等标准流。 |
<fstream> | ifstream只读文件流、ofstream写文件流、fstream双向文件流、filebuf文件流缓冲。 |
<sstream> | istringstream、ostringstream、stringstream内存字符串流。 |
<iomanip> | setw、setprecision、setfill等格式控制。 |
读取文件要检查failbit
跳转到“读取文件要检查failbit”打开不存在或无权限的文件通常使流进入失败状态。仅判断 !file.bad()不足以确认打开成功,因为 failbit与 badbit含义不同。以下程序只读取命令行指定文件:
#include <fstream>#include <iostream>#include <string>
int main(int argc, char* argv[]){ if (argc != 2) { std::cerr << "Usage: reader <text-file>\n"; return 2; } std::ifstream input(argv[1]); if (!input) { std::cerr << "Cannot open input file\n"; return 3; } std::string line; while (std::getline(input, line)) std::cout << line << '\n'; if (input.bad() || (!input.eof() && input.fail())) { std::cerr << "Read failed before end of file\n"; return 4; } return std::cout ? 0 : 5;}循环在实际读取后判断流,不写 while (!eof())先猜下一次能否读到。文件流析构会关闭文件;需要提前释放文件或检查写入关闭失败时,可显式 close()并检查状态。
good()表示没有状态位;fail()关注格式/操作失败等;bad()关注严重I/O错误;eof()表示已检测到末尾。它们不是互斥枚举,可能同时置位。格式化提取 >>和按行 getline混用时,需处理之前留下的换行;ignore()默认只忽略一个字符,清除整行应按目的给出上限和分隔符。
打开模式
跳转到“打开模式”| 标志 | 含义 |
|---|---|
in / out | 请求读/写。 |
app | 每次写入定位到末尾。 |
ate | 打开后先定位末尾,随后仍可按支持的方式定位。 |
trunc | 打开成功时丢弃原内容,具有破坏性。 |
binary | 二进制模式,避免平台文本模式的特定转换;不自动序列化任意对象。 |
模式可用按位或组合,但不是所有组合都合法。默认 ofstream写打开通常会截断旧文件;要追加须明确使用app。双向文件流的读写定位及状态切换也要遵循接口规则。C++工作草案:文件流
内存流和格式
跳转到“内存流和格式”#include <cassert>#include <iomanip>#include <sstream>#include <string>
int main(){ std::istringstream input("42 sample"); int number = 0; std::string name; input >> number >> name; assert(input && number == 42 && name == "sample"); std::ostringstream output; output << std::fixed << std::setprecision(2) << 3.1415; assert(output.str() == "3.14");}在默认浮点格式下,precision主要控制有效数字;结合fixed时控制小数位。setw常只影响下一次相关格式化输出,不能把所有格式控制器都当作相同的持续状态。std::endl还会刷新流,普通换行通常只需 '\n'。
3. 内存管理:资源、初始化和配对
跳转到“3. 内存管理:资源、初始化和配对”变量的作用域、存储期和实现中的栈/堆位置不是同一个问题。函数内可以有静态对象,编译器也可优化掉某些存储;动态分配区不是“程序里所有未用内存”。优先用容器与智能指针表达所有权,见指针与引用。
| 接口 | 作用与边界 |
|---|---|
make_unique / unique_ptr | 独占对象所有权,自动析构并释放。 |
shared_ptr / weak_ptr | 共享所有权/不延长寿命的观察;不自动保护对象内部并发读写。 |
new T(...) / delete p | 分配并初始化一个对象/匹配销毁释放。 |
new T[n] / delete[] p | 数组形式必须配数组释放;基本类型是否初始化取决于写法。 |
malloc / calloc / free | C兼容原始存储分配;必须用free配对,不调用普通C++构造析构。 |
scoped_allocator_adaptor | 将分配器策略传递给嵌套的使用分配器对象;不是“离开词法作用域便自动释放”的通用开关。 |
malloc返回的字节未初始化;calloc把分配字节置零,不代表任意C++类型都完成了正确构造,也不保证所有类型的语义零值都等同全零位。二者可能返回空指针。释放后把某一变量设为空,不能修复仍指向原存储的其他别名。
连续二维数组与指针数组不同
跳转到“连续二维数组与指针数组不同”#include <cassert>#include <memory>#include <vector>
int main(){ auto single = std::make_unique<int>(10); auto sequence = std::make_unique<int[]>(6); // 本例int元素值初始化为0。 sequence[5] = 9; assert(*single == 10 && sequence[0] == 0 && sequence[5] == 9);
auto rows = new int[2][3]{}; // 指针类型int (*)[3],连续2×3。 rows[1][2] = 7; assert(rows[1][2] == 7); delete[] rows; // 一次匹配数组分配,不逐行delete。
std::vector<std::vector<int>> separate(2, std::vector<int>(3)); separate[1][2] = 7; // 各行拥有自己的存储,不保证行间连续。 assert(separate[1][2] == 7);}new T*[m]只创建m个指针,并不会同时创建m行元素。若再逐行分配,还要逐行释放、处理部分失败;上面的容器形式更容易保持异常安全。分配与释放不能混搭 new/free或 malloc/delete。C++工作草案:内存工具
4. string拥有字符,string_view借用字符
跳转到“4. string拥有字符,string_view借用字符”std::string管理可变长字符序列;std::string_view是C++17的非拥有视图,不延长原字符串寿命,也不保证其末端有NUL。二者的size按 char数量计,不等于UTF-8文本的汉字数或用户感知字符数。
| 操作 | 注意点 |
|---|---|
| 默认/C字符串/复制构造 | 以C字符串构造通常读取到NUL;要保留内嵌NUL,指定长度。 |
size/length/empty | size与length同义,不包含末尾额外终止符。 |
append/push_back/insert | 追加/插入,可能重新分配。 |
erase/replace/clear | 删除/替换/清空,位置须符合要求。 |
[]/at/front/back | 有效字符索引小于size;at可检查越界,front/back要求非空。 |
== != <、+ | 比较/字典序/连接,不是按自然语言排序。 |
find/rfind | 返回位置或 string::npos,使用前判断。 |
substr(pos,count) | 创建拥有内容的新字符串,pos越界会失败。 |
c_str/data | 借用内部缓冲地址,不能当作独立长期副本。 |
从C++11起,string::data()和 c_str()都提供末尾NUL保证;“data不以空字符终止”是过时表述。C++17增加非const字符串的可写data重载,可修改已有字符,但不能借它越过size随意写入。C++工作草案:string访问器
#include <cassert>#include <cstring>#include <string>
int main(){ std::string text = "Hello, world!"; text += " How are you?"; assert(text.substr(0, 5) == "Hello"); const auto position = text.find("world"); assert(position != std::string::npos && position == 7); text.replace(position, 5, "reader"); text.data()[0] = 'h'; // C++17,索引在已有字符范围内。 assert(text.front() == 'h' && text.back() == '?');
const std::string binary("A\0B", 3); assert(binary.size() == 3); assert(std::strlen(binary.c_str()) == 1); // C字符串接口在首个NUL停止。 const std::string fullCopy(binary.data(), binary.size()); assert(fullCopy == binary);}借出的指针或string_view在原对象销毁、重新分配或某些修改后可能失效。通过 c_str()传给只读C接口时,应保证调用期间原string仍有效;C接口若需要长期保存,应明确复制或所有权约定。
5. C字符串的缓冲区责任
跳转到“5. C字符串的缓冲区责任”strcpy/strcat不接收目标容量,调用者必须证明缓冲足够容纳全部字符和终止NUL;数组写越界不会因为用了标准库函数便自动安全。char text[] = "Hello"只为这段初值留出空间,不能直接继续追加任意字符串。
#include <array>#include <cassert>#include <cstdio>#include <cstring>
int main(){ std::array<char, 50> buffer{}; const int required = std::snprintf(buffer.data(), buffer.size(), "%s%s", "Hello, world!", " How are you?"); assert(required >= 0 && static_cast<std::size_t>(required) < buffer.size()); assert(std::strcmp(buffer.data(), "Hello, world! How are you?") == 0);}snprintf要检查负值错误及输出截断;有缓冲长度参数也不等于可以忽略结果。普通C++文本拼接优先使用string,再在系统边界进行明确转换。