今天写 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&。
结构化绑定与 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';
}
✍️ 本节练习
用 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& 保证只读不拷贝。
读入 n 个整数,用范围 for + auto& 把所有负数改成 0,输出修改后的数组。
输入 第一行 n;第二行 n 个整数。
输出 修改后的数组。
5
-1 2 -3 4 -50 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); // 编译期校验
异常: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';
}
✍️ 本节练习
写 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 编译期校验。
写函数 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 0caught: 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 写对的话抛异常不会泄漏资源。
本章小结
- auto 推导出确定类型,主要简化长类型;结构化绑定解包 pair。
- enum class 有作用域不隐式转 int;nullptr 是类型安全空指针。
- constexpr 编译期计算 + static_assert 编译期校验。
- 异常按 const 引用捕获;RAII + 异常配合不会泄漏资源。
🧩 本章综合练习
这几道题把本章多个知识点串起来,建议合上资料独立完成,再展开答案对照。
三件事一起做:用 constexpr 写编译期平方并用 static_assert 校验;用 std::map + 结构化绑定按字典序输出城市人口;写 safeDiv 在除数为 0 时抛 std::invalid_argument 并捕获。
输入 第一行两个整数(做安全除法,第二个不为 0);第二行两个整数(第二个为 0,触发异常)。
输出 先输出 144,再输出三行 城市 人口,再输出正常除法的结果(2 位小数),最后输出 caught: divide by zero。
10 4
7 0144
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 引用捕获避免拷贝。