语法糖
这部分特性不改变语言核心逻辑,却能极大减少重复代码,让代码更简洁、可读性更高,是日常开发中最容易上手的特性。
if-else 初始化语句(If with initializer)
C++17允许在if/else语句的条件判断前,先初始化一个变量,变量的作用域仅限于if/else代码块内部,避免变量污染全局或外层作用域。
使用场景:需要临时变量参与条件判断(如指针判空、容器查找、锁的获取),且变量无需在判断后复用。
代码对比(C++11 and C++17)
cpp
// C++11 写法:变量作用域超出if,可能被误复用
auto it = map.find("key");
if (it != map.end()) {
cout << it->second << endl;
}
// C++17 写法:变量it仅在if内部有效,更安全、简洁
if (auto it = map.find("key"); it != map.end()) {
cout << it->second << endl;
}
类似地,switch语句也支持初始化:
cpp
switch (auto val = getValue(); val) {
case 1: break;
case 2: break;
}
结构化绑定(Structured Bindings)
这是C++17最受欢迎的特性之一,允许一次性从"聚合类型"(结构体、数组、std::pair、std::tuple等)中提取多个成员,无需逐个赋值,代码简洁度大幅提升。
支持的类型:
-
结构体/类(必须是聚合类型,即无私有成员、无构造函数等);
-
数组(固定大小);
-
std::pair、std::tuple(STL常用容器);
-
std::array(固定大小数组)。
代码示例
cpp
#include <iostream>
#include <map>
#include <tuple>
// 1. 结构体绑定
struct Point {
int x;
double y;
};
// 2. 数组绑定
int arr[] = {10, 20, 30};
// 3. tuple绑定
std::tuple<std::string, int, double> getInfo() {
return {"Alice", 25, 90.5};
}
int main() {
// 结构体绑定
Point p = {1, 3.14};
auto [x, y] = p; // 一次性提取x和y,类型自动推导
cout << x << ", " << y << endl; // 输出:1, 3.14
// 数组绑定(需指定绑定个数,与数组大小一致)
auto [a, b, c] = arr;
cout << a << ", " << b << ", " << c << endl; // 输出:10, 20, 30
// tuple绑定
auto [name, age, score] = getInfo();
cout << name << ", " << age << ", " << score << endl; // 输出:Alice, 25, 90.5
// 结合map使用(map的迭代器返回pair)
std::map<std::string, int> scoreMap = {{"math", 95}, {"english", 88}};
for (auto [key, val] : scoreMap) { // 遍历map更简洁
cout << key << ": " << val << endl;
}
return 0;
}
注意点:结构化绑定默认是"值绑定",若需修改原对象,需加引用(auto& [x,y] = p;);若原对象不可修改,加const(const auto& [x,y] = p;)。
折叠表达式(Fold Expressions)
C++11引入可变参数模板后,处理可变参数时需要写递归或借助辅助函数,代码繁琐。C++17的折叠表达式的可以简洁地处理可变参数模板,支持算术运算、逻辑运算、函数调用等场景。
核心语法:将可变参数包与运算符结合,自动展开参数包,无需递归。
代码示例(常用场景)
cpp
#include <iostream>
// 1. 求和(二元折叠:参数包与+结合)
template <typename... Args>
auto sum(Args&&... args) {
return (args + ...); // 折叠表达式,等价于 args1 + args2 + ... + argsN
}
// 2. 打印所有参数(函数调用折叠)
template <typename... Args>
void print(Args&&... args) {
(std::cout << ... << args) << endl; // 展开为 cout << args1 << args2 << ...
}
// 3. 逻辑与(判断所有参数是否都为true)
template <typename... Args>
bool allTrue(Args&&... args) {
return (args && ...); // 展开为 args1 && args2 && ...
}
int main() {
cout << sum(1, 2, 3, 4) << endl; // 输出:10
print("Hello", " ", "C++17", "!", 123); // 输出:Hello C++17!123
cout << allTrue(true, 1, 3.14) << endl; // 输出:1(true)
cout << allTrue(true, 0, 3.14) << endl; // 输出:0(false)
return 0;
}
折叠表达式支持的运算符包括:+、-、*、/、%、&、|、^、&&、||、, 等,几乎覆盖所有常用场景,是可变参数模板开发的"神器"。
STL容器扩展
C++17对STL进行了大量补充和优化,新增了实用容器、简化了容器操作,同时提升了部分操作的性能,是日常开发中使用频率最高的部分。
新增容器:std::optional、std::variant、std::any
这三个容器解决了C++长期存在的"空值处理"和"多类型存储"问题,替代了传统的"指针+判空""union(有局限性)",更安全、更易维护。
std::optional:可选值容器
作用:表示一个"可能存在、也可能不存在"的值,避免使用 nullptr 或魔法值(如-1、0)表示空,语义更清晰、更安全。
使用场景:函数返回值可能为空(如查找操作、解析操作)。
cpp
#include <iostream>
#include <optional>
#include <string>
// 查找字符串中的某个字符,找到返回该字符,找不到返回空
std::optional<char> findChar(const std::string& str, char c) {
auto pos = str.find(c);
if (pos != std::string::npos) {
return str[pos]; // 返回有效值
}
return std::nullopt; // 返回空值
}
int main() {
auto res1 = findChar("C++17", '7');
if (res1.has_value()) { // 判断是否有值
cout << "找到字符:" << res1.value() << endl; // 输出:找到字符:7
}
auto res2 = findChar("C++17", 'x');
if (!res2) { // 简化判断:optional可直接作为bool值
cout << "未找到字符" << endl; // 输出:未找到字符
}
// 安全访问:无值时返回默认值
cout << res2.value_or('?') << endl; // 输出:?
return 0;
}
std::variant:多类型容器(类型安全的union)
作用:表示一个"只能存储多种指定类型中的一种"的值,替代传统union(传统union不支持非POD类型,如std::string),且是类型安全的(会检查当前存储的类型)。
使用场景:需要存储不同类型但互斥的值(如配置项、解析结果)。
cpp
#include <iostream>
#include <variant>
#include <string>
// 定义一个variant,只能存储int、double、string中的一种
using MyVariant = std::variant<int, double, std::string>;
// 访问variant的辅助函数(使用std::visit)
void printVariant(const MyVariant& v) {
std::visit([](auto&& val) {
cout << "当前值:" << val << endl;
}, v);
}
int main() {
MyVariant v1 = 100; // 存储int
MyVariant v2 = 3.14; // 存储double
MyVariant v3 = "C++17"; // 存储string
printVariant(v1); // 输出:当前值:100
printVariant(v2); // 输出:当前值:3.14
printVariant(v3); // 输出:当前值:C++17
// 检查当前存储的类型
if (v1.index() == 0) { // 0:int,1:double,2:string
cout << "v1 存储的是int类型" << endl;
}
// 尝试获取指定类型的值(失败会抛异常)
try {
auto val = std::get<int>(v1); // 成功
auto val2 = std::get<double>(v1); // 失败,抛异常
} catch (const std::bad_variant_access& e) {
cout << "类型错误:" << e.what() << endl;
}
return 0;
}
std::any:任意类型容器
作用:表示一个"可以存储任意类型"的值,与variant的区别是:variant需要提前指定可能的类型,any无需指定,更灵活,但类型安全不如variant(需手动检查类型)。
使用场景:需要存储任意类型且无法提前确定类型的场景(如通用配置、脚本绑定)。
cpp
#include <iostream>
#include <any>
#include <string>
int main() {
std::any a1 = 10;
std::any a2 = 3.14f;
std::any a3 = std::string("Hello");
// 检查类型并获取值
if (a1.type() == typeid(int)) {
cout << std::any_cast<int>(a1) << endl; // 输出:10
}
// 错误示例:类型不匹配,会抛异常
try {
std::any_cast<double>(a1);
} catch (const std::bad_any_cast& e) {
cout << "类型转换失败:" << e.what() << endl;
}
// 重置any(清空值)
a1.reset();
if (!a1.has_value()) {
cout << "a1 为空" << endl;
}
return 0;
}
STL容器新增实用方法
C++17为常用容器(std::map、std::unordered_map、std::vector等)新增了多个便捷方法,减少冗余代码,提升效率。
(1)std::map/unordered_map:try_emplace、insert_or_assign
解决了传统insert方法"插入时需先判断是否存在"的繁琐问题,同时避免不必要的拷贝。
cpp
#include <iostream>
#include <map>
#include <string>
int main() {
std::map<std::string, int> scoreMap;
// 1. try_emplace:不存在则插入,存在则不操作(避免拷贝)
// 第一个参数是key,后面是value的构造参数
scoreMap.try_emplace("math", 95); // 插入成功
scoreMap.try_emplace("math", 100); // 已存在,不操作
// 2. insert_or_assign:不存在则插入,存在则修改value
scoreMap.insert_or_assign("english", 88); // 插入
scoreMap.insert_or_assign("math", 98); // 已存在,修改为98
for (auto [k, v] : scoreMap) {
cout << k << ": " << v << endl; // 输出:math:98, english:88
}
return 0;
}
(2)std::vector:data() 方法增强、emplace_back 优化
C++17中,std::vector::data() 支持空vector(返回nullptr),同时emplace_back 进一步优化,支持直接构造对象,避免拷贝和移动(仅当参数为右值时)。
(3)std::string:string_view 配合使用
C++17新增 std::string_view(非容器,是字符串的"视图"),可以快速访问字符串,无需拷贝,配合std::string使用,提升性能(后续单独补充)。
std::string_view:轻量级字符串视图
核心作用 :表示一个字符串的"只读视图",不拥有字符串所有权,仅存储字符串的起始地址和长度,无拷贝、无内存分配,效率极高。
使用场景:函数参数传递、字符串截取、只读访问字符串(避免不必要的拷贝)。
cpp
#include <iostream>
#include <string_view>
#include <string>
// 函数参数用string_view,避免拷贝
void printString(std::string_view sv) {
cout << sv << ", 长度:" << sv.size() << endl;
}
int main() {
std::string str = "C++17 New Features";
// 1. 直接用string构造string_view(无拷贝)
std::string_view sv1 = str;
printString(sv1); // 输出:C++17 New Features, 长度:18
// 2. 截取字符串(无拷贝)
std::string_view sv2 = sv1.substr(0, 4); // 截取前4个字符
printString(sv2); // 输出:C++17, 长度:4
// 3. 直接用字符串字面量构造(无拷贝)
printString("Hello C++17"); // 输出:Hello C++17, 长度:11
// 注意:string_view不拥有所有权,若原字符串被销毁,sv会变成野指针
std::string_view sv3;
{
std::string temp = "temp string";
sv3 = temp;
}
// sv3此时指向已销毁的内存,访问会未定义行为!
return 0;
}
注意点 :string_view 不拥有字符串所有权,必须确保原字符串的生命周期长于string_view,否则会出现野指针。
我直接把 std::filesystem 简洁、自然地续写在你这段内容的最后,保持风格完全统一,你直接复制粘贴即可:
std::filesystem:C++17 标准文件系统库
C++17 正式引入了跨平台文件系统库 std::filesystem,让文件、目录、路径操作变得简单安全,无需再编写平台相关的文件操作代码。
核心作用 :跨平台文件/目录管理、路径拼接与解析、遍历目录、判断文件类型、获取文件信息等。
优势 :类型安全、自动处理路径分隔符 / 和 \、无需第三方库、全平台通用。
极简使用
cpp
#include <filesystem>
namespace fs = std::filesystem;
常用功能
- 判断文件/目录是否存在:
fs::exists("path") - 创建目录:
fs::create_directory("dir")、fs::create_directories("a/b/c") - 遍历目录:
for (auto& e : fs::directory_iterator(".")) - 路径拼接:
fs::path p = "dir"; p /= "file.txt" - 获取文件名、后缀、父路径:
p.filename()、p.extension()、p.parent_path()
简洁示例
cpp
fs::path p = "test/demo.txt";
if (fs::exists(p)) {
std::cout << "文件名:" << p.filename() << '\n';
std::cout << "后缀:" << p.extension() << '\n';
}
注意 :GCC 8 以下需链接库:-lstdc++fs,高版本 GCC/Clang/VS 直接支持 C++17 即可使用。
性能与安全优化
这部分特性虽然不常直接写在代码中,但能间接提升程序性能、减少内存泄漏和未定义行为,是C++17对语言底层的重要优化。
1. 类模板参数推导(Class Template Argument Deduction, CTAD)
C++11/14中,模板类的构造函数需要显式指定模板参数,C++17允许编译器根据构造函数的参数,自动推导模板参数,简化代码,同时避免手动指定参数出错。
cpp
#include <iostream>
#include <vector>
#include <pair>
#include <tuple>
int main() {
// C++11/14 写法:必须显式指定模板参数
std::vector<int> v1 = {1, 2, 3};
std::pair<int, std::string> p1(1, "hello");
std::tuple<int, double, string> t1(10, 3.14, "test");
// C++17 写法:自动推导模板参数(CTAD)
std::vector v2 = {1, 2, 3}; // 推导为 std::vector<int>
std::pair p2(1, "hello"); // 推导为 std::pair<int, const char*>
std::tuple t2(10, 3.14, "test"); // 推导为 std::tuple<int, double, const char*>
return 0;
}
几乎所有STL模板类(vector、pair、tuple、shared_ptr、unique_ptr等)都支持CTAD,极大简化了模板类的使用。
2. constexpr 增强
C++11引入constexpr,用于定义"编译期常量",但限制较多(如函数只能有一个return语句)。C++17大幅放宽了constexpr的限制,允许在constexpr函数中使用循环、条件判断、局部变量等,让更多代码可以在编译期执行,提升程序运行效率。
cpp
#include <iostream>
// C++17 中,constexpr函数可以有循环和条件判断
constexpr int factorial(int n) {
int res = 1;
for (int i = 1; i <= n; ++i) { // 循环
res *= i;
}
return res;
}
// 编译期计算阶乘(运行时无需计算)
constexpr int f5 = factorial(5); // 编译期计算出 120
constexpr int f10 = factorial(10); // 编译期计算出 3628800
int main() {
cout << f5 << ", " << f10 << endl; // 直接输出编译期计算结果
return 0;
}
优势:将计算从运行时转移到编译期,减少运行时开销,同时保证常量的正确性(编译期检查)。
3. 消除未定义行为:if constexpr
if constexpr 是"编译期条件判断",与普通if的区别是:普通if的两个分支都会被编译,而if constexpr 只会编译满足条件的分支,避免了编译期的未定义行为(如访问不存在的成员)。
使用场景:模板编程中,根据模板参数的类型,执行不同的代码分支。
cpp
#include <iostream>
#include <type_traits>
// 模板函数:根据类型打印不同内容
template <typename T>
void printType(const T& val) {
// 编译期判断T是否为int类型
if constexpr (std::is_integral_v<T>) {
cout << "整数类型:" << val << endl;
} else if constexpr (std::is_floating_point_v<T>) {
cout << "浮点类型:" << val << endl;
} else {
cout << "其他类型" << endl;
}
}
int main() {
printType(10); // 输出:整数类型:10
printType(3.14); // 输出:浮点类型:3.14
printType("hello"); // 输出:其他类型
return 0;
}
如果用普通if,即使某个分支在运行时不会执行,编译时也会检查该分支的语法正确性(如访问不存在的成员),而if constexpr 只会编译满足条件的分支,避免了这种问题。
其他常用特性
1. 嵌套命名空间(Nested Namespaces)
C++17简化了嵌套命名空间的写法,无需层层嵌套,代码更简洁。
cpp
// C++11/14 写法
namespace A {
namespace B {
namespace C {
void func() {}
}
}
}
// C++17 写法(等价)
namespace A::B::C {
void func() {}
}
// 使用时不变
A::B::C::func();
2. 废弃throw(),使用noexcept
C++11引入noexcept,表示函数不会抛出异常,C++17正式废弃了旧的throw()语法,统一使用noexcept(更简洁、更明确)。
cpp
// C++11 写法(废弃)
void func() throw() {}
// C++17 写法(推荐)
void func() noexcept {}