C++17新特性精讲:结构化绑定、if constexpr与实用类型
本文摘要
本篇讲解了 if/switch 初始化语句、结构化绑定、if constexpr 编译期分支、std::optional/variant/any 三种实用类型、string_view…
本文是 C++ 系列教程的第 21 篇。上一篇讲解了 C++11/14 新特性,本篇深入 C++17 新特性:if/switch 初始化语句、结构化绑定、if constexpr、std::optional/variant/any、string_view、filesystem 完整实践、[[nodiscard]]。
一、if/switch 初始化语句
1.1 if 初始化语句
C++17 允许在 if/switch 条件中声明变量,作用域限制在语句块内:
#include <iostream>#include <map>#include <string>using namespace std;
int main() { map<string, int> scores = {{"张三", 88}, {"李四", 92}};
// C++17:if 初始化语句 if (auto it = scores.find("张三"); it != scores.end()) { cout << "找到: " << it->first << " = " << it->second << endl; } else { cout << "未找到" << endl; } // it 在 if-else 块外不可见
// 对比旧写法(变量泄漏到外层作用域) auto it2 = scores.find("李四"); if (it2 != scores.end()) { cout << "找到: " << it2->second << endl; } return 0;}1.2 switch 初始化语句
#include <iostream>#include <string>using namespace std;
enum class Command { Start, Stop, Pause };
Command parseCommand(const string &s) { if (s == "start") return Command::Start; if (s == "stop") return Command::Stop; return Command::Pause;}
int main() { // switch 初始化:变量只在 switch 内有效 switch (auto cmd = parseCommand("stop"); cmd) { case Command::Start: cout << "启动" << endl; break; case Command::Stop: cout << "停止" << endl; break; case Command::Pause: cout << "暂停" << endl; break; } // cmd 在 switch 外不可见 return 0;}二、结构化绑定
2.1 基本用法
结构化绑定(Structured Binding)可以一次性解包 pair、tuple、结构体等:
#include <iostream>#include <map>#include <tuple>#include <string>using namespace std;
int main() { // 解包 pair pair<string, int> person{"张三", 25}; auto [name, age] = person; cout << name << " " << age << endl; // 张三 25
// 解包 tuple tuple<int, string, double> data(1, "Alice", 88.5); auto [id, n, score] = data; cout << id << " " << n << " " << score << endl;
// 解包数组 int arr[3] = {10, 20, 30};
auto [a, b, c] = arr; cout << a << " " << b << " " << c << endl; // 10 20 30 return 0;}2.2 遍历 map 的优雅写法
#include <iostream>#include <map>#include <string>using namespace std;
int main() { map<string, int> wordCount = { {"cpp", 3}, {"python", 2}, {"java", 1} };
// 旧写法:kv.first / kv.second for (const auto &kv : wordCount) { cout << kv.first << ": " << kv.second << endl; }
cout << "---" << endl;
// C++17 结构化绑定 for (const auto &[word, count] : wordCount) { cout << word << ": " << count << endl; } return 0;}2.3 解包引用(可修改)
#include <iostream>#include <map>#include <string>using namespace std;
int main() { map<string, int> data = {{"a", 1}, {"b", 2}};
// 用引用解包,可以修改 for (auto &[key, value] : data) { value *= 10; }
for (const auto &[k, v] : data) { cout << k << " = " << v << endl; // a = 10, b = 20 } return 0;}三、if constexpr
3.1 编译期条件分支
if constexpr 在编译期求值并丢弃不满足的分支:
#include <iostream>#include <type_traits>#include <string>using namespace std;
template <typename T>void process(const T &value) { // 编译期分支:只编译满足条件的分支 if constexpr (is_integral_v<T>) { cout << "[整数] " << value << endl; } else if constexpr (is_floating_point_v<T>) { cout << "[浮点] " << value << endl; } else if constexpr (is_same_v<T, string>) { cout << "[字符串] " << value << endl; } else { cout << "[其他类型]" << endl; }}
int main() { process(42); // [整数] 42 process(3.14); // [浮点] 3.14 process(string("hi")); // [字符串] hi process(true); // [整数] 1(bool 是整型) return 0;}3.2 与模板递归结合
#include <iostream>using namespace std;
// 变参打印:用 if constexpr 简化递归template <typename T>void printOne(const T &value) { cout << value << " ";}
template <typename First, typename... Rest>void printAll(First first, Rest... rest) { cout << first << " "; if constexpr (sizeof...(Rest) > 0) { printAll(rest...); // 还有剩余参数才递归 } else { cout << endl; // 最后一个参数结束 }}
int main() { printAll(1, 2.5, "hello", 'x'); // 1 2.5 hello x printAll(42); // 42 return 0;}四、std::optional
4.1 optional 的用途
optional 表示可能不存在的值,替代返回 -1 或 nullptr 的惯用法:
#include <iostream>#include <optional>#include <string>using namespace std;
// 查找:可能找不到optional<int> findValue(const int *arr, int size, int target) { for (int i = 0; i < size; i++) { if (arr[i] == target) return arr[i]; } return nullopt; // 表示不存在}
int main() { int data[] = {3, 1, 4, 1, 5};
auto result = findValue(data, 5, 4); if (result.has_value()) { cout << "找到: " << *result << endl; // 4 } else { cout << "未找到" << endl; }
auto missing = findValue(data, 5, 99); // value_or:不存在时给默认值 cout << "结果: " << missing.value_or(-1) << endl; // -1 return 0;}4.2 optional 成员函数
#include <iostream>#include <optional>using namespace std;
int main() { optional<int> a = 42; optional<int> b; // 空 optional<int> c = nullopt; // 显式空
cout << "a 有值: " << a.has_value() << endl; // 1 cout << "b 有值: " << b.has_value() << endl; // 0
cout << "a 的值: " << a.value() << endl; // 42 cout << "b 默认值: " << b.value_or(100) << endl; // 100
// 解引用 if (a) { cout << "a 存在: " << *a << endl; }
// 重置 a.reset(); cout << "重置后: " << a.has_value() << endl; // 0
// 重新赋值 a = 99; cout << "重新赋值: " << *a << endl; // 99 return 0;}五、std::variant
5.1 variant 联合类型
variant 是类型安全的联合体,可他存储多种类型之一:
#include <iostream>#include <variant>#include <string>using namespace std;
int main() { // 可他存 int、double、string 之一 variant<int, double, string> v;
v = 42; // 当前是 int cout << "int: " << get<int>(v) << endl; // 42
v = 3.14; // 切换到 double cout << "double: " << get<double>(v) << endl; // 3.14
v = "hello"; // 切换到 string cout << "string: " << get<string>(v) << endl; // hello
// 类型安全:访问错误类型会抛异常 try { v = 42; get<string>(v); // 错误!当前是 int } catch (const bad_variant_access &e) { cout << "类型访问错误: " << e.what() << endl; } return 0;}5.2 variant 安全访问
#include <iostream>#include <variant>#include <string>using namespace std;
// 访问者模式struct Visitor { void operator()(int value) const { cout << "整数: " << value << endl; } void operator()(double value) const { cout << "浮点: " << value << endl; } void operator()(const string &value) const { cout << "字符串: " << value << endl; }};
int main() { variant<int, double, string> v;
// 用访问者安全访问 v = 100; visit(Visitor{}, v); // 整数: 100
v = 2.5; visit(Visitor{}, v); // 浮点: 2.5
v = "hi"; visit(Visitor{}, v); // 字符串: hi
// 用 lambda 访问 v = 42; visit([](auto &&value) { cout << "值: " << value << endl; }, v); return 0;}六、std::any
6.1 any 任意类型
any 可以存储任意类型的值(类似 void* 但类型安全):
#include <iostream>#include <any>#include <string>using namespace std;
int main() { any value;
value = 42; // 存 int cout << "int: " << any_cast<int>(value) << endl; // 42
value = string("hello"); // 存 string cout << "string: " << any_cast<string>(value) << endl; // hello
value = 3.14; // 存 double cout << "double: " << any_cast<double>(value) << endl; // 3.14
// 检查类型 value = 42; if (value.type() == typeid(int)) { cout << "是 int 类型" << endl; }
// 安全转换 auto ptr = any_cast<string>(&value); // 失败返回 nullptr if (ptr) { cout << *ptr << endl; } else { cout << "不是 string 类型" << endl; } return 0;}6.2 三种实用类型对比
| 类型 | 用途 | 适用 |
|---|---|---|
| optional | 值可能存在或不存在 | 查找、返回值 |
| variant<A,B,C> | 值是一组类型之一 | 联合数据、状态 |
| any | 值可以是任意类型 | 配置、动态数据 |
七、string_view
7.1 避免不必要的拷贝
string_view 是只读字符串视图,� ��拥有内存,避免拷贝:
#include <iostream>#include <string_view>#include <string>using namespace std;
// 旧写法:const string& 仍可能触发临时对象void oldPrint(const string &s) { cout << s << endl;}
// 新写法:string_view 零拷贝void newPrint(string_view sv) { cout << sv << endl;}
int main() { string full = "Hello World"; const char *cstr = "C-style";
// string_view 可以直接接收各种来源 newPrint(full); // string newPrint(cstr); // const char* newPrint("literal"); // 字符串字面量
// 子串视图(零拷贝) string_view view(full); string_view sub = view.substr(6, 5); cout << "子串: " << sub << endl; // World
// 只读 // sub[0] = 'x'; // 错误!string_view 只读 return 0;}7.2 string_view 注意事项
#include <iostream>#include <string_view>using namespace std;
int main() { // 注意:string_view 不拥有内存! // 返回局部字符串的 view 是悬垂引用
// 正确用法:作为函数参数(只读传递) auto startsWith = [](string_view s, string_view prefix) { return s.substr(0, prefix.size()) == prefix; };
cout << startsWith("HelloWorld", "Hello") << endl; // 1 cout << startsWith("HelloWorld", "World") << endl; // 0 return 0;}八、[[nodiscard]] 与 [[maybe_unused]]
8.1 [[nodiscard]] 禁止忽略返回值
#include <iostream>using namespace std;
// 警告:调用者必须使用返回值[[nodiscard]] int computeResult() { return 42;}
class Error {public: [[nodiscard]] bool isValid() const { return true; }};
int main() { // computeResult(); // 警告!返回值被忽略 int result = computeResult(); // 正确 cout << "结果: " << result << endl;
Error e; // e.isValid(); // 警告!bool 返回值被忽略 if (e.isValid()) { // 正确 cout << "有效" << endl; } return 0;}8.2 [[maybe_unused]] 抑制警告
#include <iostream>using namespace std;
void debug([[maybe_unused]] int level) { // level 可能在某些配置下不使用 // [[maybe_unused]] 抑制未使用警告}
int main() { debug(3); return 0;}九、实战:配置文件解析器
综合本篇 C++17 特性,实� ��健壮的配置解析:
#include <iostream>#include <fstream>#include <sstream>#include <map>#include <optional>#include <variant>#include <string_view>#include <filesystem>using namespace std;namespace fs = filesystem;
// 配置值:可能是整数、浮点或字符串using ConfigValue = variant<int, double, string>;
class ConfigParser {private: map<string, ConfigValue> config;
optional<ConfigValue> parseValue(string_view raw) { // 尝试解析为 int try { size_t pos; int i = stoi(string(raw), &pos); if (pos == raw.size()) return ConfigValue(i); } catch (...) {}
// 尝试解析为 double try { size_t pos; double d = stod(string(raw), &pos); if (pos == raw.size()) return ConfigValue(d); } catch (...) {}
// 默认字符串 return ConfigValue(string(raw)); }
public: bool load(string_view filename) { ifstream fin(string(filename)); if (!fin) return false;
string line; while (getline(fin, line)) { // 去掉注释和空行 auto comment = line.find('#'); if (comment != string::npos) line = line.substr(0, comment); if (line.empty()) continue;
auto eq = line.find('='); if (eq == string::npos) continue;
string key = line.substr(0, eq); string value = line.substr(eq + 1);
// 去空格 auto trim = [](string s) { while (!s.empty() && isspace(s.back())) s.pop_back(); while (!s.empty() && isspace(s.front())) s.erase(s.begin()); return s; }; config[trim(key)] = *parseValue(trim(value)); } return true; }
void showAll() const { for (const auto &[key, value] : config) { cout << kuy << " = "; visit([](const auto &v) { cout << v; }, value); cout << endl; } }
template <typename T> optional<T> get(const string &key) const { auto it = config.find(key); if (it == config.end()) return nullopt; if (auto ptr = get_if<T>(&it->second)) return *ptr; return nullopt; }};
int main() { // 创建配置 ofstream fout("app.conf"); fout << "# 应用配置" << endl; fout << "host = localhost" << endl; fout << "port = 8080" << endl; fout << "timeout = 30.5" << endl; fout << "mode = production" << endl; fout.close();
// 解析配置 ConfigParser parser; if (parser.load("app.conf")) { parser.showAll();
cout << "---" << endl; if (auto port = parser.get<int>("port")) { cout << "端口: " << *port << endl; // 8080 } if (auto host = parser.get<string>("host")) { cout << "主机: " << *host << endl; // localhost } if (auto timeout = parser.get<double>("timeout")) { cout << "超时: " << *timeout << endl; // 30.5 } } fs::remove("app.conf"); return 0;}总结
本篇讲解了 if/switch 初始化语句、结构化绑定、if constexpr 编译期分支、std::optional/variant/any 三种实用类型、string_view 零拷贝视图、[[nodiscard]]/[[maybe_unused]] 属性,并用配置解析器串联实战。重点掌握:结构化绑定遍历 map、if constexpr 与模板的配合、optional 处理可能不存在的值、variant 的类型安全访问、string_view 不拥有内存的特性。
下一篇将讲解 C++20 新特性精讲(concepts、ranges、三路比较),敬请期待!
C++17新特性精讲:结构化绑定、if constexpr与实用类型
作者:Wang Shijie
本文链接:https://wsj-blog.pages.dev/posts/c-17-xin-te-xing-jing-jiang-jie-gou-hua-bang-ding-if/
本文采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。
留言评论
评论资源加载失败,请重试。