关键字constexpr扩展constexpr使用范围可用于if语句中也可用于lambda表达式中。#includeiostream templatebool ok constexpr void foo() { //在编译期进行判断if和else语句不生成代码 if constexpr (ok true) { //当ok为true时下面的else块不生成汇编代码 std::cout ok std::endl; } else { //当ok为false时上面的if块不生成汇编代码 std::cout not ok std::endl; } } int main() { footrue();//输出ok并且汇编代码中只有std::cout ok std::endl;这一句 foofalse();//输出not ok并且汇编代码中只有std::cout not ok std::endl;这一句 return 0; }static_assert扩展static_assert用法静态断言的显示文本可选。如static_assert(true, ); static_assert(true);//c17支持typename扩展用法允许出现在模板的模板的参数中。首先回顾一下typename的用法①用于模板中表示模板参数为类型②用于声明某名字是变量名如struct A { typedef int Example; }; //第一种用法声明模板参数为类型 templatetypename T struct B { }; struct C { typedef typename A::Example E;//第二种用法声明某名字为一种类型 }; int main() { typename A::Example e;//第二种用法声明某名字为一种类型 return 0; }新特性下的typename用法如#includeiostream #includetypeinfo templatetypename T struct A { int num; A() { std::cout A Construct std::endl; std::cout template typename is: typeid (T).name() std::endl; } }; //此处的T可省略X代表模板类型T和X前的typename可替换成class templatetemplatetypename T typename X struct B { Xdouble e; B() { std::cout B Construct std::endl; } }; int main() { ABA a; std::cout *************************** std::endl; BA b; return 0; }inlineInline 变量, inline变量可以让变量有多于一次的定义。C17之前我们定义全局变量 总需要将变量定义在cpp文件中然后在通过extern关键字来告诉编译器 这个变量已经在其他地方定义过了。 inline变量出现后我们可以直接将全局变量定义在头文件中而不用担心出现redefine的错误信息。扩展用法可用于定义内联变量功能与内联函数相似。inline可避免函数或变量多重定义的问题如果已定义相同的函数或变量且该函数或变量声明为inline编译器会自动链接到该函数或变量。如不发生错误// test.h inline void print() { std::cout hello world std::endl; } inline int num 0; // func.h include test.h inline void add(int arg) { num arg; print(); } // main.cpp include func.h int main() { num 0; print(); add(10); return 0; }auto从c11开始auto关键字能够通过初始化器推导出变量的类型。在c14中auto关键字的能力进一步提升能够通过return语句推导出函数的返回类型。使用auto关键字能够提高编码效率同时能够简化重构流程。但是C11中的auto推导往往结果与预期的不同。c11 中为了支持统一初始化引入了新的统一初始化语法如下所示。// c11 auto x3{ 1, 2 }; // std::initializer_listint auto x4 { 3 }; // decltype(x4) is std::initializer_listint auto x5{ 3 }; // std::initializer_listint这三种方式初始化的变量最终类型推导的结果都是 std::initializer_list 而不是我们认为的int。 这是因为当用于auto声明变量的表达式是{}括起来的推导的型别就会变成 std::initializer_list。在C17中对auto表达式推导的规则进行了改变// c17 auto x3{ 1, 2 }; // error: not a single element auto x4 { 3 }; // decltype(x4) is std::initializer_listint auto x5{ 3 }; // decltype(x5) is int对比发现 auto x5{3}, 会直接将变量推导成 x5 而 x3{1, 2} 这种方式也会编译失败。auto推导的规则变得更加直观。语法lambda表达式lambda也是c11中引入的在C11中lambda表达式只能用捕获thisthis是当前对象的一个只读的引用。在C17中可以捕获this, this是当前对象的一个拷贝捕获当前对象的拷贝能够确保当前对象释放后lambda表达式能安全的调用this中的变量和方法。条件表达式中支持初始化语句c17中支持在 if 或者switch 语句中进行初始化 这个能力的出现能够让代码更加的简洁。// c17之前 mapint, string c { {1,a}}; { auto res c.insert(make_pair(2, b)); if(!res.second) { cout key 1 exist endl; } else { cout insert success, value: res.first-second endl; } }上面的一段代码由于res是一个临时变量不想影响到后面的代码所以用一对花括号限制了其作用域。但是如果使用c17的语法在if条件中初始化res则代码就会显得更加简洁// c17 mapint, string c { {1,a}}; if(auto res c.insert(make_pair(2, b)); !res.second ) { cout key 1 exist endl; } else { cout insert success, value: res.first-second endl; }折叠表达式用于变长参数模板的解包只支持各种运算符和操作符分左、右折叠如#includestring templatetypename ... T auto sum(T ... arg) { return (arg ...);//右折叠 } templatetypename ... T double sum_strong(T ... arg) { return (arg ... 0);//右折叠 } templatetypename ... T double sub1(T ... arg) { return (arg - ...);//右折叠 } templatetypename ... T double sub2(T ... arg) { return (... - arg);//左折叠 } int main() { int s1 sum(1, 2, 2, 4, 5);//解包((((1)2)3)4)5 15 double s2 sum(1.1, 2.2, 3.3, 4.4, 5.5, 6.6); double s3 sum(1, 2.2, 3, 4.4, 5); double s4 sub1(5, 2, 1, 1);//解包((((5-)2-)1-)1) 1 double s5 sub2(5, 2, 1, 1);//解包(5-(2-(1-(1)))) 3 double s6 sum_strong();//s6 0 std::string str1(he); std::string str2(ll); std::string str3(o ); std::string str4(world); std::string str5 sum(str1, str2, str3, str4);//str5 hello world return 0; }结构化绑定用一对包含一个或多个变量的中括号表示结构化绑定但是使用结构化绑定时须用auto关键字即绑定时声明变量例子/* * 例子多值返回 */ struct S { double num1; long num2; }; S foo(int arg1, double arg2) { double result1 arg1 * arg2; long result2 arg2 / arg1; return {result1, result2};//返回结构体S对象 }; int main() { auto [num1, num2] foo(10, 20.2);//自动推导num1为doublenum2为long return 0; }允许非类型模板参数进行常量计算非类型模板参数可传入类的静态成员如class MyClass { public: static int a; }; templateint *arg void foo() {} int main() { fooMyClass::a(); return 0; }条件分支语句初始化在if和switch中可进行初始化如templatelong value void foo(int ok) { if constexpr (ok 10; value 0) { } } int main() { int num 0; if(int i 0; i 0) { } foo10(num); switch(int k 10; k) { case 0:break; case 1:break; default:break; } return 0; }聚合初始化在初始化对象时可用花括号进行对其成员进行赋值如struct MyStruct1 { int a; int b; }; struct MyStruct2 { int a; MyStruct1 ms; }; int main() { MyStruct1 a{10}; MyStruct2 b{10, 20}; MyStruct2 c{1, {}}; MyStruct2 d{ {}, {}}; MyStruct2 e{ {}, {1, 2}}; return 0; }嵌套命名空间简化多层命名空间的写法如//传统写法 namespace A { namespace B { namespace C { }; }; }; //新写法 namespace A::B::C { };lambda表达式捕获*this的值lambda表达式可捕获*this的值但this及其成员为只读如struct MyStruct { double ohseven 100.7; auto f() { return [this] { return [*this] { this-ohseven 200.2;//错误只读变量不可赋值 return ohseven;//正确 }; }(); } auto g() { return []{ return [*this]{};//错误外层lambda表达式没有捕获this }(); } };枚举[类]对象的构造可以给枚举[类]对象赋值如enum MyEnum { value }; MyEnum me {10};//错误不能用int右值初始化MyEnum类型对象 enum byte : unsigned char { }; byte b { 42 }; //正确 byte c { 42 }; //错误不能用int右值初始化byte类型对象 byte d byte{ 42 }; //正确其值与b相等 byte e { -1 }; //错误常量表达式-1不能缩小范围为byte类型 struct A { byte b; }; A a1 { { 42 } }; //错误不能用int右值初始化byte类型对象 A a2 { byte{ 42 } }; //正确 void f(byte); f({ 42 }); //错误无类型说明符 enum class Handle : unsigned int { value 0 }; Handle h { 42 }; //正确十六进制单精度浮点数字面值以0x前缀开头的十六进制数以f后缀的单精度浮点数合并就有了十六进制的单精度浮点数如int main() { float value 0x1111f; return 0; }基于对齐内存的动态内存分配谈到动态内存分配少不了new和delete运算符新标准中的new和delete运算符新增了按照对齐内存值来分配、释放内存空间的功能即一个新的带对齐内存值的new、delete运算符重载函数原型void* operator new(std::size_t size, std::align_val_t alignment); void* operator new[](std::size_t size, std::align_val_t alignment); void operator delete(void*, std::size_t size, std::align_val_t alignment);细化表达式的计算顺序为了支持泛型编程和重载运算符的广泛使用新特性将计算顺序进行的细化如以下争议代码段#includemap int main() { std::mapint, int tmp; //对于std::map的[]运算符重载函数在使用[]新增key时std::map就已经插入了一个新的键值对 tmp[0] tmp.size();//此处不知道插入的是{0, 0}还是{0, 1} return 0; }为了解决该情况新计算顺序规则为后缀表达式从左到右求值。这包括函数调用和成员选择表达式。赋值表达式从右向左求值。这包括复合赋值。从左到右计算移位操作符的操作数。模板类的模板参数自动推导定义模板类的对象时可以不指定模板参数但必须要在构造函数中能推导出模板参数如templateclass T struct A { explicit A(const T, ...) noexcept {} // #1 A(T, ...){} // #2 }; int i; A a1 { i, i }; //错误不能根据#1推导为右值引用也不能通过#1实现复制初始化 A a2{i, i}; //正确调用#1初始化成功a2推导为Aint类型 A a3{0, i}; //正确调用#2初始化成功a2推导为Aint类型 A a4 {0, i}; //正确调用#2初始化成功a2推导为Aint类型 templateclass T A(const T, const T) - AT; // #3 templateclass T explicit A(T, T) - AT; // #4 A a5 {0, 1}; //错误#1和#2构造函数结果相同即冲突。根据#3推导为Aint类型 A a6{0, 1}; //正确通过#2推断为Aint类型 A a7 {0, i}; //错误不能将非静态左值引用绑定到右值。根据#3推导为Aint类型 A a8{0, i}; //错误不能将非静态左值引用绑定到右值。根据#3推导为Aint类型 templateclass T struct B { templateclass U using TA T;//定义别名 templateclass U B(U, TAU);//构造函数 }; B b{(int*)0, (char*)0}; //正确推导为Bchar *类型简化重复命名空间的属性列表如[[ using CC: opt(1), debug ]] void f() {} //作用相同于 [[ CC::opt(1), CC::debug ]] void f() {}不支持、非标准的属性在添加属性列表时编译器会忽略不支持的非标准的属性不会发出警告和错误。改写与继承构造函数在类的继承体系中构造函数的自动调用是一个令人头疼的问题。新特性引入继承与改写构造函数的用法。例#includeiostream struct B1 { B1(int) { std::cout B1 std::endl; } }; struct D1 : B1 { using B1::B1;//表示继承B1的构造函数 }; D1 d1(0); //正确委托基类构造函数进行初始化调用B1::B1(int)宏__has_include判断有没有包含某文件如int main() { #if __has_include(cstdio) printf(hehe); #endif #if __has_include(iostream) std::cout hehe std::endl; #endif return 0; }属性fallthrough用于switch语句块内表示会执行下一个case或default如intmain(){intok1,ok2;switch(0){case0:ok10;[[fallthrough]];case1:ok21;[[fallthrough]];}return0;}nodiscard可用于类声明、函数声明、枚举声明中表示函数的返回值没有被接收在编译时会出现警告。如[[nodiscard]]classA{};//该属性在这其实没用[[nodiscard]]enumclassB{};//该属性在这其实没用classC{};[[nodiscard]]intfoo(){return10;}[[nodiscard]]Afunc1(){returnA();}[[nodiscard]]Bfunc2(){returnB();}[[nodiscard]]Cfunc3(){returnC();}intmain(){foo();//warning: ignoring return valuefunc1();//warning: ignoring return valuefunc2();//warning: ignoring return valuefunc3();//warning: ignoring return valuereturn0;}maybe_unused可用于类、typedef、变量、非静态数据成员、函数、枚举或枚举值中。用于抑制编译器对没用实体的警告。即加上该属性后对某一实体不会发出“没有用”的警告。用法例子[[maybe_unused]]classA{};[[maybe_unused]]enumB{};[[maybe_unused]]intC;[[maybe_unused]]voidfun();数据类型c17的标准库也进行了扩充 新增了下面几种数据类型std::variantstd::variant是类型安全的联合体是一个加强版的 unionvariant支持更加复杂的数据类型例如mapstring等等std::optionalstd::optional表示一个可能存在的值。 当我们通过函数创建一个对象时通常使用通过函数返回错误码而通过出参返回对象本身。如果通过optional返回创建的实例就会变得更加直观std::optional 提供了下面几个方法has_value() // 检查对象是否有值 value() // 返回对象的值值不存在时则抛出 std::bad_optional_access 异常 value_or() // 值存在时返回值不存在时返回默认值std::any一个类型安全的可以保存任何值的容器std::string_viewstring_view我最早使用的是boost版本的c17中的string_view 和 boost类似。string_view可以理解成原始字符串一个只读引用。 string_view 本身没有申请额外的内存来存储原始字符串的data仅仅保存了原始字符串地址和长度等信息。 在很多情况下我们只是临时处理字符串本不需要对原始字符串的一份拷贝。使用string_view可以减少不必要的内存拷贝可以提高程序性能。相比使用字符串指针string_view做了更好的封装。需要注意的是string_view 由于没有原始字符串的所有权使用string_view 一定要注意原始字符串的生命周期。当原始的字符串已经销毁则不能再调用string_view。