ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

C++ 常用标准库函数实战避坑:string、vector、algorithm

C++ 常用标准库函数实战避坑:string、vector、algorithm 简介这是一份面向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、appendvector 的 push_back、resize、reservealgorithm 的 sort、lower_bound、unique。背下来不难难的是换成一道真实任务——读一行逗号分隔的数字塞进数组、按结构体某个字段倒序排、在有序数组里找第一个不小于 x 的位置——手就停了或者写出来编译能过、跑起来结果不对。围绕「C 常用标准库函数」的梳理要解决的就是这个落差。按string、vector、map、algorithm、sstream这几个日常出场率最高的头文件把真正高频的那批函数挑出来说清它在哪个头文件、参数怎么传、返回值什么时候是坑、失败时该看哪一行。从刚配好 VSCode C/C 环境、还在纠结c_cpp_properties.json的新手到工作几年仍然频繁查 cppreference 的老手都能在这里找到一两条自己没注意过的边界find返回的npos不能和-1比reserve不改 sizesort的比较器写会在特定数据上直接崩。2. string 与 vector 的常用函数从初始化到容量控制这两个头文件承担了日常代码里八成以上的调用量也集中了一半以上的运行时错误。它们的共同点是函数名都很好记参数含义却容易记反。2.1 string 上最高频的四个函数find、substr、size、appendfind返回的是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只动 capacityresize只动 size。#include vector #include iostream int main() { std::vectorint v; v.reserve(1000); // capacity 至少 1000size 仍是 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) 24sizeof(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_mapoperator[] 和 find 的取舍operator[]在键不存在时会默认构造一个值并插入所以「只读查询」绝不能用它否则容器会被悄悄改大。#include map #include string std::mapstd::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 / lengthstring字符个数O(1)与 int 混算出现负数findstring首次出现下标拿 npos 和 -1 比较substrstring子串pos 越界抛异常reservevector仅 capacity以为能直接下标赋值resizevector仅 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::vectorStudent 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::vectorint 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)找第一个 xupper_bound同上O(log n)找第一个 xunique相邻重复才合并O(n)必须先排序remove_if无序要求O(n)配合 erase 删除降序数组上直接调lower_bound得到的是无意义的结果——它不会报错只是默默给出错误答案查错时优先确认这一点。降序容器要显式传std::greaterint()。3.3 unique erase 去重链以及 count_if、find_ifunique只合并相邻的重复元素所以排序是必要前置。它返回新的逻辑尾容器长度并不改变必须再配合erase才真正删除。#include algorithm #include vector std::vectorint 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::vectorint parse_ints(const std::string s) { std::vectorint 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 42pos 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)不抛异常返回 12stoi(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::vectorint v, const std::functionvoid(int) cb) { for (int x : v) if (x % 2 0) cb(x); } int main() { std::vectorint 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: c17, intelliSenseMode: windows-gcc-x64 } ], version: 4 }includePath的优先级是「先匹配先生效」把标准库路径写在自己项目路径前面会导致同名头文件被错误解析。5.3 用编译告警和 static_assert 给标准库调用加护栏标准库的很多坑属于隐式转换size_t和int相减、double窄化成int、const char*和std::string混比。把告警打开编译器能替你抓出大部分。g -stdc17 -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在降序数组上的表现各跑一遍比读十遍文档记得牢。本文还有配套的精品资源点击获取
返回列表