第十一章 C++17 新特性详解

← 返回目录

本章把 C++17 的全部重要新特性集中讲一遍。 前面各章已经穿插了部分,这里系统汇总 + 补漏。

11.1 结构化绑定:拆解变量一把梭

std::map<std::string, int> m{{"a", 1}, {"b", 2}};
for (auto [key, value] : m) { ... }      // 遍历拆包
auto [x, y] = std::pair{3, 4};           // 拆 pair
auto [a, b, c] = std::tuple{1, 2.5, "s"}; // 拆 tuple
struct P { int x; int y; };
auto [px, py] = P{1, 2};                  // 拆 struct 成员

也可以配合引用修改原对象:

for (auto& [k, v] : m) v *= 2;

它是语法糖,本质还是声明变量。注意:不能拆"类里有 私有成员且不是聚合"的对象,以及不能拆 string(没有绑定协议)。

11.2 if / switch 初始化语句

if (auto it = m.find("x"); it != m.end()) {
    // 使用 it
}   // it 出了 if 就没了

switch (int v = get(); v) { ... }

好处:变量作用域收紧,不会"留着旧值"污染后面代码。

11.3 std::optional:可能没有值的值

#include <optional>
std::optional<int> find_max(const std::vector<int>& v) {
    if (v.empty()) return std::nullopt;   // 没有最大值
    return *std::max_element(v.begin(), v.end());
}

auto r = find_max(v);
if (r) {                       // 有值吗?
    std::println("最大值 {}", *r);
    std::println("最大值 {}", r.value_or(-1));  // 无值给默认
}

适用场景:函数"可能没有结果"(查找失败、空输入)。 比返回 -1 这种"魔法哨兵值"安全得多。

11.4 std::variant:安全的多类型联合

#include <variant>
std::variant<int, double, std::string> v;

v = 42;                 // 当前存 int
v = 3.14;               // 现在存 double
v = "hi";               // 现在存 string

// 取出来:
if (auto p = std::get_if<int>(&v)) {
    std::println("是整数 {}", *p);
}
// 或访问器(推荐):
std::visit([](const auto& x) { std::println("{}", x); }, v);

一句话:一个变量,多个类型轮流存,类型安全地访问。 比起 C 的 union(随意解释内存),variant 不会"看错类型"。

11.5 std::any:随便什么类型(尽量少用)

std::any a = 42;
a = std::string("hello");
int n = std::any_cast<int>(a);  // 类型不对会抛异常

用途:极少数"类型完全未知"的场合。能用 variant 就别用 any。

11.6 string_view(已在第六章讲过,回顾要点)

零拷贝的字符串视图,参数传它,别传 const string&(拷贝)。 **别返回悬空的 string_view**。

11.7 <filesystem>:现代文件系统操作

#include <filesystem>
namespace fs = std::filesystem;

fs::path p = "data/in.txt";
p.extension();          // ".txt"
p.stem();               // "in"
p.parent_path();        // "data"
fs::exists(p);          // 是否存在
fs::create_directories("a/b/c");  // 递归建目录
fs::copy(p, "backup.txt");
fs::remove("old.txt");

// 遍历目录(经典例子)
for (const auto& entry : fs::directory_iterator(".")) {
    if (entry.is_regular_file())
        std::println("{} ({} 字节)", entry.path().string(), entry.file_size());
}

可以递归遍历:fs::recursive_directory_iterator。 从此不再需要 system("ls") 之类的土办法。

11.8 if constexpr(第十章讲过,回顾)

编译期分支,模板编程必备。

if constexpr (std::is_pointer_v<T>) { ... }

11.9 折叠表达式(第十章讲过)

变参模板求和/执行:

template <typename... Ts>
auto sum(Ts... args) { return (args + ...); }   // 左折叠

11.10 inline 变量(编译多文件不再重复定义)

inline constexpr int MAX_SIZE = 1024;   // 头文件里也能放
inline int counter = 0;

没有 inline 的全局变量放在头文件里,多个 .cpp include 会 "重复定义"报错。inline 解决了(C++17)。

11.11 [[nodiscard]]:结果别扔了

[[nodiscard]] int check() { return 42; }
check();   // 编译器警告:返回值被丢弃!

给"调用后必须处理结果"的函数(检查、错误码)加上, 防止忘处理返回值。也常用于容器的 empty() 等。

11.12 其他 C++17 小特性(了解)

11.13 实战小结:C++17 风格代码长什么样

#include <print>
#include <filesystem>
#include <optional>
#include <vector>

std::optional<long long> file_size(const std::filesystem::path& p) {
    std::error_code ec;
    auto sz = std::filesystem::file_size(p, ec);   // 不抛异常版
    if (ec) return std::nullopt;
    return sz;
}

int main() {
    for (const auto& entry : std::filesystem::directory_iterator(".")) {
        if (auto sz = file_size(entry.path()); sz) {
            std::println("{} => {} 字节", entry.path().string(), *sz);
        }
    }
}

体会:optional 表达"可能失败",filesystem 表达"操作文件系统", if-with-initializer 收紧作用域——每个特性都让代码更短更安全。

本章小结

练习题


  1. 用 optional 写一个函数:从 vector<int> 里找最大值,空则无。
  2. 用 variant 存 int/string,用 visit 打印(不管存啥都能打印)。
  3. 遍历当前目录,把 .txt 文件复制到 backup 目录(filesystem)。
  4. 用结构化绑定遍历 map 并修改 value 翻倍。
  5. 写一个 clamp 的模板版本,让所有数值类型都能用。