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

【C++】一篇文章详解C++17新特性
语法糖这部分特性不改变语言核心逻辑却能极大减少重复代码让代码更简洁、可读性更高是日常开发中最容易上手的特性。if-else 初始化语句If with initializerC17允许在if/else语句的条件判断前先初始化一个变量变量的作用域仅限于if/else代码块内部避免变量污染全局或外层作用域。使用场景需要临时变量参与条件判断如指针判空、容器查找、锁的获取且变量无需在判断后复用。代码对比C11 and C17// C11 写法变量作用域超出if可能被误复用autoitmap.find(key);if(it!map.end()){coutit-secondendl;}// C17 写法变量it仅在if内部有效更安全、简洁if(autoitmap.find(key);it!map.end()){coutit-secondendl;}类似地switch语句也支持初始化switch(autovalgetValue();val){case1:break;case2:break;}结构化绑定Structured Bindings这是C17最受欢迎的特性之一允许一次性从“聚合类型”结构体、数组、std::pair、std::tuple等中提取多个成员无需逐个赋值代码简洁度大幅提升。支持的类型结构体/类必须是聚合类型即无私有成员、无构造函数等数组固定大小std::pair、std::tupleSTL常用容器std::array固定大小数组。代码示例#includeiostream#includemap#includetuple// 1. 结构体绑定structPoint{intx;doubley;};// 2. 数组绑定intarr[]{10,20,30};// 3. tuple绑定std::tuplestd::string,int,doublegetInfo(){return{Alice,25,90.5};}intmain(){// 结构体绑定Point p{1,3.14};auto[x,y]p;// 一次性提取x和y类型自动推导coutx, yendl;// 输出1, 3.14// 数组绑定需指定绑定个数与数组大小一致auto[a,b,c]arr;couta, b, cendl;// 输出10, 20, 30// tuple绑定auto[name,age,score]getInfo();coutname, age, scoreendl;// 输出Alice, 25, 90.5// 结合map使用map的迭代器返回pairstd::mapstd::string,intscoreMap{{math,95},{english,88}};for(auto[key,val]:scoreMap){// 遍历map更简洁coutkey: valendl;}return0;}注意点结构化绑定默认是“值绑定”若需修改原对象需加引用auto [x,y] p;若原对象不可修改加constconst auto [x,y] p;。折叠表达式Fold ExpressionsC11引入可变参数模板后处理可变参数时需要写递归或借助辅助函数代码繁琐。C17的折叠表达式的可以简洁地处理可变参数模板支持算术运算、逻辑运算、函数调用等场景。核心语法将可变参数包与运算符结合自动展开参数包无需递归。代码示例常用场景#includeiostream// 1. 求和二元折叠参数包与结合templatetypename...Argsautosum(Args...args){return(args...);// 折叠表达式等价于 args1 args2 ... argsN}// 2. 打印所有参数函数调用折叠templatetypename...Argsvoidprint(Args...args){(std::cout...args)endl;// 展开为 cout args1 args2 ...}// 3. 逻辑与判断所有参数是否都为truetemplatetypename...ArgsboolallTrue(Args...args){return(args...);// 展开为 args1 args2 ...}intmain(){coutsum(1,2,3,4)endl;// 输出10print(Hello, ,C17,!,123);// 输出Hello C17!123coutallTrue(true,1,3.14)endl;// 输出1truecoutallTrue(true,0,3.14)endl;// 输出0falsereturn0;}折叠表达式支持的运算符包括、-、*、/、%、、|、^、、||、, 等几乎覆盖所有常用场景是可变参数模板开发的“神器”。STL容器扩展C17对STL进行了大量补充和优化新增了实用容器、简化了容器操作同时提升了部分操作的性能是日常开发中使用频率最高的部分。新增容器std::optional、std::variant、std::any这三个容器解决了C长期存在的“空值处理”和“多类型存储”问题替代了传统的“指针判空”“union有局限性”更安全、更易维护。std::optional可选值容器作用表示一个“可能存在、也可能不存在”的值避免使用 nullptr 或魔法值如-1、0表示空语义更清晰、更安全。使用场景函数返回值可能为空如查找操作、解析操作。#includeiostream#includeoptional#includestring// 查找字符串中的某个字符找到返回该字符找不到返回空std::optionalcharfindChar(conststd::stringstr,charc){autoposstr.find(c);if(pos!std::string::npos){returnstr[pos];// 返回有效值}returnstd::nullopt;// 返回空值}intmain(){autores1findChar(C17,7);if(res1.has_value()){// 判断是否有值cout找到字符res1.value()endl;// 输出找到字符7}autores2findChar(C17,x);if(!res2){// 简化判断optional可直接作为bool值cout未找到字符endl;// 输出未找到字符}// 安全访问无值时返回默认值coutres2.value_or(?)endl;// 输出?return0;}std::variant多类型容器类型安全的union作用表示一个“只能存储多种指定类型中的一种”的值替代传统union传统union不支持非POD类型如std::string且是类型安全的会检查当前存储的类型。使用场景需要存储不同类型但互斥的值如配置项、解析结果。#includeiostream#includevariant#includestring// 定义一个variant只能存储int、double、string中的一种usingMyVariantstd::variantint,double,std::string;// 访问variant的辅助函数使用std::visitvoidprintVariant(constMyVariantv){std::visit([](autoval){cout当前值valendl;},v);}intmain(){MyVariant v1100;// 存储intMyVariant v23.14;// 存储doubleMyVariant v3C17;// 存储stringprintVariant(v1);// 输出当前值100printVariant(v2);// 输出当前值3.14printVariant(v3);// 输出当前值C17// 检查当前存储的类型if(v1.index()0){// 0int1double2stringcoutv1 存储的是int类型endl;}// 尝试获取指定类型的值失败会抛异常try{autovalstd::getint(v1);// 成功autoval2std::getdouble(v1);// 失败抛异常}catch(conststd::bad_variant_accesse){cout类型错误e.what()endl;}return0;}std::any任意类型容器作用表示一个“可以存储任意类型”的值与variant的区别是variant需要提前指定可能的类型any无需指定更灵活但类型安全不如variant需手动检查类型。使用场景需要存储任意类型且无法提前确定类型的场景如通用配置、脚本绑定。#includeiostream#includeany#includestringintmain(){std::any a110;std::any a23.14f;std::any a3std::string(Hello);// 检查类型并获取值if(a1.type()typeid(int)){coutstd::any_castint(a1)endl;// 输出10}// 错误示例类型不匹配会抛异常try{std::any_castdouble(a1);}catch(conststd::bad_any_caste){cout类型转换失败e.what()endl;}// 重置any清空值a1.reset();if(!a1.has_value()){couta1 为空endl;}return0;}STL容器新增实用方法C17为常用容器std::map、std::unordered_map、std::vector等新增了多个便捷方法减少冗余代码提升效率。1std::map/unordered_maptry_emplace、insert_or_assign解决了传统insert方法“插入时需先判断是否存在”的繁琐问题同时避免不必要的拷贝。#includeiostream#includemap#includestringintmain(){std::mapstd::string,intscoreMap;// 1. try_emplace不存在则插入存在则不操作避免拷贝// 第一个参数是key后面是value的构造参数scoreMap.try_emplace(math,95);// 插入成功scoreMap.try_emplace(math,100);// 已存在不操作// 2. insert_or_assign不存在则插入存在则修改valuescoreMap.insert_or_assign(english,88);// 插入scoreMap.insert_or_assign(math,98);// 已存在修改为98for(auto[k,v]:scoreMap){coutk: vendl;// 输出math:98, english:88}return0;}2std::vectordata() 方法增强、emplace_back 优化C17中std::vector::data() 支持空vector返回nullptr同时emplace_back 进一步优化支持直接构造对象避免拷贝和移动仅当参数为右值时。3std::stringstring_view 配合使用C17新增 std::string_view非容器是字符串的“视图”可以快速访问字符串无需拷贝配合std::string使用提升性能后续单独补充。std::string_view轻量级字符串视图核心作用表示一个字符串的“只读视图”不拥有字符串所有权仅存储字符串的起始地址和长度无拷贝、无内存分配效率极高。使用场景函数参数传递、字符串截取、只读访问字符串避免不必要的拷贝。#includeiostream#includestring_view#includestring// 函数参数用string_view避免拷贝voidprintString(std::string_view sv){coutsv, 长度sv.size()endl;}intmain(){std::string strC17 New Features;// 1. 直接用string构造string_view无拷贝std::string_view sv1str;printString(sv1);// 输出C17 New Features, 长度18// 2. 截取字符串无拷贝std::string_view sv2sv1.substr(0,4);// 截取前4个字符printString(sv2);// 输出C17, 长度4// 3. 直接用字符串字面量构造无拷贝printString(Hello C17);// 输出Hello C17, 长度11// 注意string_view不拥有所有权若原字符串被销毁sv会变成野指针std::string_view sv3;{std::string temptemp string;sv3temp;}// sv3此时指向已销毁的内存访问会未定义行为return0;}注意点string_view 不拥有字符串所有权必须确保原字符串的生命周期长于string_view否则会出现野指针。std::filesystemC17 标准文件系统库C17 正式引入了跨平台文件系统库std::filesystem让文件、目录、路径操作变得简单安全无需再编写平台相关的文件操作代码。核心作用跨平台文件/目录管理、路径拼接与解析、遍历目录、判断文件类型、获取文件信息等。优势类型安全、自动处理路径分隔符/和\、无需第三方库、全平台通用。极简使用#includefilesystemnamespacefsstd::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()简洁示例fs::path ptest/demo.txt;if(fs::exists(p)){std::cout文件名p.filename()\n;std::cout后缀p.extension()\n;}注意GCC 8 以下需链接库-lstdcfs高版本 GCC/Clang/VS 直接支持 C17 即可使用。性能与安全优化这部分特性虽然不常直接写在代码中但能间接提升程序性能、减少内存泄漏和未定义行为是C17对语言底层的重要优化。1. 类模板参数推导Class Template Argument Deduction, CTADC11/14中模板类的构造函数需要显式指定模板参数C17允许编译器根据构造函数的参数自动推导模板参数简化代码同时避免手动指定参数出错。#includeiostream#includevector#includepair#includetupleintmain(){// C11/14 写法必须显式指定模板参数std::vectorintv1{1,2,3};std::pairint,std::stringp1(1,hello);std::tupleint,double,stringt1(10,3.14,test);// C17 写法自动推导模板参数CTADstd::vector v2{1,2,3};// 推导为 std::vectorintstd::pairp2(1,hello);// 推导为 std::pairint, const char*std::tuplet2(10,3.14,test);// 推导为 std::tupleint, double, const char*return0;}几乎所有STL模板类vector、pair、tuple、shared_ptr、unique_ptr等都支持CTAD极大简化了模板类的使用。2. constexpr 增强C11引入constexpr用于定义“编译期常量”但限制较多如函数只能有一个return语句。C17大幅放宽了constexpr的限制允许在constexpr函数中使用循环、条件判断、局部变量等让更多代码可以在编译期执行提升程序运行效率。#includeiostream// C17 中constexpr函数可以有循环和条件判断constexprintfactorial(intn){intres1;for(inti1;in;i){// 循环res*i;}returnres;}// 编译期计算阶乘运行时无需计算constexprintf5factorial(5);// 编译期计算出 120constexprintf10factorial(10);// 编译期计算出 3628800intmain(){coutf5, f10endl;// 直接输出编译期计算结果return0;}优势将计算从运行时转移到编译期减少运行时开销同时保证常量的正确性编译期检查。3. 消除未定义行为if constexprif constexpr 是“编译期条件判断”与普通if的区别是普通if的两个分支都会被编译而if constexpr 只会编译满足条件的分支避免了编译期的未定义行为如访问不存在的成员。使用场景模板编程中根据模板参数的类型执行不同的代码分支。#includeiostream#includetype_traits// 模板函数根据类型打印不同内容templatetypenameTvoidprintType(constTval){// 编译期判断T是否为int类型ifconstexpr(std::is_integral_vT){cout整数类型valendl;}elseifconstexpr(std::is_floating_point_vT){cout浮点类型valendl;}else{cout其他类型endl;}}intmain(){printType(10);// 输出整数类型10printType(3.14);// 输出浮点类型3.14printType(hello);// 输出其他类型return0;}如果用普通if即使某个分支在运行时不会执行编译时也会检查该分支的语法正确性如访问不存在的成员而if constexpr 只会编译满足条件的分支避免了这种问题。其他常用特性1. 嵌套命名空间Nested NamespacesC17简化了嵌套命名空间的写法无需层层嵌套代码更简洁。// C11/14 写法namespaceA{namespaceB{namespaceC{voidfunc(){}}}}// C17 写法等价namespaceA::B::C{voidfunc(){}}// 使用时不变A::B::C::func();2. 废弃throw()使用noexceptC11引入noexcept表示函数不会抛出异常C17正式废弃了旧的throw()语法统一使用noexcept更简洁、更明确。// C11 写法废弃voidfunc()throw(){}// C17 写法推荐voidfunc()noexcept{}