news 2026/8/30 4:59:44

C++17这波又带来了好东西

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
C++17这波又带来了好东西

文章目录

    • 一、语言语法
      • 1. 结构化绑定
      • 2. if / switch 语句内初始化变量
      • 3. 折叠表达式 Fold‑Expression
      • 4. inline 内联变量
      • 5. constexpr if 编译期分支
      • 6. 类模板参数推导
      • 7. 嵌套命名空间简写
      • 8. 属性标记 [[nodiscard]]
    • 二、标准库高频更新
      • 1. std::optional
      • 2. std::variant
      • 3. std::any
      • 4. std::filesystem
      • 5. std::string_view
      • 6. std::as_const
      • 7. std::clamp

一、语言语法

1. 结构化绑定

C++11/14痛点:获取 pair、tuple、结构体成员,必须用.first/.second/get<>,代码啰嗦,可读性差。

新能力:一次性解包元组、pair、结构体、数组元素。(刷算法题又爽了)

#include<map>#include<iostream>intmain(){std::map<int,std::string>mp;mp[1]="hello";// 遍历map神器for(auto&[key,val]:mp){std::cout<<key<<" "<<val;}return0;}

场景:遍历map,返回多值函数,解包结构体。

  • auto [key,val]:值拷贝,修改不会影响原容器;
  • auto& [key,val]:引用,可以修改原值;
  • const auto& [key,val]:只读引用,禁止修改;

结构化绑定出来的变量,不能直接放进 lambda 的捕获列表[],C++17 标准禁止。

结构化绑定产生的是一种别名,并不是一个普通实体变量,lambda 无法捕获它。

#include<map>#include<iostream>#include<functional>intmain(){std::map<int,std::string>mp{{1,"hello"}};for(auto&[key,val]:mp){// ❌ 非法!不能捕获 key / valautolambda=[key,val](){std::cout<<key<<" "<<val;};lambda();}return0;}

方案1:先拷贝一份普通变量,再捕获(最常用)

#include<map>#include<iostream>#include<functional>intmain(){std::map<int,std::string>mp{{1,"hello"}};for(auto&[key,val]:mp){// 先拷贝出来intk=key;std::string v=val;// ✅ 捕获普通变量没问题autolambda=[k,v](){std::cout<<k<<" "<<v;};lambda();}return0;}

方案2:捕获外层整个对象,lambda 内部再解包

如果你是想异步执行(线程任务),可以捕获整个 pair/map‑item,不要捕获绑定别名:

#include<map>#include<iostream>#include<functional>#include<thread>intmain(){std::map<int,std::string>mp{{1,"hello"}};for(auto&item:mp){// 捕获整个元素autolambda=[item](){auto[k,v]=item;// lambda 内部再结构化绑定std::cout<<k<<" "<<v;};std::threadt(lambda);t.join();}return0;}

2. if / switch 语句内初始化变量

旧写法:变量定义在 if 外面,扩大作用域,容易污染上下文。

// C++17之前autores=get_result();if(res.is_ok()){}

C++17:

if(autores=get_result();res.is_ok()){// res 仅在 if‑else 范围内生效}switch(autoval=get_id();val){case1:break;}

3. 折叠表达式 Fold‑Expression

解决可变参数包Args...难以遍历运算的痛点。

#include<iostream>template<typename...Args>autosum(Args...args){return(args+...);}intmain(){std::cout<<sum(1,2,3,4)<<std::endl;return0;}

输出结果:10。

  • (pack op ...)右折叠
  • (... op pack)左折叠

4. inline 内联变量

痛点 C++14:头文件中定义全局变量/静态成员变量,多文件包含就触发重定义链接错误,只能放源文件。

// 头文件直接写,多文件包含不会报重定义inlineconststd::string g_name="demo";structConfig{inlinestaticintcount=0;};

工程最实用特性之一!头文件常量、类静态成员变量首选方案。

5. constexpr if 编译期分支

编译期判断,不满足的分支直接丢弃,不会参与编译(模板场景下)。

template<typenameT>voidfunc(T t){ifconstexpr(std::is_integral_v<T>){// 整数类型执行这段}else{// 其他类型}}

场景:模板特化简化、类型分支判断、可变参数处理;替代笨重的模板重载。

  • 非模板场景:两个分支代码语法都必须合法;
  • 条件必须是编译期可求出的常量表达式。

6. 类模板参数推导

不用显式写出模板参数,编译器自动推导

// C++14std::pair<int,std::string>p{1,"abc"};// C++17,自动推导类型std::pair p{1,"abc"};std::vector vec{1,2,3};

只适用于类模板,不能推导函数模板;复杂自定义类有时需要手动提供推导指引。

7. 嵌套命名空间简写

// C++14 冗长写法namespaceA{namespaceB{namespaceC{}}}// C++17 简写namespaceA::B::C{}

头文件高频使用,减少缩进。

8. 属性标记 [[nodiscard]]

标记函数返回值不允许忽略,如果调用方丢弃返回值编译器给出警告,适合错误码、optional 返回值。

[[nodiscard]]intopen_file();

工程规范、错误防御高频使用。

二、标准库高频更新

1. std::optional

头文件:<optional>

std::optional<T>是一个包装容器,内部存储一块T的内存 + 一个布尔标记,标记当前是否有效值。
它专门用来解决函数返回值有可能失败、没有结果的问题。
在C++17之前我们只能用这些丑陋方案标记失败:

  • 返回-1INT_MAX等魔法值;缺点:返回值本身就可以取到这个数字,歧义;
  • 返回指针,失败返回nullptr;缺点:要管理堆内存、容易内存泄漏;

optional 本质:值语义对象,可拷贝、可移动,不需要堆内存。

#include<optional>#include<iostream>// 查找id对应的分数,查找失败则无结果std::optional<int>query_score(intid){if(id==100){return95;// 有值}returnstd::nullopt;// 代表空,没有结果}intmain(){autores=query_score(99);// 方式1:判断是否有值if(res.has_value()){std::cout<<res.value()<<"\n";}// 方式2:bool隐式转换简写判断if(res){std::cout<<*res<<"\n";}// 方式3:取不到值时给默认值,不会抛异常intscore=res.value_or(60);return0;}
API作用
.has_value()bool,判断是否包含有效值
.value()获取值;空的时候调用直接抛异常std::bad_optional_access
*opt解引用获取值;空的时候行为是未定义UB,不会抛异常!
.value_or(default)有值返回原值,无值返回默认值,安全,无异常
.reset()清空optional,变成空状态

使用场景:

  1. 查询类接口(数据库查找、配置查找)查找可能失败;
  2. 函数参数可选(某些字段可以不存在);
  3. 解析报文,某个字段可能缺失;

注意点:

  1. 不要混用.value()*:空的时候.value()抛异常,*直接UB,隐患极大
  2. 不要返回std::optional<std::string&>optional不能存引用,只能存值对象;
  3. 安全优先,拿不到值优先使用value_or()
  4. 返回optional的函数,建议加上[[nodiscard]],防止调用方忽略失败状态

2. std::variant

头文件:<variant>

std::variant<T1,T2,T3...>类型安全的联合体,同一时刻,variant里面最多保存其中一种类型的值
底层是一块足够容纳最大类型的缓冲区 + 一个下标标记,记录当前存放的是哪一种类型;栈上分配,无堆内存开销

C语言原生union的致命缺陷:

  • 没有标记记录当前存的是什么类型;、
  • 不能存放带非平凡构造析构的类型,例如std::string
#include<variant>#include<iostream>#include<string>voidprint_value(conststd::variant<int,double,std::string>&v){// 判断当前存储的类型if(std::holds_alternative<int>(v)){std::cout<<"int: "<<std::get<int>(v)<<"\n";}elseif(std::holds_alternative<double>(v)){std::cout<<"double: "<<std::get<double>(v)<<"\n";}elseif(std::holds_alternative<std::string>(v)){std::cout<<"string: "<<std::get<std::string>(v)<<"\n";}}intmain(){std::variant<int,double,std::string>data;data=10;print_value(data);data=3.14;print_value(data);data="hello variant";print_value(data);return0;}
API作用
std::holds_alternative<T>(var)判断variant当前是不是T类型
std::get<T>(var)取出T类型的值;类型不匹配抛std::bad_variant_access
std::get_if<T>(&var)安全版本。匹配成功返回指针,失败返回nullptr,不会抛异常
std::visit(fun, var)访问器,自动匹配当前类型执行回调,最优雅的遍历方式

使用场景:

  1. 消息协议、报文,一个字段可能是多种不同类型;
  2. 状态机的数据载体;
  3. 解析配置,字段类型不固定,但是类型集合有限;

注意点:

  1. variant不能存放引用std::variant<int&>非法;
  2. variant里面不能放重复类型,variant<int,int>编译报错;
  3. std::get<T>类型不对直接抛异常;不想抛异常优先用std::get_if
  4. 存放的所有类型必须能被构造;禁止放入没有默认构造函数的类型;
  5. 如果需要表示variant里面什么都没有,加上std::monostatestd::variant<std::monostate, int, std::string>

3. std::any

头文件:<any>

std::any无约束的类型擦除容器,可以存放任意一种单值类型,不需要提前声明类型列表。
内部实现:堆上分配内存存储对象,运行时记录类型信息。

variant的本质区别:

  • variant:类型集合编译期就要全部写死;栈内存;几乎无运行时开销;
  • any:类型可以运行时任意变化;堆分配;有运行时开销,类型安全检查;
#include<any>#include<iostream>#include<string>intmain(){std::any container;container=100;// 取出值,类型错误抛 std::bad_any_castintnum=std::any_cast<int>(container);std::cout<<num<<"\n";container=std::string("hello any");std::string str=std::any_cast<std::string>(container);std::cout<<str<<"\n";// 安全读取,不抛异常if(int*p=std::any_cast<int>(&container)){std::cout<<*p<<"\n";}// 判断是否为空if(!container.has_value()){}return0;}
API作用
.has_value()是否存有对象
std::any_cast<T>(any_obj)取出值;类型不匹配抛异常
std::any_cast<T>(&any_obj)指针版本,失败返回nullptr,安全无异常

使用场景:

  1. 通用事件总线,事件携带的数据类型完全无法提前预知
  2. 脚本、反射框架,通用缓存

对比选型指南:

  • 类型集合有限 → std::variant
  • 有可能为空 → std::optional
  • 类型完全未知、任意类型 → std::any

注意点:

  1. any有堆内存开销,频繁创建销毁性能较差;高性能、实时循环里面尽量少用;
  2. any_cast类型不对会抛异常;优先用指针版std::any_cast<T>(&x)做安全判断;
  3. any不能存放引用

4. std::filesystem

头文件:<filesystem>

  • C++17第一次把跨平台文件系统操作纳入标准库。在此之前Windows需要调用Win32 API,Linux调用posix,两套代码,无法跨平台;
  • std::filesystem::path是路径对象,自动处理不同操作系统路径分隔符(windows\,linux/);
#include<filesystem>#include<iostream>namespacefs=std::filesystem;intmain(){fs::path log_dir="./log";// 判断目录是否存在if(!fs::exists(log_dir)){// 创建目录fs::create_directories(log_dir);}fs::path cfg_file=log_dir/"config.json";// /运算符拼接路径,跨平台!if(fs::exists(cfg_file)){// 获取文件大小,字节autosize=fs::file_size(cfg_file);std::cout<<"file size:"<<size<<"\n";}// 遍历文件夹下所有文件for(auto&entry:fs::directory_iterator(log_dir)){if(entry.is_regular_file()){std::cout<<entry.path()<<"\n";}}return0;}
  • fs::exists(path)判断文件/目录是否存在
  • fs::create_directories(path)递归创建多级目录(推荐);create_directory只能创建单级
  • fs::file_size(path)获取文件字节大小
  • fs::remove(path)删除文件
  • fs::rename(old,new)文件重命名
  • fs::directory_iterator单层遍历文件夹
  • fs::recursive_directory_iterator递归遍历所有子文件夹

注意:

  1. GCC8~9版本需要手动链接库:编译加-lstdc++fs;GCC10以后不需要;
  2. filesystem的接口大部分会抛异常;如果你不想抛异常,所有函数都有带std::error_code重载版本;
std::error_code ec;fs::create_directories(log_dir,ec);if(ec){// 创建失败}
  1. 路径对象fs::pathstd::string可以互相转换;不要自己手动拼接字符串路径,用path1 / path2

5. std::string_view

头文件:<string_view>

  • std::string_view字符串只读视图
  • 不拥有、不拷贝、不存储字符串内存
  • 内部只有两个成员:const char*指针 + 长度;
  • 只是借用一段已经存在的字符缓冲区,零拷贝、性能极高

C++17之前函数接收字符串参数只能写const std::string& s,如果我们传入一个char*字符串字面量,编译器会隐式构造临时std::string,发生内存拷贝,带来开销。
string_view完美规避拷贝开销。

#include<string_view>#include<string>#include<iostream>// 只读接收字符串,零拷贝开销voidprint(std::string_view s){std::cout<<s<<"\n";// s[0] = 'a'; // ❌只读,不能修改}intmain(){std::string str{"hello world"};print(str);// 字符串字面量,无拷贝print("hello literal");// 截取子串,不会生成新string,零开销!std::string_viewsub(str.data(),5);print(sub);return0;}
  • .data()返回底层const char*指针
  • .size()/.length()获取字符长度
  • .substr(pos,len)截取子视图(零拷贝)

选型建议:

  • 函数入参,只读字符串,优先std::string_view
  • 绝对不要将string_view作为函数返回值!极易悬垂引用。

高危坑点:

string_view生命周期不能超过原始字符串缓冲区。原始字符串销毁后,string_view变成野指针,悬垂引用。

// ❌危险反面例子std::string_viewbad_func(){std::string temp="abc";returntemp;// temp函数结束销毁,返回的视图悬空!}

6. std::as_const

头文件:<utility>

  • std::as_const(x)返回变量x的const左值引用,不会产生拷贝;
  • 作用:把一个非const对象,临时当作只读对象使用。
#include<utility>#include<vector>#include<iostream>intmain(){std::vector<int>vec{1,2,3};auto&const_ref=std::as_const(vec);// const_ref.push_back(5); // 编译报错,只读,无法修改// 只读范围for循环for(autov:std::as_const(vec)){std::cout<<v<<"\n";}return0;}

使用场景:

  1. 遍历容器,强制只读,防止不小心修改原数据
  2. lambda捕获时生成const副本

注意点:

  • as_const只生成引用,不会拷贝对象;无性能开销
  • 不能用于临时对象

7. std::clamp

头文件:<algorithm>

template<classT>constexprTclamp(constT&value,constT&low,constT&high);

数值截断函数,把一个值限制在[low, high]的闭区间内。

C++17之前,你必须手动写max、min嵌套:

intval=std::max(low,std::min(high,val));

clamp一步替代:

#include<algorithm>#include<iostream>intmain(){inta=std::clamp(120,0,100);//100intb=std::clamp(-10,0,100);//0intc=std::clamp(50,0,100);//50std::cout<<a<<" "<<b<<" "<<c<<"\n";return0;}

传入参数low 必须 <= high;如果low>high,属于未定义行为。


版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/30 4:59:28

旅游网站源码 Java+SpringBoot+Vue 前后分离

一、关键词大湾区旅游网站&#xff0c;大湾区文旅综合服务网站&#xff0c;湾区旅游资源线上管理平台二、作品包含源码数据库全套环境和工具资源本地部署教程三、项目技术前端技术&#xff1a;Html、Css、Js、Vue2、Element-ui后端技术&#xff1a;Java、SpringBoot2、MyBatis四…

作者头像 李华
网站建设 2026/8/30 4:58:57

2018百度AI异构计算工程师笔试题复盘:从体系结构到CUDA优化

2018年那批秋招&#xff0c;我第一次在招聘页面上看到“AI异构计算工程师”这个岗位&#xff0c;第一反应是&#xff1a;这岗位到底考什么&#xff1f;是考深度学习模型&#xff0c;还是考数据结构&#xff1f;后来真把笔试题过了一遍才发现&#xff0c;这套题比想象中实在得多…

作者头像 李华
网站建设 2026/8/30 4:56:56

SpringBoot+Vue游戏攻略平台项目实战:从源码到部署全程解析

简介&#xff1a;本资源是一套高完成度的MOBA类游戏攻略分享平台毕业设计项目&#xff0c;面向计算机专业本科生及Java全栈学习者&#xff0c;解决毕设选题难、前后端整合实践弱、工程文档不全等实际问题&#xff0c;亦适用于课程设计与期末大作业。压缩包共813个文件&#xff…

作者头像 李华
网站建设 2026/8/30 4:56:33

网约车出行无障碍服务标准发布

2026年7月20日&#xff0c;我国首部面向网约车场景的无障碍团体标准——《网约车出行无障碍服务建设指南 第1部分&#xff1a;面向视障人群》正式发布&#xff0c;并将于2026年8月20日起实施。该标准由中国互联网协会&#xff08;ISC&#xff09;与电信终端产业协会&#xff08…

作者头像 李华
网站建设 2026/8/30 4:52:25

从背题到建体系:八股文面试的逆袭路径

上周有个读者跟我聊了很久&#xff0c;说自己把某平台整理的《Java 面试题大全》从头到尾刷了三遍&#xff0c;背得滚瓜烂熟&#xff0c;结果面试官问了一句“线程池的核心参数你在项目里调过吗&#xff1f;踩过哪些坑”&#xff0c;他当场就卡住了。他问我&#xff1a;是不是我…

作者头像 李华
网站建设 2026/8/30 4:52:20

LLM机器人进IRC,Libera.Chat政策更新带来了什么?

如果你在开源项目的 IRC 频道里挂过机器人&#xff0c;最近应该会注意到一个消息&#xff1a;Libera.Chat 更新了 Bot/LLM 政策。很多人第一反应是&#xff0c;IRC 都这么大年纪了&#xff0c;为什么还要专门为 AI 机器人出规则&#xff1f;但这个问题反过来问才更准确&#xf…

作者头像 李华