← C++ 教材目录(共 13 章)

第 9 章 现代 C++ 特性

red wenzi · 2026-09-15 · 编程语言 · C++ · 📖 预计阅读 21 分钟 · 共 5 道练习
🎯 本章你会学到:auto、结构化绑定、constexpr、异常处理。建议边读边敲代码,每节的练习先自己做,再展开答案对照。

今天写 C++,没必要停留在“带类的 C”。C++11 之后的一系列改进让代码更短、更安全、也更快。本章目标不是让你背,而是让你知道遇到问题时有这些工具。

9.1 auto 与结构化绑定

auto 不是弱类型——它推导出的类型是确定的,只是不用你写出来。

auto:简化长类型名

auto n = 42; 推导为 int。主要价值是简化迭代器和长类型名:for (auto it = m.begin(); ...) 比手写 std::map<std::string, int>::iterator 舒服得多。遍历 vector 想改元素用 auto&,只读用 const auto&。

⚠️ 易错点 类型不明显时不要滥用:auto x = compute(); 你就不知道 x 到底是什么了。

结构化绑定与 if 初始化语句

for (const auto& [key, value] : m) 把 pair 解包成两个变量,遍历 map 和写 Dijkstra 时特别常用。if (auto it = m.find("a"); it != m.end()) 把变量的作用域限制在 if 内部,避免污染外层。

示例代码
std::map<std::string, int> m{{"Jinan", 900}, {"Shanghai", 2400}};
for (const auto& [city, pop] : m) {
    std::cout << city << ": " << pop << '\n';
}
if (auto it = m.find("Jinan"); it != m.end()) {   // C++17 if 初始化
    std::cout << it->second << '\n';
}
⚠️ 易错点 enum class 不会隐式转 int(int bad = c; 编译错误),这是它的价值;nullptr 是类型安全的空指针;using 比 typedef 更可读。

✍️ 本节练习

9.1.1必做城市人口表

用 std::map 存三个城市的人口,用结构化绑定遍历输出“城市: 人口”。

输入 无

输出 三行,按 map 键序(字典序)。

样例输入
(本题无输入)
样例输出
Jinan: 900
Shanghai: 2400
Taiyuan: 530

💡 提示 for (const auto& [city, pop] : m)。

✅ 查看参考答案与解析
#include <iostream>
#include <map>
#include <string>
int main() {
    std::map<std::string, int> m{
        {"Jinan", 900}, {"Taiyuan", 530}, {"Shanghai", 2400}};
    for (const auto& [city, pop] : m) {
        std::cout << city << ": " << pop << '\n';
    }
    return 0;
}

解析 结构化绑定把 pair 解包成两个变量,const auto& 保证只读不拷贝。

9.1.2挑战auto 引用改值

读入 n 个整数,用范围 for + auto& 把所有负数改成 0,输出修改后的数组。

输入 第一行 n;第二行 n 个整数。

输出 修改后的数组。

样例输入
5
-1 2 -3 4 -5
样例输出
0 2 0 4 0

💡 提示 for (auto& x : v) 才能改原元素。

✅ 查看参考答案与解析
#include <iostream>
#include <vector>
int main() {
    int n;
    std::cin >> n;
    std::vector<int> v(n);
    for (int i = 0; i < n; ++i) { std::cin >> v[i]; }
    for (auto& x : v) { if (x < 0) { x = 0; } }
    for (size_t i = 0; i < v.size(); ++i) {
        std::cout << v[i] << (i + 1 == v.size() ? '\n' : ' ');
    }
    return 0;
}

解析 auto& 推导为 int&,直接修改原元素;漏掉 & 就只改拷贝。

9.2 constexpr 与异常处理

constexpr 是“能算就算”的思维;异常是“错误沿着调用栈向上报”的机制。两者配合 RAII,是 C++ 里最省心的组合。

constexpr:把计算放到编译期

constexpr int factorial(int n) 在输入编译期已知时,编译期就算出结果,运行时直接读值。static_assert(factorial(5) == 120) 编译期断言。编译期结果还能当数组长度、模板参数。

示例代码
constexpr int factorial(int n) {
    return n <= 1 ? 1 : n * factorial(n - 1);
}
static_assert(factorial(5) == 120);   // 编译期校验
⚠️ 易错点 同一个 constexpr 函数也能在运行期调用(传运行期变量),一个函数两用。

异常:throw / try / catch

double divide(double a, double b) { if (b == 0.0) throw std::invalid_argument("divide by zero"); return a / b; }。catch 按 const 引用捕获避免拷贝;更通用的异常(std::exception)要写在后面当兜底。

示例代码
try {
    std::cout << divide(1.0, 0.0) << '\n';
}
catch (const std::invalid_argument& e) {
    std::cout << "caught: " << e.what() << '\n';
}
⚠️ 易错点 抛异常时栈展开会自动调用局部对象的析构函数——RAII 写对的话,异常不会造成资源泄漏。写数据结构时:内部用 assert 暴露 bug,对外接口用异常报告错误。

✍️ 本节练习

9.2.1必做编译期阶乘

写 constexpr 阶乘函数,用 static_assert 验证 factorial(5) == 120,然后输出 factorial(6)。

输入 无

输出 720

样例输入
(本题无输入)
样例输出
720

💡 提示 constexpr int factorial(int n) { return n <= 1 ? 1 : n * factorial(n - 1); }

✅ 查看参考答案与解析
#include <iostream>
constexpr int factorial(int n) {
    return n <= 1 ? 1 : n * factorial(n - 1);
}
static_assert(factorial(5) == 120);
int main() {
    std::cout << factorial(6) << '\n';
    return 0;
}

解析 编译期就算出结果,运行时直接读值;static_assert 编译期校验。

9.2.2挑战除法保护

写函数 double divide(double a, double b),b == 0 时 throw std::invalid_argument。main 里读 a、b,try-catch 捕获:正常输出结果,除零输出 caught: divide by zero。

输入 一行两个数 a b。

输出 结果(保留 2 位小数)或 caught: divide by zero。

样例输入
10 0
样例输出
caught: divide by zero

💡 提示 #include <stdexcept>;catch (const std::invalid_argument& e)。

✅ 查看参考答案与解析
#include <iomanip>
#include <iostream>
#include <stdexcept>
double divide(double a, double b) {
    if (b == 0.0) { throw std::invalid_argument("divide by zero"); }
    return a / b;
}
int main() {
    double a, b;
    std::cin >> a >> b;
    try {
        std::cout << std::fixed << std::setprecision(2) << divide(a, b) << '\n';
    }
    catch (const std::invalid_argument& e) {
        std::cout << "caught: " << e.what() << '\n';
    }
    return 0;
}

解析 异常按 const 引用捕获,避免拷贝;RAII 写对的话抛异常不会泄漏资源。

本章小结

🧩 本章综合练习

这几道题把本章多个知识点串起来,建议合上资料独立完成,再展开答案对照。

9.A综合编译期计算 + 结构化绑定 + 异常处理

三件事一起做:用 constexpr 写编译期平方并用 static_assert 校验;用 std::map + 结构化绑定按字典序输出城市人口;写 safeDiv 在除数为 0 时抛 std::invalid_argument 并捕获。

输入 第一行两个整数(做安全除法,第二个不为 0);第二行两个整数(第二个为 0,触发异常)。

输出 先输出 144,再输出三行 城市 人口,再输出正常除法的结果(2 位小数),最后输出 caught: divide by zero

样例输入
10 4
7 0
样例输出
144
Jinan 900
Shanghai 2400
Taiyuan 530
2.50
caught: divide by zero

💡 提示 结构化绑定写 for (const auto& [city, pop] : m);异常用 const 引用捕获。

✅ 查看参考答案与解析
#include <iomanip>
#include <iostream>
#include <map>
#include <stdexcept>
#include <string>

constexpr int square(int x) { return x * x; }
static_assert(square(12) == 144, "square(12) must be 144");   // 编译期校验

double safeDiv(double a, double b) {
    if (b == 0.0) { throw std::invalid_argument("divide by zero"); }
    return a / b;
}

int main() {
    int a1{}, b1{}, a2{}, b2{};
    std::cin >> a1 >> b1 >> a2 >> b2;

    std::cout << square(12) << '\n';

    std::map<std::string, int> pop{          // map 自动按字典序排列
        {"Shanghai", 2400}, {"Jinan", 900}, {"Taiyuan", 530}};
    for (const auto& [city, p] : pop) { std::cout << city << ' ' << p << '\n'; }

    std::cout << std::fixed << std::setprecision(2);
    std::cout << safeDiv(a1, b1) << '\n';
    try {
        std::cout << safeDiv(a2, b2) << '\n';  // 除零:抛异常
    }
    catch (const std::invalid_argument& e) {
        std::cout << "caught: " << e.what() << '\n';
    }
    return 0;
}

解析 static_assert 在编译期就拦下错误,比运行时 assert 更早;异常按 const 引用捕获避免拷贝。

📚 本文概念都在知识大全:

现代 C++ · auto · 结构化绑定 · constexpr · 异常处理