本文是 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 条件中声明变量,作用域限制在语句块内:
cpp
#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 初始化语句
cpp
#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、结构体等:
cpp
#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 的优雅写法
cpp
#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 解包引用(可修改)
cpp
#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 在编译期求值并丢弃不满足的分支:
cpp
#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 与模板递归结合
cpp
#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 << e
ndl; // 最后一个参数结束
}
}
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 的惯用法:
cpp
#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 成员函数
cpp
#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 是类型安全的联合体,可他存储多种类型之一:
cpp
#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 安全访问
cpp
#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* 但类型安全):
cpp
#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 是只读字符串视图 ,�
��拥有内存,避免拷贝:
cpp
#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 注意事项
cpp
#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] 禁止忽略返回值
cpp
#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] 抑制警告
cpp
#include <iostream>
using namespace std;
void debug([[maybe_unused]] int level) {
// level 可能在某些配置下不使用
// [[maybe_unused]] 抑制未使用警告
}
int main() {
debug(3);
return 0;
}
九、实战:配置文件解析器
综合本篇 C++17 特性,实�
��健壮的配置解析:
cpp
#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、三路比较),敬请期待!