简介:这是一份面向C++初学者与需要随时查阅标准库接口的开发者整理的常用函数速查文档,针对日常编程中数学运算、字符串处理、内存操作与类型转换等高频需求,把零散的函数原型与返回值说明汇集到一处,便于快速定位与对照使用。压缩包内仅1个pdf文件,约93KB,篇幅精炼、便于打印或放在本地随时翻阅。内容按数学函数、字符串处理函数、其他常用函数及输入输出成员函数分类罗列,涵盖abs、fabs、pow、sqrt、log等数学计算,memcpy、memset、strcpy、strcat、strcmp、strstr等字符串与内存操作,以及abort、exit、atof、atoi、rand、srand等实用接口,并逐条标注函数原型、功能描述与返回值含义,同时说明所属头文件与使用时的语法语义注意事项,能帮助读者减少翻查手册的时间、降低误用风险。目前已有984人学习下载,适合作为课堂学习、课后练习与项目开发的案头参考。
1. C++ 常用标准库函数,背表不如记住「哪个头文件加什么参数」
打开任何一份 C++ 八股整理,标准库那一页永远是密密麻麻的函数表:string 的 substr、find、append,vector 的 push_back、resize、reserve,algorithm 的 sort、lower_bound、unique。背下来不难,难的是换成一道真实任务——读一行逗号分隔的数字塞进数组、按结构体某个字段倒序排、在有序数组里找第一个不小于 x 的位置——手就停了,或者写出来编译能过、跑起来结果不对。
围绕「C++ 常用标准库函数」的梳理要解决的就是这个落差。按<string>、<vector>、<map>、<algorithm>、<sstream>这几个日常出场率最高的头文件,把真正高频的那批函数挑出来,说清它在哪个头文件、参数怎么传、返回值什么时候是坑、失败时该看哪一行。
从刚配好 VSCode C/C++ 环境、还在纠结c_cpp_properties.json的新手,到工作几年仍然频繁查 cppreference 的老手,都能在这里找到一两条自己没注意过的边界:find返回的npos不能和-1比,reserve不改 size,sort的比较器写<=会在特定数据上直接崩。
2. string 与 vector 的常用函数:从初始化到容量控制
这两个头文件承担了日常代码里八成以上的调用量,也集中了一半以上的运行时错误。它们的共同点是:函数名都很好记,参数含义却容易记反。
2.1 string 上最高频的四个函数:find、substr、size、append
find返回的是std::size_t,找不到时返回std::string::npos,它是size_t的最大值,不是-1。把返回值存进int再和-1比较,是新手最常见的翻车点。
#include <string> #include <iostream> int main() { std::string s = "C++ standard library"; std::size_t p = s.find("standard"); // 找不到返回 std::string::npos if (p != std::string::npos) { std::string sub = s.substr(p, 8); // 从下标 p 开始取 8 个字符 std::cout << sub << '\n'; // 输出 standard } std::cout << s.size() << '\n'; // 20,等价于 s.length(),O(1) }substr(pos, n)的第二个参数是「最多取多少个」,省略则取到末尾;如果pos > size(),它抛std::out_of_range,而不是返回空串。find的第二个参数可以指定起始下标,第三个参数用于查找字符数组,带n的那个重载能避免对 C 字符串做隐式strlen扫描。
2.2 vector 的 size、capacity、reserve、resize 差在哪
这四个函数经常被当作同义词用,实际语义完全不同:size是已有元素个数,capacity是已分配内存能装多少个,reserve只动 capacity,resize只动 size。
#include <vector> #include <iostream> int main() { std::vector<int> v; v.reserve(1000); // capacity 至少 1000,size 仍是 0 // v[0] = 1; // 错误:size 为 0,越界写 for (int i = 0; i < 10; ++i) v.emplace_back(i * i); v.resize(5); // size 变 5,多余元素被析构;capacity 不变 std::cout << v.size() << ' ' << v.capacity() << '\n'; // 5 1000 }判断该用push_back还是emplace_back:元素类型有非平凡的构造函数时,emplace_back直接在容器内存里构造,省一次移动;对int、double这类平凡类型两者没差别。用reserve预分配再emplace_back,能消掉扩容时的整块搬迁,这是刷题时最划算的一行优化。
2.3 字符串数组初始化与二维 char 数组的边界
std::string arr[3] = {"a", "bb", "ccc"}和char arr[3][8] = {"a", "bb", "ccc"}看着像,行为差很远:前者每个元素是独立对象,长度可变;后者每行固定 8 字节,超长会截断或编译报错,且必须留出结尾的'\0'。
#include <cstring> #include <string> char fixed[3][8] = {"a", "bb", "ccc"}; // 每行最多 7 个可见字符 + '\0' std::string dyn[3] = {"a", "bb", "ccc"}; // 长度自由 // sizeof(fixed) == 24,sizeof(fixed[0]) == 8 // strlen(fixed[1]) == 2,不要写成 sizeof(fixed[1]) / sizeof(char)混用sizeof和strlen是 C 风格数组的老问题:sizeof得到的是数组总字节数(包含所有'\0'和填充),strlen只数到第一个'\0'。要拿行数就用sizeof(fixed) / sizeof(fixed[0])。
2.4 map 与 unordered_map:operator[] 和 find 的取舍
operator[]在键不存在时会默认构造一个值并插入,所以「只读查询」绝不能用它,否则容器会被悄悄改大。
#include <map> #include <string> std::map<std::string, int> cnt; cnt["apple"]++; // 不存在则插入 0 再自增 auto it = cnt.find("pear"); if (it != cnt.end()) { /* 只读查询用 find,不改变容器 */ } std::size_t n = cnt.count("pear"); // map 下只能是 0 或 1| 函数 | 头文件 | 生效对象 | 常见误用 |
|---|---|---|---|
| size / length | <string> | 字符个数,O(1) | 与 int 混算出现负数 |
| find | <string> | 首次出现下标 | 拿 npos 和 -1 比较 |
| substr | <string> | 子串 | pos 越界抛异常 |
| reserve | <vector> | 仅 capacity | 以为能直接下标赋值 |
| resize | <vector> | 仅 size | 缩小后仍访问旧下标 |
| operator[] | <map> | 读或插入 | 只读查询污染容器 |
3. algorithm 下的高频函数:sort 比较器与二分查找
<algorithm>是标准库里性价比最高的一个头文件:几十行手写代码,换成一个函数调用,而且复杂度有保证。前提是你得知道每个函数对数据状态的要求。
3.1 sort 的引入库与比较器必须满足严格弱序
sort定义在<algorithm>里,只写#include <vector>编译会报「未声明的标识符」。它的第三个参数是比较器,必须满足严格弱序:comp(a, a)必须为假。
#include <algorithm> // sort 属于 <algorithm>,不是 <vector> #include <vector> #include <string> struct Student { std::string name; int score; }; int main() { std::vector<Student> v{{"a", 90}, {"b", 85}, {"c", 90}}; std::sort(v.begin(), v.end(), [](const Student& x, const Student& y) { if (x.score != y.score) return x.score > y.score; // 分数降序 return x.name < y.name; // 同分按名字升序 }); }写成return x.score >= y.score;就破坏了严格弱序,元素相等时比较器返回真,std::sort的实现在遇到重复键时可能越界访问,表现为随机崩溃或死循环,而不是编译错误。比较器还要注意捕获方式:[&]捕获的变量如果在排序期间被修改,同样会破坏有序性假设。
3.2 lower_bound 与 upper_bound:二分查找不用手写
只要区间已按同一个比较器排好序,lower_bound返回第一个不小于目标的位置,upper_bound返回第一个大于目标的位置,两者相减就是等于目标值的元素个数。这是「C++ 二分查找」类题目最稳的写法。
#include <algorithm> #include <vector> #include <iostream> int main() { std::vector<int> a{1, 3, 3, 5, 9}; auto lo = std::lower_bound(a.begin(), a.end(), 3); // 第一个 >= 3 auto hi = std::upper_bound(a.begin(), a.end(), 3); // 第一个 > 3 std::cout << (lo - a.begin()) << ' ' // 1 << (hi - a.begin()) << ' ' // 3 << (hi - lo) << '\n'; // 2 }| 函数 | 前提 | 复杂度 | 典型用途 |
|---|---|---|---|
| sort | 随机访问迭代器 | O(n log n) | 通用排序 |
| stable_sort | 随机访问迭代器 | O(n log²n) | 需要保留相等元素原序 |
| lower_bound | 区间已按同一比较器有序 | O(log n) | 找第一个 >= x |
| upper_bound | 同上 | O(log n) | 找第一个 > x |
| unique | 相邻重复才合并 | O(n) | 必须先排序 |
| remove_if | 无序要求 | O(n) | 配合 erase 删除 |
降序数组上直接调lower_bound,得到的是无意义的结果——它不会报错,只是默默给出错误答案,查错时优先确认这一点。降序容器要显式传std::greater<int>()。
3.3 unique + erase 去重链,以及 count_if、find_if
unique只合并相邻的重复元素,所以排序是必要前置。它返回新的逻辑尾,容器长度并不改变,必须再配合erase才真正删除。
#include <algorithm> #include <vector> std::vector<int> v{3, 1, 3, 2, 1}; std::sort(v.begin(), v.end()); // 1 1 2 3 3 v.erase(std::unique(v.begin(), v.end()), v.end()); // 1 2 3 std::size_t odd = std::count_if(v.begin(), v.end(), [](int x) { return x % 2 == 1; }); // 2 auto it = std::find_if(v.begin(), v.end(), [](int x) { return x > 1; }); // 指向 23.4 判断质数这类小算法的标准库写法
「判断质数 C++ 优化」的常见写法是用i * i <= n代替sqrt,避免浮点误差,也省一次类型转换。
bool is_prime(long long n) { if (n < 2) return false; for (long long i = 2; i * i <= n; ++i) // 等价于 i <= sqrt(n),无浮点误差 if (n % i == 0) return false; return true; }如果循环上界需要动态计算,std::sqrt返回值要向下取整后加一处理,别直接拿浮点结果当边界。累加类操作则交给<numeric>里的std::accumulate,第三个参数的类型决定累加结果的类型,写0会在数据量大时溢出,长整型场景要写0LL。
4. 流 I/O 与数值转换:字符串转数组、stoi 的异常边界
命令行工具和算法题里最常见的输入形态是「一行逗号分隔的数字」,而cin >>遇到逗号会停下。这时候要换sstream,不是继续给cin加格式。
4.1 cin 与 getline 混用的经典坑
cin >> n会跳过前导空白但在缓冲区留下换行符,紧跟的getline读到的就是一个空行。
#include <iostream> #include <string> int main() { int n; std::cin >> n; std::string line; std::getline(std::cin, line); // 先吃掉残留的换行,否则下一行为空 std::getline(std::cin, line); // 这次才是真正的第一行内容 std::cout << line << '\n'; }排查这类问题时,先看std::cin.fail()和std::cin.eof()的状态位:解析失败后cin会进入失败状态,必须std::cin.clear()清除标志,再丢弃缓冲区里的残留字符,否则后续所有读入全部无效。
4.2 用 istringstream 把字符串转成数组
#include <sstream> #include <string> #include <vector> std::vector<int> parse_ints(const std::string& s) { std::vector<int> out; std::istringstream iss(s); std::string token; while (std::getline(iss, token, ',')) { // 以逗号为分隔符逐段取 if (!token.empty()) out.push_back(std::stoi(token)); } return out; }std::getline(iss, token, ',')的第三个参数是分隔符,不传则按换行切分。用while (iss >> token)则按空白切分,遇到逗号不会断开。两种方式按输入格式二选一,别混用。
4.3 stoi、stod 的参数顺序与异常类型
std::stoi的签名是stoi(const string&, size_t* pos = 0, int base = 10),第二个参数回传「解析停止的位置」,传nullptr表示不关心。
#include <string> #include <iostream> int main() { std::size_t pos = 0; std::string s = "42 3.14"; int a = std::stoi(s, &pos); // a = 42,pos = 2 double b = std::stod(s.substr(pos)); // 3.14 std::cout << a << ' ' << b << '\n'; try { int bad = std::stoi("abc"); // 抛 std::invalid_argument } catch (const std::invalid_argument&) { std::cout << "no digits\n"; } catch (const std::out_of_range&) { std::cout << "too large\n"; } }stoi("12abc")不抛异常,返回 12;stoi("999999999999")抛out_of_range。这两个行为差异是「字符串转数组」时最常见的静默 bug 来源。
4.4 to_string 与格式化输出
std::to_string(double)固定输出 6 位小数,需要控制精度就得回到流。
#include <iomanip> #include <sstream> #include <string> std::string fmt(double x) { std::ostringstream oss; oss << std::fixed << std::setprecision(3) << x; // 保留 3 位小数 return oss.str(); } std::string a = std::to_string(3.14159); // "3.141590" std::string b = fmt(3.14159); // "3.142"| 函数 | 失败行为 | 是否抛异常 |
|---|---|---|
| stoi / stol | 无数字抛 invalid_argument | 是 |
| stoi | 超出 int 范围抛 out_of_range | 是 |
| stoi("12abc") | 解析到非法字符即停 | 否 |
| stod("nan") | 返回 NaN | 否 |
| to_string | 无失败路径 | 否 |
setw只对下一次输出生效,需要每行都对齐就得在循环里重复调用,这一点和setprecision的粘性行为不同。
5. 把标准库函数用出边界:lambda 回调、编译环境与护栏
5.1 用 lambda 和 std::function 传回调
sort、for_each、transform都接受可调用对象。需要把回调存起来延迟调用时用std::function,只在当次调用中使用则直接用 lambda,代价更低。
#include <functional> #include <vector> void for_each_even(const std::vector<int>& v, const std::function<void(int)>& cb) { for (int x : v) if (x % 2 == 0) cb(x); } int main() { std::vector<int> v{1, 2, 3, 4}; int sum = 0; for_each_even(v, [&sum](int x) { sum += x; }); // 引用捕获,注意生命周期 // sum == 6 }捕获列表写成[&]时,回调一旦被存进std::function并延迟到原作用域之外执行,引用就悬空了。跨作用域的回调要么按值捕获,要么明确保证被引用对象的生存期长于回调。
5.2 VSCode 配置与 MSVC 运行库报错的排查顺序
「microsoft visual c++ 14.0 is required」这类报错要分成两种,处理方式完全不同:
| 报错时机 | 常见原因 | 处理方向 |
|---|---|---|
| 安装第三方包时报 14.0 required | 缺少 MSVC 编译工具链 | 安装对应版本的生成工具 |
| 编译期 fatal error C1083 | 找不到头文件 | 检查编译器路径与 includePath |
| 运行 exe 时缺 DLL | 缺少 Microsoft Visual C++ Redistributable | 安装对应架构的运行库 |
VSCode 的智能提示和实际编译是两条链路,c_cpp_properties.json只影响补全,改完仍报找不到头文件,要看tasks.json里编译器实际用的是哪个。
{ "configurations": [ { "name": "Win32", "includePath": ["${workspaceFolder}/**"], "compilerPath": "C:/msys64/ucrt64/bin/g++.exe", "cppStandard": "c++17", "intelliSenseMode": "windows-gcc-x64" } ], "version": 4 }includePath的优先级是「先匹配先生效」,把标准库路径写在自己项目路径前面,会导致同名头文件被错误解析。
5.3 用编译告警和 static_assert 给标准库调用加护栏
标准库的很多坑属于隐式转换:size_t和int相减、double窄化成int、const char*和std::string混比。把告警打开,编译器能替你抓出大部分。
g++ -std=c++17 -Wall -Wextra -Wconversion -Wshadow main.cpp -o main#include <cstddef> #include <vector> static_assert(sizeof(std::size_t) == 8, "该模块只在 64 位平台构建"); std::size_t idx = 0; int n = 10; // if (idx < n) ... // -Wsign-compare 会在这里报警-Wconversion专门盯窄化转换,-Wsign-compare盯有符号无符号比较,两者恰好覆盖了size()参与算术运算时最常出的错误。再写一个小的自检程序,把find返回npos、lower_bound在降序数组上的表现各跑一遍,比读十遍文档记得牢。
本文还有配套的精品资源,点击获取