C++17 是一个大版本,引入了很多实用特性,被称为「最舒服的 C++ 版本」。
最常用的特性之一,一键解包 tuple/struct/数组:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| // 解包 pair
std::pair<int, std::string> getPerson() { return {25, "Tom"}; }
auto [age, name] = getPerson();
// 解包 tuple
auto [a, b, c] = std::make_tuple(1, 2.5, "hello");
// 解包 struct
struct Point { int x; int y; };
Point p{10, 20};
auto [x, y] = p;
// 解包数组
int arr[] = {1, 2, 3};
auto [a, b, c] = arr;
|
结合 map 遍历超好用:
1
2
3
4
| std::map<int, std::string> m = {{1, "a"}, {2, "b"}};
for (const auto& [key, value] : m) {
std::cout << key << ": " << value << std::endl;
}
|
实战(Q347 前 K 个高频元素):
1
2
3
4
5
6
7
8
9
| // 传统写法:p.first / p.second 访问键值对
for (auto& p : u_map) {
vec.push_back({p.second, p.first}); // p.first 是数字,p.second 是次数
}
// 结构化绑定写法:一行解包,见名知意
for (auto& [num, cnt] : u_map) {
vec.push_back({cnt, num});
}
|
注意:LeetCode 默认 C++17,可以直接用 auto [num, cnt];本地老编译器(C++14 及以下)不支持。
在 if 条件里定义变量,缩小作用域:
1
2
3
4
5
6
7
8
9
10
11
| // C++17 写法
if (auto it = m.find(key); it != m.end()) {
// 使用 it
}
// it 在这里已经不可见了
// switch 同理
switch (int val = getValue(); val) {
case 1: ...
case 2: ...
}
|
解决了「全局变量在头文件里定义导致多重定义」的老问题:
1
2
3
4
5
6
7
| // 头文件中,C++17 之前会报重复定义
inline constexpr int MAX_SIZE = 1024;
// 静态成员变量也可以直接在类内初始化
class MyClass {
inline static int count = 0; // C++17 不需要在 .cpp 里再定义一次
};
|
简化可变参数模板的展开:
1
2
3
4
5
6
7
8
9
10
11
12
13
| // 对所有参数求和
template<typename... Args>
auto sum(Args... args) {
return (... + args); // 一元右折叠:((a1 + a2) + a3) + ...
}
sum(1, 2, 3, 4); // = 10
// 带初始值的折叠
template<typename... Args>
void printAll(Args... args) {
(std::cout << ... << args) << std::endl;
}
|
四种折叠形式:
(... op args) 一元右折叠(args op ...) 一元左折叠(init op ... op args) 二元右折叠(args op ... op init) 二元左折叠
C++17 中 constexpr 可以用的地方更多了:
编译期条件判断,替代部分 SFINAE 写法:
1
2
3
4
5
6
7
8
| template<typename T>
auto getValue(T x) {
if constexpr (std::is_integral_v<T>) {
return x + 1;
} else {
return x;
}
}
|
lambda 可以在编译期执行:
1
2
| constexpr auto square = [](int n) { return n * n; };
static_assert(square(5) == 25); // 编译期计算
|
构造模板类时不用写模板参数,编译器自动推导:
1
2
3
4
5
6
7
| // C++11/14:必须写参数
std::pair<int, double> p1(1, 2.5);
std::vector<int> v1{1, 2, 3};
// C++17:自动推导
std::pair p2(1, 2.5); // pair<int, double>
std::vector v2{1, 2, 3}; // vector<int>
|
表示「可能有值,可能为空」,替代指针判空:
1
2
3
4
5
6
7
8
9
10
11
12
| std::optional<int> findNum(int x) {
if (x > 0) return x;
return std::nullopt; // 空
}
auto res = findNum(5);
if (res) {
std::cout << *res << std::endl;
}
// 有值返回,没值返回默认
int val = findNum(-1).value_or(0);
|
类型安全的 union,访问时用 std::visit:
1
2
3
4
5
6
7
8
| std::variant<int, double, std::string> v;
v = 42;
v = 3.14;
v = "hello";
std::visit([](auto&& arg) {
std::cout << arg << std::endl;
}, v);
|
可以存任意类型的容器,类似 void* 但类型安全:
1
2
3
4
5
6
| std::any a = 42;
a = std::string("hello");
if (a.type() == typeid(std::string)) {
std::cout << std::any_cast<std::string>(a) << std::endl;
}
|
字符串的「视图」,不持有数据,零拷贝:
1
2
3
4
5
6
| void print(std::string_view sv) { // 传 const char* 或 std::string 都不用拷贝
std::cout << sv << std::endl;
}
print("hello"); // OK
print(std::string("hi")); // OK
|
注意: string_view 不拥有数据,源字符串销毁后就悬空了。
终于有了标准的文件操作库:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| #include <filesystem>
namespace fs = std::filesystem;
// 创建目录
fs::create_directory("test");
// 遍历目录
for (auto& entry : fs::directory_iterator(".")) {
std::cout << entry.path() << std::endl;
}
// 文件大小、路径操作
auto size = fs::file_size("test.txt");
auto ext = fs::path("test.txt").extension();
|
| 特性 | 说明 |
|---|
[[nodiscard]] | 标记返回值不能忽略,编译器会警告 |
[[maybe_unused]] | 标记可能不用的变量,消除警告 |
[[fallthrough]] | switch 中显式标注故意 fall-through |
| 十六进制浮点字面量 | 0x1.2p3 |
std::as_const | 把左值转成 const 引用 |
std::clamp | 把值限制在区间内 clamp(val, min, max) |
1
2
3
| // [[nodiscard]] 示例
[[nodiscard]] int compute() { return 42; }
compute(); // 警告:返回值被忽略
|
C++17 是日常开发提升幸福感最多的版本:
- 结构化绑定 + if-init:代码更简洁
- optional/variant/any:类型安全三件套
- string_view:字符串处理性能提升
- filesystem:终于不用自己写文件操作了
- constexpr if:模板元编程变得可读