DEV Community

Yilong Wu
Yilong Wu

Posted on

C++ Standard Library Hidden Gems — 6 Headers You're Probably Overlooking

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());
Enter fullscreen mode Exit fullscreen mode

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}
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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();
}
Enter fullscreen mode Exit fullscreen mode

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());
Enter fullscreen mode Exit fullscreen mode

std::accumulate 一个函数就省掉了我至少 50 行样板代码。std::gcdstd::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}
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

它把"返回值到底有没有效"这个猜谜游戏变成了类型安全的检查。编译器会逼着你先判断再用,不会让你忘。


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"
Enter fullscreen mode Exit fullscreen mode

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();
}
Enter fullscreen mode Exit fullscreen mode

结构化绑定适用于 struct、pair、tuple 和数组。配合 std::map::insert(返回 pair<iterator, bool>),这个写法在现代 C++ 里随处可见。


速查表

头文件 必试函数
<numeric> accumulategcdiotapartial_sum
<algorithm> next_permutationnth_elementrotatebinary_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)