C++17新特性
1、折叠表达式
c++17中引入了折叠表达式,主要是方便模版编程,分为左右折叠。
折叠表达式的本质是对参数包进行「二元运算符折叠」,将参数包中的元素通过指定运算符依次结合,最终得到一个单一值。
语法:
| 类型 | 语法格式 | 说明 |
|---|---|---|
| 一元左折叠 | ( … op pack ) | 从左到右结合:((pack1 op pack2) op pack3) … |
| 一元右折叠 | ( pack op … ) | 从右到左结合:(pack1 op (pack2 op pack3)) … |
| 二元左折叠(带初始值) | ( init op … op pack ) | 初始值参与左折叠:(((init op pack1) op pack2) op pack3) … |
| 二元右折叠(带初始值) | ( pack op … op init ) | 初始值参与右折叠:(pack1 op (pack2 op (pack3 op init))) … |
eg:
一元折叠
#include<iostream>// 模板函数,接收任意数量的整数参数template<typename...T>autosum(T...args){// 一元左折叠:((arg1 + arg2) + arg3) + ...return(...+args);}intmain(){std::cout<<sum(1,2,3,4)<<std::endl;// 输出:10std::cout<<sum(10,20)<<std::endl;// 输出:30// 注意:参数包不能为空(一元折叠要求至少一个元素)// std::cout << sum() << std::endl; // 编译错误!return0;}二元折叠
#include<iostream>template<typename...T>autosum(T...args){// 二元右折叠:arg1 + (arg2 + (arg3 + 0))// 初始值0,空参数包时返回0return(args+...+0);}intmain(){std::cout<<sum()<<std::endl;// 输出:0(空参数包)std::cout<<sum(1,2,3)<<std::endl;// 输出:6return0;}2、类模版参数推导
类模板实例化时,可以不必显式指定类型,前提是保证类型可以推导
#include<iostream>usingnamespacestd;template<classT>classClassTest{public:ClassTest(T,T){};};intmain(){autoy=newClassTest{100,200};// 分配的类型是 ClassTest<int>return0;}3、auto 占位的非类型模板形参
#include<iostream>usingnamespacestd;template<autoT>voidfunc1(){cout<<T<<endl;}intmain(){func1<100>();//func1<int>();return0;}4、编译期constexpr if语句
#include<iostream>usingnamespacestd;template<boolok>constexprvoidfunc2(){//在编译期进行判断,if和else语句不生成代码ifconstexpr(ok==true){//当ok为true时,下面的else块不生成汇编代码cout<<"ok"<<endl;}else{//当ok为false时,上面的if块不生成汇编代码cout<<"not ok"<<endl;}}intmain(){func2<true>();//输出ok,并且汇编代码中只有 cout << "ok" << endl;func2<false>();//输出not ok,并且汇编代码中只有 cout << "not ok" << endl; return 0;}5、inline变量
扩展的inline用法,使得可以在头文件或者类内初始化静态成员变量
// mycode.hinlineintvalue=100;// mycode.cppclassAAA{inlinestaticintvalue2=200;};6、结构化绑定
结构化绑定(Structured Bindings)核心作用是一次性将复合类型(如结构体、数组、std::pair/tuple)的多个成员 / 元素绑定到多个变量上,避免手动逐个提取成员,让代码更简洁、易读。
- 绑定数组
#include<iostream>usingnamespacestd;intmain(){intarr[]={10,20,30};// 绑定数组的3个元素到a、b、cauto[a,b,c]=arr;cout<<a<<", "<<b<<", "<<c<<endl;// 输出:10, 20, 30// 注意:默认是值拷贝,修改a不会影响原数组a=100;cout<<arr[0]<<endl;// 输出:10(原数组未变)// 用引用绑定,修改会影响原数组auto&[x,y,z]=arr;x=100;cout<<arr[0]<<endl;// 输出:100(原数组被修改)return0;}- 绑定简单结构体/类(要求结构体的所有非静态成员都是公有(private/protected 成员无法绑定))
#include<iostream>#include<string>usingnamespacestd;// 简单结构体(所有成员公有)structPerson{string name;intage;doubleheight;};intmain(){Person p{"Alice",25,1.65};// 绑定结构体的3个成员到name、age、heightauto[name,age,height]=p;cout<<"姓名:"<<name<<",年龄:"<<age<<",身高:"<<height<<endl;// const 引用绑定(避免拷贝,且禁止修改)constauto&[n,a,h]=p;// n = "Bob"; // 编译错误:const引用不可修改return0;}- 绑定 std::pair/std::tuple(最常用场景)
STL 中 std::pair(如 map 的键值对)、std::tuple 是结构化绑定的高频使用场景
#include<iostream>#include<tuple>#include<map>#include<string>usingnamespacestd;intmain(){// 1. 绑定std::pair(map的元素是pair)map<string,int>score_map={{"Math",90},{"English",85}};// 遍历map,绑定键值对到key和valuefor(constauto&[key,value]:score_map){cout<<key<<":"<<value<<endl;}// 2. 绑定std::tupletuple<string,int,double>t("Tom",30,1.75);auto[name,age,height]=t;cout<<"Tuple:"<<name<<", "<<age<<", "<<height<<endl;return0;}- 绑定返回值(函数返回复合类型)
#include<iostream>#include<tuple>usingnamespacestd;// 函数返回多个值(tuple)tuple<int,string,double>get_student_info(){return{101,"zxp",99.9};}intmain(){// 直接绑定返回值的三个元素auto[id,name,score]=get_student_info();cout<<"学号:"<<id<<",姓名:"<<name<<",分数:"<<score<<endl;return0;}7、lambda表达式捕获 *this
- 捕获this:lambda 拿到对象的指针(引用),多线程中若原对象已销毁,lambda 访问成员变量会触发未定义行为;
- C++17 捕获*this:lambda 直接拷贝整个对象(捕获副本),不受原对象生命周期影响,默认只能读、无法修改原对象(修改的是副本)。
#include<iostream>#include<thread>usingnamespacestd;classTest{public:intnum=10;voidfunc(){// 1. 捕获this:指向原对象,原对象销毁后访问num会出错autolambda1=[this](){cout<<this->num<<endl;// 依赖原对象存活};// 2. C++17捕获*this:拷贝对象副本,原对象销毁也不影响autolambda2=[*this](){cout<<this->num<<endl;// 访问的是副本的num};// 模拟多线程场景(仅示例,实际需注意线程生命周期)threadt1(lambda1);threadt2(lambda2);t1.detach();// 若Test对象提前析构,t1访问num是未定义行为t2.detach();// t2访问的是副本,安全}};intmain(){{Test t;t.func();// 函数执行完后,t会被销毁}return0;}8、__has_include
跨平台项目需要考虑不同平台编译器的实现,使用__has_include可以判断当前环境下是否存在某个头文件。
intmain(){#if__has_include("iostream")cout<<"iostream exist."<<endl;#endif#if__has_include(<cmath>)cout<<"<cmath> exist."<<endl;#endifreturn0;}9、std::filesystem
常用函数std::filesystem::exists(const path& pval):用于判断path是否存在std::filesystem::copy(const path& from, const path& to):目录复制std::filesystem::absolute(const path& pval, const path& base = current_path()):获取相对于base的绝对路径std::filesystem::create_directory(const path& pval):当目录不存在时创建目录std::filesystem::create_directories(const path& pval):形如/a/b/c这样的,如果都不存在,创建目录结构std::filesystem::file_size(const path& pval):返回目录的大小
10、std::string_view
C++ 中char*转std::string会触发内存拷贝(调用拷贝构造),即便只读操作也会产生不必要的性能损耗;C++17 新增std::string_view,仅作为char*/std::string的「只读视图」,不拷贝内存,仅保留字符串的指针和长度,只读不可写,优化了只读场景下的内存开销。
#include<string_view>#include<string>usingnamespacestd;voidread_str(constchar*cstr){// string:拷贝内存,性能损耗string s=cstr;// string_view:仅视图,无内存拷贝,只读string_view sv=cstr;}11、std::optional
C / 早期 C++ 用T*指针(NULL/nullptr)表示 “有值 / 无值”;C++17 引入std::optional<>,以类型安全的方式封装 “值存在 / 不存在” 的语义,无需依赖指针,通过内置标志位标记值是否有效。
#include<optional>usingnamespacestd;// 可选的int值:有值则存数值,无值则为空optional<int>get_value(boolhas_val){if(has_val)return10;return{};// 无值状态}12、std::variant
C++17 的std::variant是增强版union,支持存储复合类型,同一时间仅能保存一种类型的值;可通过index()获取当前值对应模板参数的索引(从 0 开始),或用holds_alternative<T>(v)判断当前值是否为类型T。
#include<variant>#include<iostream>usingnamespacestd;intmain(){variant<int,string,double>v="hello";cout<<v.index()<<endl;// 输出1(对应string,索引从0开始)cout<<boolalpha<<holds_alternative<string>(v)<<endl;// 输出truereturn0;}