Skip to content

Ch 16: std::tuple 和 std::optional

  • 熟练使用 std::tuple 的构造和访问
  • 掌握 C++17 结构化绑定的各种用法
  • 深入理解 std::optional 的语义和 API
  • 学会在函数中返回多值和可选值
  • 理解 tuple 和 pair 的选择场景
# Python - tuple 是不可变序列
t = (1, "hello", 3.14)
# 不可变性
# t[0] = 2 # ❌ TypeError
# 解包
x, y, z = t
print(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
points = {(0, 0): "origin", (1, 1): "diagonal"}
# 命名元组
from collections import namedtuple
Point = namedtuple('Point', ['x', 'y'])
p = Point(3, 4)
print(p.x, p.y) # 3 4
#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;
}
#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;
}
#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;
}
// 现代方式:返回 tuple
std::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;
}
#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;
}
#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;
}
#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;
}
# Python - None 表示值不存在
data = {"a": 1, "b": 2}
result = data.get("c") # None
result = 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;
}
#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;
}
#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;
}
#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;
}
#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;
}
#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;
}
#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;
}
#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";
}
// 遍历 tuple
template<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...>{});
}
// 连接 tuple
template<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;
}
#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;
}
#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;
}
#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 等。