Hi everyone! I'm AlanWu, a junior high school C++ learner. When I first started coding, I lived in two headers: #include <iostream> and #include <vector>. Everything else? I either didn't know it existed or assumed it was for "advanced programmers."
Turns out, the C++ standard library is full of tools that solve everyday problems in one line — I just never knew to look for them. Here are six that have saved me hours of writing unnecessary code.
My GitHub: https://github.com/Cn-Alanwu
1. <numeric> — Not Just Math, Actually Useful
Most beginners never include <numeric>. I didn't either — until I needed to compute a cumulative sum and wrote a 5-line loop. Then someone showed me:
#include <numeric>
#include <vector>
std::vector<int> v = {1, 2, 3, 4, 5};
// Sum all elements — no loop needed
int total = std::accumulate(v.begin(), v.end(), 0); // 15
// Fill with sequential values
std::iota(v.begin(), v.end(), 1); // v = {1, 2, 3, 4, 5} again
// GCD and LCM (C++17)
int g = std::gcd(12, 18); // 6
int l = std::lcm(12, 18); // 36
// Prefix sum — partial_sum produces {1, 3, 6, 10, 15}
std::vector<int> prefix(v.size());
std::partial_sum(v.begin(), v.end(), prefix.begin());
std::accumulate alone has probably saved me 50 lines of boilerplate. std::gcd and std::lcm replaced hand-written Euclidean algorithm functions I had copy-pasted across three projects.
2. <algorithm> — Beyond sort(), There's Gold
Everyone knows std::sort. But <algorithm> is massive, and some of its best tools are rarely taught:
#include <algorithm>
#include <vector>
std::vector<int> v = {5, 2, 9, 1, 5, 6};
// Find the 3rd smallest element (no full sort needed!)
std::nth_element(v.begin(), v.begin() + 2, v.end());
// v[2] is now the 3rd smallest; elements before are <=, after are >=
// All permutations (great for brute-force problems on Luogu!)
std::string s = "123";
do {
std::cout << s << std::endl; // prints 123, 132, 213, 231, 312, 321
} while (std::next_permutation(s.begin(), s.end()));
// Binary search on sorted ranges
bool found = std::binary_search(v.begin(), v.end(), 5);
// Rotate array elements
std::rotate(v.begin(), v.begin() + 2, v.end());
// {1, 2, 3, 4, 5} -> {3, 4, 5, 1, 2}
std::next_permutation is a cheat code for competitive programming brute-force problems. std::nth_element is O(n) — way faster than sorting the whole array when you only need one element.
3. <string_view> (C++17) — Avoid Unnecessary Copies
When you pass a string to a function, C++ usually copies it (or you use const std::string& and hope the caller has a std::string). What if the caller has a const char* or a substring?
#include <string_view>
#include <iostream>
// Works with std::string, const char*, and substrings — zero copies!
void print_first_word(std::string_view text) {
auto pos = text.find(' ');
std::cout << text.substr(0, pos) << std::endl;
}
int main() {
std::string s = "hello world";
print_first_word(s); // works
print_first_word("goodbye world"); // works — no allocation!
print_first_word(s.substr(0, 5) + "!"); // works on substring
}
std::string_view is a lightweight, non-owning view into a string. It's essentially a pointer + length. No heap allocation, no copying. For read-only string parameters, there's almost no reason to use const std::string& anymore.
4. <optional> (C++17) — Functions That Might Fail, Without Exceptions
How do you write a function that "might not have an answer"? Without <optional>, beginners often return sentinel values like -1 or empty strings:
#include <optional>
#include <string>
#include <vector>
// Clean: no sentinel values, no exceptions
std::optional<int> find_index(const std::vector<int>& v, int target) {
for (size_t i = 0; i < v.size(); i++) {
if (v[i] == target) return i;
}
return std::nullopt; // "no answer"
}
// Usage
auto idx = find_index({3, 1, 4, 1, 5}, 4);
if (idx) {
std::cout << "Found at: " << *idx << std::endl; // 2
} else {
std::cout << "Not found" << std::endl;
}
// Or use value_or() for defaults
int position = find_index({3, 1, 4, 1, 5}, 99).value_or(-1); // -1
This replaces the "is this return value valid?" guessing game with a type-safe approach. The compiler forces you to check before using the value.
5. <bitset> — Bit Manipulation Without Headaches
Bit operations are common in competitive programming (set representation, DP state compression, flags). The manual way with <<, >>, &, | is error-prone:
#include <bitset>
#include <iostream>
// Fixed-size bit array — operations read like English
std::bitset<8> bits("10110011");
std::cout << bits.count() << std::endl; // 5 (number of 1s)
std::cout << bits.test(3) << std::endl; // 0 (bit at position 3)
std::cout << bits.any() << std::endl; // true (at least one 1?)
bits.set(0, true); // set bit 0 to 1
bits.flip(2); // toggle bit 2
bits.reset(7); // set bit 7 to 0
std::cout << bits.to_ullong() << std::endl; // convert to unsigned long long
std::cout << bits.to_string() << std::endl; // back to string "00110011"
srd::bitset handles boundary checks and conversion, and the .count() method is internally optimized. For bit DP problems on Luogu, this is a lifesaver.
6. <tuple> + Structured Bindings (C++17) — Return Multiple Values Cleanly
Returning multiple values from a function used to mean either std::pair (only two values) or std::tuple with verbose std::get<>():
#include <tuple>
#include <iostream>
// Return three values naturally
std::tuple<int, double, std::string> get_stats() {
return {42, 3.14, "hello"};
}
int main() {
// C++17 structured binding — auto unpacking!
auto [count, pi, message] = get_stats();
std::cout << count << ", " << pi << ", " << message << std::endl;
// 42, 3.14, hello
// std::tie for existing variables (works in C++11)
int a;
double b;
std::string c;
std::tie(a, b, c) = get_stats();
}
Structured bindings work with structs, pairs, tuples, and arrays. Combined with std::map::insert (which returns pair<iterator, bool>), this pattern appears everywhere in modern C++.
Quick Reference
| Header | Must-Try |
|---|---|
<numeric> |
accumulate, gcd, iota, partial_sum
|
<algorithm> |
next_permutation, nth_element, rotate, binary_search
|
<string_view> |
Non-owning string parameter — zero-copy |
<optional> |
value_or(), has_value(), std::nullopt
|
<bitset> |
count(), set(), flip(), to_string()
|
<tuple> |
Structured bindings: auto [a, b, c] = ...
|
None of these are "advanced." They're just standard tools that most tutorials skip over because they'd rather teach you to write a swap function by hand. Next time you find yourself writing a loop for something that feels like it should be built-in — it probably is.
GitHub: https://github.com/Cn-Alanwu
Tags: #cpp #beginners #tutorial #programming #cpp17 #stl
C++ 标准库里的宝藏 — 6 个你可能忽略但超实用的头文件
大家好,我是 AlanWu,一名学 C++ 的初中生。刚入门的时候,我的代码基本只有两个头文件:#include <iostream> 和 #include <vector>。其他的?要么不知道存在,要么以为那是"大佬"才用的。
后来才发现,C++ 标准库里有大量一行代码就能解决日常需求的工具。下面这 6 个帮我省了无数手写代码的时间。
我的 GitHub:https://github.com/Cn-Alanwu
1. <numeric> — 不只是数学,是真的好用
初学者几乎没人 include <numeric>。我也是,直到有一次需要算前缀和,自己写了 5 行循环,然后别人给我看了这个:
#include <numeric>
#include <vector>
std::vector<int> v = {1, 2, 3, 4, 5};
// 求和 — 不用写循环
int total = std::accumulate(v.begin(), v.end(), 0); // 15
// 填连续值
std::iota(v.begin(), v.end(), 1); // v 恢复到 {1, 2, 3, 4, 5}
// 最大公约数和最小公倍数(C++17)
int g = std::gcd(12, 18); // 6
int l = std::lcm(12, 18); // 36
// 前缀和 — partial_sum 输出 {1, 3, 6, 10, 15}
std::vector<int> prefix(v.size());
std::partial_sum(v.begin(), v.end(), prefix.begin());
光 std::accumulate 一个函数就省掉了我至少 50 行样板代码。std::gcd 和 std::lcm 直接替代了我之前在三个项目里复制粘贴的手写欧几里得算法。
2. <algorithm> — 除了 sort(),宝藏太多了
大家都知道 std::sort。但 <algorithm> 这个头文件超大,里面很多好用的东西根本没人在入门教程里讲:
#include <algorithm>
#include <vector>
std::vector<int> v = {5, 2, 9, 1, 5, 6};
// 找第 3 小的元素(O(n),不用全排序!)
std::nth_element(v.begin(), v.begin() + 2, v.end());
// v[2] 现在是第 3 小,它左边的都 <= 它,右边的都 >= 它
// 全排列生成(洛谷暴力枚举题的作弊码!)
std::string s = "123";
do {
std::cout << s << std::endl; // 输出 123, 132, 213, 231, 312, 321
} while (std::next_permutation(s.begin(), s.end()));
// 有序区间的二分查找
bool found = std::binary_search(v.begin(), v.end(), 5);
// 旋转数组
std::rotate(v.begin(), v.begin() + 2, v.end());
// {1, 2, 3, 4, 5} → {3, 4, 5, 1, 2}
std::next_permutation 对于洛谷上那些需要枚举所有排列的暴力题简直是外挂。std::nth_element 只需要 O(n) —— 当你只需要某个位置的值时,比全排序快多了。
3. <string_view>(C++17)— 告别不必要的字符串拷贝
把字符串传给函数,C++ 默认会拷贝一份(要么你用 const std::string&,还得指望调用方刚好有 std::string)。如果调用方只有一个 const char* 或者子串呢?
#include <string_view>
#include <iostream>
// 能接受 std::string、const char*、子串 — 全部零拷贝!
void print_first_word(std::string_view text) {
auto pos = text.find(' ');
std::cout << text.substr(0, pos) << std::endl;
}
int main() {
std::string s = "hello world";
print_first_word(s); // OK
print_first_word("goodbye world"); // OK — 没有内存分配!
print_first_word(s.substr(0, 5) + "!"); // 子串也 OK
}
std::string_view 是一个轻量的、不拥有数据的字符串视图,本质上就是指针+长度。没有堆分配,没有拷贝。对于只需要读字符串的参数,几乎没有理由继续用 const std::string& 了。
4. <optional>(C++17)— 函数可能没结果,不用抛异常
怎么表示一个函数"可能没有答案"?没有 <optional> 的时候,新手通常返回哨兵值,比如 -1 或空字符串:
#include <optional>
#include <string>
#include <vector>
// 干净:不用哨兵值,不用异常
std::optional<int> find_index(const std::vector<int>& v, int target) {
for (size_t i = 0; i < v.size(); i++) {
if (v[i] == target) return i;
}
return std::nullopt; // 表示"没有答案"
}
// 使用
auto idx = find_index({3, 1, 4, 1, 5}, 4);
if (idx) {
std::cout << "找到,位置:" << *idx << std::endl; // 2
} else {
std::cout << "没找到" << std::endl;
}
// 或者用 value_or() 给默认值
int position = find_index({3, 1, 4, 1, 5}, 99).value_or(-1); // -1
它把"返回值到底有没有效"这个猜谜游戏变成了类型安全的检查。编译器会逼着你先判断再用,不会让你忘。
5. <bitset> — 位操作不头疼
位运算在算法竞赛里很常见(集合表示、DP 状态压缩、标记位)。手动用 <<、>>、&、| 写很容易出错:
#include <bitset>
#include <iostream>
// 固定大小的位数组 — 操作读起来像英语
std::bitset<8> bits("10110011");
std::cout << bits.count() << std::endl; // 5(1 的个数)
std::cout << bits.test(3) << std::endl; // 0(第 3 位是 0)
std::cout << bits.any() << std::endl; // true(至少一个 1?)
bits.set(0, true); // 第 0 位设为 1
bits.flip(2); // 第 2 位翻转
bits.reset(7); // 第 7 位清零
std::cout << bits.to_ullong() << std::endl; // 转成 unsigned long long
std::cout << bits.to_string() << std::endl; // 转回字符串 "00110011"
std::bitset 自动处理边界检查,.count() 方法内部是优化过的。洛谷上的状态压缩 DP 题,有它省心太多。
6. <tuple> + 结构化绑定(C++17)— 干净地返回多个值
函数返回多个值,以前要么用 std::pair(只能两个值),要么用 std::tuple 再配上一堆啰嗦的 std::get<>():
#include <tuple>
#include <iostream>
// 自然返回三个值
std::tuple<int, double, std::string> get_stats() {
return {42, 3.14, "hello"};
}
int main() {
// C++17 结构化绑定 — 自动解包!
auto [count, pi, message] = get_stats();
std::cout << count << ", " << pi << ", " << message << std::endl;
// 42, 3.14, hello
// std::tie 用于已有变量(C++11 就支持)
int a;
double b;
std::string c;
std::tie(a, b, c) = get_stats();
}
结构化绑定适用于 struct、pair、tuple 和数组。配合 std::map::insert(返回 pair<iterator, bool>),这个写法在现代 C++ 里随处可见。
速查表
| 头文件 | 必试函数 |
|---|---|
<numeric> |
accumulate、gcd、iota、partial_sum
|
<algorithm> |
next_permutation、nth_element、rotate、binary_search
|
<string_view> |
非拥有型字符串参数,零拷贝 |
<optional> |
value_or()、has_value()、std::nullopt
|
<bitset> |
count()、set()、flip()、to_string()
|
<tuple> |
结构化绑定:auto [a, b, c] = ...
|
这些东西没有一个是"高级"的,它们只是标准库里被大多数入门教程跳过的好工具——因为教程宁愿教你手写 swap 也不提这些。下次当你发现自己又在写循环实现某个功能,感觉这应该有人写好了的话 — 它大概率确实有人写好了,就在标准库里。
GitHub: https://github.com/Cn-Alanwu
Top comments (0)