Ch 16: std::tuple 和 std::optional
- 熟练使用 std::tuple 的构造和访问
- 掌握 C++17 结构化绑定的各种用法
- 深入理解 std::optional 的语义和 API
- 学会在函数中返回多值和可选值
- 理解 tuple 和 pair 的选择场景
16.1 Python tuple 回顾
Section titled “16.1 Python tuple 回顾”tuple 基础
Section titled “tuple 基础”# Python - tuple 是不可变序列t = (1, "hello", 3.14)
# 不可变性# t[0] = 2 # ❌ TypeError
# 解包x, y, z = tprint(x, y, z) # 1 hello 3.14
# 多返回值(返回 tuple)def divide(a, b): return a // b, a % b # 隐式返回 tuple
quotient, remainder = divide(10, 3)print(quotient, remainder) # 3 1
# 嵌套解包(a, (b, c)) = (1, (2, 3))tuple 作为字典 key
Section titled “tuple 作为字典 key”# tuple 可哈希,可用作字典 keypoints = {(0, 0): "origin", (1, 1): "diagonal"}
# 命名元组from collections import namedtuplePoint = namedtuple('Point', ['x', 'y'])p = Point(3, 4)print(p.x, p.y) # 3 416.2 C++ std::tuple 基础
Section titled “16.2 C++ std::tuple 基础”#include <tuple>#include <string>#include <iostream>
int main() { // 方式 1:直接构造 std::tuple<int, std::string, double> t1{1, "hello", 3.14};
// 方式 2:make_tuple(C++11,自动推断类型) auto t2 = std::make_tuple(2, "world", 2.71);
// 方式 3:C++17 列表初始化 std::tuple<int, std::string> t3{42, "answer"};
// 方式 4:std::tie(用于比较) int a = 1; std::string b = "x"; auto t4 = std::tie(a, b); // tuple<int&, string&>
// 注意:make_tuple 会按值存储,tie 会按引用存储
return 0;}#include <tuple>#include <string>#include <iostream>
int main() { auto t = std::make_tuple(1, "hello", 3.14);
// 按索引访问 int x = std::get<0>(t); // 1 std::string y = std::get<1>(t); // "hello" double z = std::get<2>(t); // 3.14
// 按类型访问(当类型唯一时) int a = std::get<int>(t); std::string b = std::get<std::string>(t);
// ❌ 如果类型不唯一,编译错误 // auto t2 = std::make_tuple(1, 2); // std::get<int>(t2); // ❌ 编译错误!两个 int
std::cout << x << " " << y << " " << z << std::endl;
return 0;}结构化绑定(C++17)
Section titled “结构化绑定(C++17)”#include <tuple>#include <string>#include <iostream>
int main() { auto t = std::make_tuple(1, "hello", 3.14);
// C++17 结构化绑定(最常用) auto [x, y, z] = t; std::cout << x << " " << y << " " << z << std::endl;
// 可以用 & 和 const const auto& [a, b, c] = t; // const 引用
// 注意:结构化绑定的变量不能直接作为函数参数模板 // 需要用 auto 或指定类型
return 0;}16.3 多返回值函数
Section titled “16.3 多返回值函数”#include <tuple>#include <utility>#include <iostream>
// 传统方式:使用输出参数bool divide_traditional(int a, int b, int& quotient, int& remainder) { if (b == 0) return false; quotient = a / b; remainder = a % b; return true;}
// 现代方式:返回 tuplestd::tuple<int, int> divide_tuple(int a, int b) { if (b == 0) return {0, 0}; // 或者 throw return {a / b, a % b};}
// C++17 最简洁std::pair<int, int> divide_pair(int a, int b) { if (b == 0) return {0, 0}; return {a / b, a % b};}
int main() { // 使用 tuple auto [quotient, remainder] = divide_tuple(10, 3); std::cout << quotient << " " << remainder << std::endl; // 3 1
// 使用 pair auto [q, r] = divide_pair(10, 3); std::cout << q << " " << r << std::endl; // 3 1
return 0;}实际应用场景
Section titled “实际应用场景”#include <tuple>#include <vector>#include <string>#include <iostream>#include <algorithm>
// 场景 1:返回多个值struct User { std::string name; int age; std::string email;};
std::tuple<User, bool> find_user(const std::vector<User>& users, const std::string& name) { auto it = std::find_if(users.begin(), users.end(), [&name](const User& u) { return u.name == name; }); if (it != users.end()) { return {*it, true}; } return {User{}, false};}
// 场景 2:返回多个统计数据struct Stats { int min; int max; double avg;};
Stats compute_stats(const std::vector<int>& nums) { if (nums.empty()) return {0, 0, 0.0}; int min = nums[0], max = nums[0]; long long sum = 0; for (int n : nums) { min = std::min(min, n); max = std::max(max, n); sum += n; } return {min, max, static_cast<double>(sum) / nums.size()};}
// 场景 3:交换操作template<typename T>std::tuple<T, T> swap_values(T a, T b) { return {b, a};}
int main() { std::vector<User> users = { {"Alice", 25, "alice@example.com"}, {"Bob", 30, "bob@example.com"} };
auto [user, found] = find_user(users, "Alice"); if (found) { std::cout << "Found: " << user.name << " (" << user.age << ")\n"; }
std::vector<int> nums = {5, 2, 8, 1, 9, 3}; auto [min_val, max_val, avg] = compute_stats(nums); std::cout << "Stats: min=" << min_val << ", max=" << max_val << ", avg=" << avg << "\n";
auto [new_a, new_b] = swap_values(10, 20); std::cout << new_a << " " << new_b << "\n"; // 20 10
return 0;}16.4 std::pair 详解
Section titled “16.4 std::pair 详解”pair vs tuple
Section titled “pair vs tuple”#include <utility>#include <tuple>#include <iostream>
int main() { // pair 是两个元素的 tuple std::pair<int, std::string> p{1, "one"};
// 访问 int x = p.first; // 1 std::string y = p.second; // "one"
// 结构化绑定 auto [a, b] = p;
// make_pair auto p2 = std::make_pair(2, "two");
// 比较(按 first 排序) std::pair<int, int> a1 = {1, 5}; std::pair<int, int> a2 = {1, 3}; if (a1 < a2) { // 比较 first,first 相同比较 second std::cout << "a1 < a2\n"; }
return 0;}pair 的实际应用
Section titled “pair 的实际应用”#include <utility>#include <map>#include <string>#include <iostream>#include <vector>
int main() { // 1. map 的元素类型就是 pair std::map<std::string, int> ages = { {"Alice", 25}, {"Bob", 30} };
for (const auto& [name, age] : ages) { std::cout << name << ": " << age << "\n"; }
// 2. 返回坐标 std::pair<int, int> find_center(int x1, int y1, int x2, int y2) { return {(x1 + x2) / 2, (y1 + y2) / 2}; }
// 3. 区间 std::vector<std::pair<int, int>> ranges = { {0, 10}, {20, 30}, {40, 50} };
// 检查一个值是否在某个区间 auto is_in_range = [](int val, auto& ranges) { for (const auto& [start, end] : ranges) { if (val >= start && val <= end) return true; } return false; };
std::cout << std::boolalpha; std::cout << "15 in range: " << is_in_range(15, ranges) << "\n"; // false std::cout << "25 in range: " << is_in_range(25, ranges) << "\n"; // true
return 0;}16.5 std::optional 详解
Section titled “16.5 std::optional 详解”optional 的概念
Section titled “optional 的概念”# Python - None 表示值不存在data = {"a": 1, "b": 2}
result = data.get("c") # Noneresult = data.get("c", 0) # 0(默认值)
if result is not None: print(result)#include <optional>#include <string>#include <iostream>
int main() { // optional 表示"可能有值,也可能没有"
// 1. 空 optional std::optional<int> empty_opt; // 空 std::optional<int> null_opt = std::nullopt; // 空(显式) std::optional<int> null_opt2{}; // 空
// 2. 有值的 optional std::optional<int> opt1 = 42; // 包含值 42 std::optional<int> opt2{42}; // 同上 std::optional<int> opt3 = std::make_optional(42);
// 3. 检查 if (opt1.has_value()) { std::cout << "opt1 has value: " << opt1.value() << "\n"; }
// 4. 更简洁的检查 if (opt1) { std::cout << "opt1 is truthy: " << *opt1 << "\n"; }
if (!empty_opt) { std::cout << "empty_opt is empty\n"; }
return 0;}optional 的访问方式
Section titled “optional 的访问方式”#include <optional>#include <iostream>#include <string>
int main() { std::optional<std::string> opt = "hello";
// 方式 1:解引用 std::string s1 = *opt; // "hello"
// 方式 2:value() std::string s2 = opt.value(); // "hello"
// 方式 3:value_or() std::string s3 = opt.value_or("default"); // "hello"
// 空 optional std::optional<std::string> empty;
// 解引用空 optional - 未定义行为! // *empty // ❌ 危险!
// value() 访问空 optional - 抛异常 // empty.value() // ❌ throw std::bad_optional_access
// value_or() 安全 std::string s4 = empty.value_or("default"); // "default"
// C++20: and_then(链式操作) std::optional<int> parse_int(const std::string& s) { try { return std::stoi(s); } catch (...) { return std::nullopt; } }
auto result = parse_int("42").and_then([](int x) { return x > 0 ? std::optional<int>(x * 2) : std::nullopt; });
return 0;}optional 作为返回值
Section titled “optional 作为返回值”#include <optional>#include <unordered_map>#include <string>#include <iostream>
// 场景:查找用户,可能找不到struct User { std::string name; int age;};
std::optional<User> find_user( const std::unordered_map<std::string, User>& users, const std::string& name) { auto it = users.find(name); if (it != users.end()) { return it->second; // 返回 User } return std::nullopt; // 用户不存在}
// 场景:解析,可能失败std::optional<int> parse_age(const std::string& s) { try { int age = std::stoi(s); if (age < 0 || age > 150) { return std::nullopt; // 超出合理范围 } return age; } catch (...) { return std::nullopt; // 解析失败 }}
int main() { std::unordered_map<std::string, User> users = { {"alice", {"Alice", 25}}, {"bob", {"Bob", 30}} };
// 查找用户 if (auto user = find_user(users, "alice")) { std::cout << "Found: " << user->name << "\n"; } else { std::cout << "User not found\n"; }
// C++17 if 初始化语句 if (auto user = find_user(users, "charlie"); !user) { std::cout << "charlie not found\n"; }
// 解析年龄 auto age1 = parse_age("25"); auto age2 = parse_age("abc"); auto age3 = parse_age("-5");
std::cout << "Age 1: " << (age1 ? std::to_string(*age1) : "invalid") << "\n"; std::cout << "Age 2: " << (age2 ? std::to_string(*age2) : "invalid") << "\n"; std::cout << "Age 3: " << (age3 ? std::to_string(*age3) : "invalid") << "\n";
return 0;}optional 和函数参数
Section titled “optional 和函数参数”#include <optional>#include <iostream>#include <string>
// optional 作为函数参数void print_name(const std::optional<std::string>& name) { if (name) { std::cout << "Name: " << *name << "\n"; } else { std::cout << "Name: [unknown]\n"; }}
// optional 参数配合默认值void greet(const std::string& name, const std::optional<std::string>& title = std::nullopt) { if (title) { std::cout << *title << " " << name << "\n"; } else { std::cout << "Hello, " << name << "\n"; }}
// C++17 if 初始化语句void print_name2(const std::optional<std::string>& name) { if (const auto& n = name; n.has_value()) { std::cout << "Name is: " << *n << "\n"; }}
int main() { print_name("Alice"); // Name: Alice print_name(std::nullopt); // Name: [unknown] print_name(std::optional<std::string>()); // Name: [unknown]
greet("Bob"); // Hello, Bob greet("Alice", "Dr."); // Dr. Alice
return 0;}16.6 结构化绑定详解
Section titled “16.6 结构化绑定详解”#include <tuple>#include <utility>#include <array>#include <iostream>
int main() { // tuple 解包 auto t = std::make_tuple(1, "hello", 3.14); auto [x, y, z] = t; // x=1, y="hello", z=3.14
// pair 解包 std::pair<int, std::string> p{42, "answer"}; auto [key, value] = p; // key=42, value="answer"
// 数组/结构体解包(C++ 结构体需要 public 成员) int arr[] = {10, 20, 30}; auto [a, b, c] = arr; // a=10, b=20, c=30
// 结构体 struct Point { double x, y; }; Point pt{3.0, 4.0}; auto [px, py] = pt; // px=3.0, py=4.0
std::cout << x << " " << y << " " << z << "\n"; std::cout << key << " " << value << "\n"; std::cout << a << " " << b << " " << c << "\n"; std::cout << px << " " << py << "\n";
return 0;}在循环中使用
Section titled “在循环中使用”#include <map>#include <vector>#include <string>#include <iostream>
int main() { // 遍历 map std::map<std::string, int> ages = { {"Alice", 25}, {"Bob", 30}, {"Charlie", 35} };
// C++17 结构化绑定(最推荐) for (const auto& [name, age] : ages) { std::cout << name << ": " << age << "\n"; }
// 用 & 修改 for (auto& [name, age] : ages) { if (name == "Alice") { age = 26; // 修改年龄 } }
// pair 数组 std::vector<std::pair<std::string, int>> scores = { {"Alice", 95}, {"Bob", 87} };
for (const auto& [player, score] : scores) { std::cout << player << ": " << score << "\n"; }
// 二维 vector std::vector<std::vector<int>> matrix = { {1, 2, 3}, {4, 5, 6} };
for (const auto& row : matrix) { for (int val : row) { std::cout << val << " "; } std::cout << "\n"; }
return 0;}结构化绑定的细节
Section titled “结构化绑定的细节”#include <tuple>#include <iostream>
int main() { // 结构化绑定创建隐藏的变量 auto [x, y, z] = std::make_tuple(1, 2, 3);
// x, y, z 的类型是推导出来的 // 相当于编译器生成了类似这样的代码: // struct { int _0; int _1; int _2; } __hidden = {1, 2, 3}; // auto& x = __hidden._0; // auto& y = __hidden._1; // auto& z = __hidden._2;
// 修饰符 const auto [a, b] = std::make_tuple(1, 2); // const auto& [c, d] = std::make_tuple(3, 4); // 引用(绑定到临时对象,注意生命周期)
// 不能混合 // auto& [e, f] = ... // e 是引用 // const auto& [g, h] = ... // g 是 const 引用
// 陷阱:引用绑定到临时对象 // auto& [x, y] = std::make_tuple(1, 2); // ❌ 危险! // make_tuple 返回临时对象,引用在语句结束时悬空
// 正确做法:用 auto 或 const auto const auto& [p, q] = std::make_tuple(1, 2); // OK,const 延长临时对象生命周期
std::cout << a << " " << b << "\n";
return 0;}16.7 tuple 的高级用法
Section titled “16.7 tuple 的高级用法”tuple 作为模板参数
Section titled “tuple 作为模板参数”#include <tuple>#include <vector>#include <iostream>
// tuple 作为函数参数包template<typename... Args>void print_all(Args... args) { ((std::cout << args << " "), ...); // C++17 fold expression std::cout << "\n";}
// 遍历 tupletemplate<typename Tuple, std::size_t... Is>void print_tuple_impl(const Tuple& t, std::index_sequence<Is...>) { ((std::cout << std::get<Is>(t) << " "), ...); std::cout << "\n";}
template<typename... Args>void print_tuple(const std::tuple<Args...>& t) { print_tuple_impl(t, std::index_sequence_for<Args...>{});}
// 连接 tupletemplate<typename... Tuples>auto concatenate(Tuples... tuples) { // C++17 简化实现 return std::tuple_cat(tuples...);}
int main() { print_all(1, 2.5, "hello", 'c');
auto t = std::make_tuple(1, 2.5, "hello"); print_tuple(t);
auto combined = concatenate( std::make_tuple(1, 2), std::make_tuple(3, 4.0), std::make_tuple("hello") ); // combined = (1, 2, 3, 4.0, "hello")
std::cout << std::get<0>(combined) << " " << std::get<4>(combined) << "\n"; // 1 hello
return 0;}tuple 和 std::apply
Section titled “tuple 和 std::apply”#include <tuple>#include <functional>#include <iostream>
int add(int a, int b) { return a + b; }int multiply(int a, int b) { return a * b; }
int main() { // std::apply - 把 tuple 的元素作为参数调用函数 auto result = std::apply(add, std::make_tuple(3, 4)); // 7 std::cout << result << "\n";
auto result2 = std::apply(multiply, std::make_tuple(3, 4)); // 12 std::cout << result2 << "\n";
// 结合 lambda auto process = [](int a, int b, int c) { return (a + b) * c; }; auto r = std::apply(process, std::make_tuple(1, 2, 3)); // (1+2)*3 = 9 std::cout << r << "\n";
return 0;}tuple 比较
Section titled “tuple 比较”#include <tuple>#include <iostream>
int main() { // tuple 可以比较(字典序) std::tuple<int, int, int> a = {1, 2, 3}; std::tuple<int, int, int> b = {1, 2, 4}; std::tuple<int, int, int> c = {1, 2, 3};
std::cout << std::boolalpha; std::cout << "a < b: " << (a < b) << "\n"; // true std::cout << "a == c: " << (a == c) << "\n"; // true
// 实际应用:版本比较 std::tuple<int, int, int> v1 = {1, 0, 0}; std::tuple<int, int, int> v2 = {1, 0, 1};
if (v1 < v2) { std::cout << "v1 is older\n"; }
return 0;}16.8 完整示例:配置解析器
Section titled “16.8 完整示例:配置解析器”#include <optional>#include <tuple>#include <string>#include <vector>#include <iostream>#include <sstream>#include <stdexcept>
// 解析配置行 "key=value" 或 "key:value"std::optional<std::pair<std::string, std::string>> parse_line(const std::string& line) { auto eq_pos = line.find('='); auto colon_pos = line.find(':');
std::size_t pos; char sep;
if (eq_pos != std::string::npos && colon_pos != std::string::npos) { // 两个都有,取更靠前的 if (eq_pos < colon_pos) { pos = eq_pos; sep = '='; } else { pos = colon_pos; sep = ':'; } } else if (eq_pos != std::string::npos) { pos = eq_pos; sep = '='; } else if (colon_pos != std::string::npos) { pos = colon_pos; sep = ':'; } else { return std::nullopt; // 没有分隔符 }
std::string key = line.substr(0, pos); std::string value = line.substr(pos + 1);
// 去除空白 auto trim = [](std::string& s) { auto start = s.find_first_not_of(" \t"); auto end = s.find_last_not_of(" \t"); if (start == std::string::npos) { s = ""; } else { s = s.substr(start, end - start + 1); } };
trim(key); trim(value);
if (key.empty()) return std::nullopt;
return std::make_pair(key, value);}
// 解析多个配置行std::tuple<std::vector<std::pair<std::string, std::string>>, std::vector<std::string>>parse_config(const std::string& config_text) { std::vector<std::pair<std::string, std::string>> valid; std::vector<std::string> errors;
std::istringstream stream(config_text); std::string line; int line_num = 0;
while (std::getline(stream, line)) { ++line_num;
// 跳过空行和注释 if (line.empty() || line[0] == '#' || line[0] == ';') { continue; }
if (auto parsed = parse_line(line)) { valid.push_back(*parsed); } else { errors.push_back("Line " + std::to_string(line_num) + ": invalid format - " + line); } }
return {valid, errors};}
int main() { std::string config = R"( # Configuration file hostname=localhost port: 8080 debug=true max_connections = 100 timeout = 30 invalid line another bad: line without value )";
auto [entries, errors] = parse_config(config);
std::cout << "=== Valid Entries ===\n"; for (const auto& [key, value] : entries) { std::cout << key << " = " << value << "\n"; }
std::cout << "\n=== Errors ===\n"; for (const auto& err : errors) { std::cout << err << "\n"; }
// 查找特定配置 auto find_config = [](const auto& entries, const std::string& key) -> std::optional<std::string> { for (const auto& [k, v] : entries) { if (k == key) return v; } return std::nullopt; };
if (auto port = find_config(entries, "port")) { std::cout << "\nPort: " << *port << "\n"; }
return 0;}| 类型 | 用途 | 关键操作 |
|---|---|---|
std::tuple<Args...> | 异构固定大小集合 | get<N>, 结构化绑定 auto [a, b, c] |
std::pair<T, U> | 两个元素的 tuple | .first, .second |
std::optional<T> | 可能存在或不存在的值 | has_value(), value(), value_or() |
std::nullopt | 表示 optional 为空 | opt = std::nullopt |
使用建议:
- 返回 2 个值 →
std::pair - 返回 3+ 个值 →
std::tuple - 可能不存在的值 →
std::optional - C++17 结构化绑定简化解包
下章预告:ch17 学习其他标准容器 std::array、std::deque、std::set 等。