您好,登錄后才能下訂單哦!
這篇文章主要講解了總結C++17的新特性,內容清晰明了,對此有興趣的小伙伴可以學習一下,相信大家閱讀完之后會有幫助。
編譯器版本:GCC 7.1、Clang 5.0
__cplusplus:201703L
編譯選項:-std=c++17
擴展constexpr使用范圍,可用于if語句中,也可用于lambda表達式中。
例子1:
#include<iostream> template<bool 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() { foo<true>();//輸出ok,并且匯編代碼中只有std::cout << "ok" << std::endl;這一句 foo<false>();//輸出not ok,并且匯編代碼中只有std::cout << "not ok" << std::endl;這一句 return 0; }
例子2:
int main() { constexpr auto add1 = [](int n, int m){ auto func1 = [=] { return n; }; //func1 lambda表達式 auto func2 = [=] { return m; }; //func2 lambda表達式 return [=] { return func1() + func2(); }; }; constexpr auto add2 = [](int n, int m){ return n + m; }; auto add3 = [](int n, int m){ return n + m; }; int sum1 = add1(30, 40)( ); //傳入常量值,add1在編譯期計算,立即返回70 int sum2 = add2(sum1, 4); //由于傳入非constexpr變量,add2的constexpr失效,變成運行期lambda constexpr int sum3 = add3(1, 2); //sum3為constexpr變量,傳入常量值,add3變成編譯期lambda,立即返回3 int sum4 = add2(10, 2);//傳入常量值,add2在編譯期計算,立即返回12 return 0; }
擴展static_assert用法,靜態斷言的顯示文本可選。
如:
static_assert(true, ""); static_assert(true);//c++17支持
擴展auto的推斷范圍
如:
auto x1 = { 1, 2 }; //推斷出std::initializer_list<int>類型 auto x2 = { 1, 2.0 }; //錯誤:類型不統一,無法推斷 auto x3{ 1, 2 }; //錯誤:auto的聚合初始化只能一個元素 auto x4 = { 3 }; //推斷出std::initializer_list<int>類型 auto x5{ 3 }; //推斷出int類型
擴展用法,允許出現在模板的模板的參數中。
首先回顧一下typename的用法,①用于模板中,表示模板參數為類型;②用于聲明某名字是變量名
如例1:
struct A { typedef int Example; }; //第一種用法:聲明模板參數為類型 template<typename T> struct B { }; struct C { typedef typename A::Example E;//第二種用法:聲明某名字為一種類型 }; int main() { typename A::Example e;//第二種用法:聲明某名字為一種類型 return 0; }
新特性下的typename用法,
如例2:
#include<iostream> #include<typeinfo> template<typename 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 template<template<typename T> typename X> struct B { X<double> e; B() { std::cout << "B Construct" << std::endl; } }; int main() { A<B<A>> a; std::cout << "***************************" << std::endl; B<A> b; return 0; }
運行結果:
擴展用法,可用于定義內聯變量,功能與內聯函數相似。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; }
用于變長參數模板的解包,只支持各種運算符(和操作符),分左、右折疊
如:
#include<string> template<typename ... T> auto sum(T ... arg) { return (arg + ...);//右折疊 } template<typename ... T> double sum_strong(T ... arg) { return (arg + ... + 0);//右折疊 } template<typename ... T> double sub1(T ... arg) { return (arg - ...);//右折疊 } template<typename ... 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關鍵字,即綁定時聲明變量
例子1:
/* * 例子:多值返回 */ 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為double,num2為long return 0; }
例子2:
#include<list> #include<map> /* * 例子:循環遍歷 */ template<typename T, typename U> struct MyStruct { T key; U value; }; int main() { std::list<MyStruct<int, double>> Container1; std::map<int, MyStruct<long long, char>> Container2; for(auto [key, value] : Container1) { //key為int類型,value為double類型 } for(auto [key, value] : Container2) { //key為int類型,value為MyStruct<long long, char>類型 //value1為long long類型,value2為char類型 auto [value1, value2] = value; } return 0; }
非類型模板參數可傳入類的靜態成員
如:
class MyClass { public: static int a; }; template<int *arg> void foo() {} int main() { foo<&MyClass::a>(); return 0; }
在if和switch中可進行初始化
如:
template<long value> void foo(int &ok) { if constexpr (ok = 10; value > 0) { } } int main() { int num = 0; if(int i = 0; i == 0) { } foo<10>(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的值,但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); void operator delete[](void*, std::size_t size, std::align_val_t alignment);
參數說明:
size —— 分配的字節數。必須為alignment的整數倍。
alignment —— 指定的對齊內存值。必須是實現支持的合法對齊。
new的返回值:
成功,返回指向新分配內存起始地址的指針。
用法例子:
#include<new> struct alignas(8) A {}; int main() { A *a = static_cast<A *>(::operator new(sizeof(A), static_cast<std::align_val_t>(alignof (A)))); ::operator delete(a, sizeof(A), static_cast<std::align_val_t>(alignof (A))); return 0; }
為了支持泛型編程和重載運算符的廣泛使用,新特性將計算順序進行的細化
如以下爭議代碼段:
#include<map> int main() { std::map<int, int> tmp; //對于std::map的[]運算符重載函數,在使用[]新增key時,std::map就已經插入了一個新的鍵值對 tmp[0] = tmp.size();//此處不知道插入的是{0, 0}還是{0, 1} return 0; }
為了解決該情況,新計算順序規則為:
①后綴表達式從左到右求值。這包括函數調用和成員選擇表達式。
②賦值表達式從右向左求值。這包括復合賦值。
③從左到右計算移位操作符的操作數。
定義模板類的對象時,可以不指定模板參數,但必須要在構造函數中能推導出模板參數
如:
template<class 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推導為A<int>類型
A a3{0, i}; //正確,調用#2初始化成功,a2推導為A<int>類型
A a4 = {0, i}; //正確,調用#2初始化成功,a2推導為A<int>類型
template<class T> A(const T&, const T&) -> A<T&>; // #3 template<class T> explicit A(T&&, T&&) -> A<T>; // #4
A a5 = {0, 1}; //錯誤,#1和#2構造函數結果相同(即沖突)。根據#3推導為A<int&>類型
A a6{0, 1}; //正確,通過#2推斷為A<int>類型
A a7 = {0, i}; //錯誤,不能將非靜態左值引用綁定到右值。根據#3推導為A<int&>類型
A a8{0, i}; //錯誤,不能將非靜態左值引用綁定到右值。根據#3推導為A<int&>類型
template<class T> struct B { template<class U> using TA = T;//定義別名 template<class U> B(U, TA<U>);//構造函數 };
B b{(int*)0, (char*)0}; //正確,推導為B<char *>類型
2.13 簡化重復命名空間的屬性列表
如:
[[ using CC: opt(1), debug ]] void f() {} //作用相同于 [[ CC::opt(1), CC::debug ]] void f() {}
在添加屬性列表時,編譯器會忽略不支持的非標準的屬性,不會發出警告和錯誤。
在類的繼承體系中,構造函數的自動調用是一個令人頭疼的問題。新特性引入繼承與改寫構造函數的用法。
例子1:
#include<iostream> struct B1 { B1(int) { std::cout << "B1" << std::endl; } }; struct D1 : B1 { using B1::B1;//表示繼承B1的構造函數 }; D1 d1(0); //正確,委托基類構造函數進行初始化,調用B1::B1(int)
例子2:
#include<iostream> struct B1 { B1(int) { std::cout << "B1" << std::endl; } }; struct B2 { B2(int) { std::cout << "B2" << std::endl; } }; struct D1 : B1, B2 { using B1::B1;//表示繼承B1的構造函數 using B2::B2;//表示繼承B2的構造函數 }; D1 d1(0); //錯誤:函數沖突, struct D2 : B1, B2 { using B1::B1; using B2::B2; //正確,D2::D2(int)隱藏了B1::B1(int)和B2::B2(int)。另外由于B1和B2沒有默認的構造函數,因此必須顯式調用B1和B2的構造函數 D2(int) : B1(1), B2(0) { std::cout << "D2" << std::endl; } }; struct D3 : B1 { using B1::B1; }; D3 d3(0);//正確,繼承B1的構造函數,即利用B1的構造函數來初始化,輸出B1 // 程序入口 int main() { D2 d(100);//編譯通過,輸出B1 B2 D2 return 0; }
例子3:
#include<iostream> struct B1 { B1() { std::cout << "B1 default" << std::endl; } B1(int) { std::cout << "B1" << std::endl; } }; struct B2 { B2() { std::cout << "B2 default" << std::endl; } B2(int) { std::cout << "B2" << std::endl; } }; struct D1 : B1, B2 { using B1::B1; using B2::B2; //正確,D2::D2(int)隱藏了B1::B1(int)和B2::B2(int),但必須要顯示調用B1和B2的構造函數 D1(int) : B1(1), B2(0) { std::cout << "D2" << std::endl; } //有默認構造函數,在不顯示調用基類的構造函數時自動調用基類的默認構造函數 D1() { std::cout << "D2 default" << std::endl; } }; // 程序入口 int main() { D1 d(100);//編譯通過,輸出B1 B2 D2 D1 dd; //輸出 //B1 default //B2 default //D2 default return 0; }
見1.5
當模板參數為非類型時,可用auto自動推導類型
如:
#include<iostream> template<auto T> void foo() { std::cout << T << std::endl; } int main() { foo<100>();//輸出100 foo<int>();//no matching function for call to "foo<int>()" return 0; }
判斷有沒有包含某文件
如:
int main() { #if __has_include(<cstdio>) printf("hehe"); #endif #if __has_include("iostream") std::cout << "hehe" << std::endl; #endif return 0; }
用于switch語句塊內,表示會執行下一個case或default
如:
int main() { int ok1, ok2; switch (0) { case 0: ok1 = 0; [[fallthrough]]; case 1: ok2 = 1; [[fallthrough]]; } return 0; }
可用于類聲明、函數聲明、枚舉聲明中,表示函數的返回值沒有被接收,在編譯時會出現警告。
如:
[[nodiscard]] class A {}; //該屬性在這其實沒用 [[nodiscard]] enum class B {}; //該屬性在這其實沒用 class C {}; [[nodiscard]] int foo() { return 10; } [[nodiscard]] A func1() { return A(); } [[nodiscard]] B func2() { return B(); } [[nodiscard]] C func3() { return C(); } int main() { foo();//warning: ignoring return value func1();//warning: ignoring return value func2();//warning: ignoring return value func3();//warning: ignoring return value return 0; }
可用于類、typedef、變量、非靜態數據成員、函數、枚舉或枚舉值中。用于抑制編譯器對沒用實體的警告。即加上該屬性后,對某一實體不會發出“沒有用”的警告。
用法例子:
[[maybe_unused]] class A {}; [[maybe_unused]] enum B {}; [[maybe_unused]] int C; [[maybe_unused]] void fun();
看完上述內容,是不是對總結C++17的新特性有進一步的了解,如果還想學習更多內容,歡迎關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。