【C++】一篇文章详解C++17新特性

语法糖

这部分特性不改变语言核心逻辑,却能极大减少重复代码,让代码更简洁、可读性更高,是日常开发中最容易上手的特性。

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 {}
相关推荐
mmz12072 小时前
贪心算法3(c++)
c++·算法·贪心算法
j_xxx404_2 小时前
蓝桥杯基础--排序模板合集II(快速,归并,桶排序)
数据结构·c++·算法·蓝桥杯·排序算法
Jordannnnnnnn2 小时前
追赶29,28
c++
:mnong2 小时前
油藏数值模型ReservoirSim 系统设计分析
c++
计算机安禾2 小时前
【数据结构与算法】第13篇:栈(三):中缀表达式转后缀表达式及计算
c语言·开发语言·数据结构·c++·算法·链表
简单~3 小时前
C++ 函数模板完全指南
c++·函数模板
·心猿意码·3 小时前
C++ 线程安全单例模式的底层源码级解析
c++·单例模式
故事和你913 小时前
洛谷-入门4-数组3
开发语言·数据结构·c++·算法·动态规划·图论
Yu_Lijing3 小时前
基于C++的《Head First设计模式》笔记——原型模式
c++·笔记·设计模式