C++11特性(详细版)
C11
- 1、C11優勢
- 2、列表初始化
- 3、變量類型推導
- 1、為什么需要類型推導
- 2、decltype類型推導(了解)
- 為什么需要decltype
- decltype
- 4、final 與 override
- final
- override
- 5、默認成員函數控制
- 1、顯示缺省函數
- 2、刪除默認函數(禁止調用)
- 6、右值引用與移動語義
- 1、左值引用和右值引用
- 1、什么是左值?什么是左值引用?
- 2、什么是右值?什么是右值引用?
- 2、左值引用與右值引用比較
- 3、右值引用使用場景和意義
- 7、完美轉發
- 8、新的類功能
- 9、lambda表達式
- lambda表達式語法
- 10、可變參數列表(先學會基本特性)
- 可變參數包結合完美轉發的好處
- 11、包裝器
1、C11優勢
??相比C++98/03,C++11則帶來了數量可觀的變化,其中包含了約140個新特性,以及對C++03標準中約600個缺陷的修正,這使得C++11更像是從C++98/03中孕育出的一種新語言。相比較而言,C++11能更好地用于系統開發和庫開發、語法更加泛華和簡單化、更加穩定和安全,不僅功能更強大,而且能提升程序員的開發效率。
2、列表初始化
C++98對于自定義類型,無法使用列表初始化,在C++11中改進了
C++11擴大了用大括號括起的列表(初始化列表)的使用范圍,使其可用于所有的內置類型和用戶自定義的類型,使用初始化列表時,可添加等號(=),也可不添加。
1、內置類型的列表初始化
// 內置類型變量 int x1 = {10}; int x2{10};//建議使用原來的 int x3 = 1+2; int x4 = {1+2}; int x5{1+2}; // 數組 int arr1[5] {1,2,3,4,5}; int arr2[]{1,2,3,4,5}; // 動態數組,在C++98中不支持 int* arr3 = new int[5]{1,2,3,4,5}; // 標準容器 vector<int> v{1,2,3,4,5};//這種初始化就很友好,不用push_back一個一個插入 map<int, int> m{{1,1}, {2,2,},{3,3},{4,4}};2、自定義類型的列表初始化
多個對象想要支持列表初始化,需給該類(模板類)添加一個帶有initializer_list類型參數的構造函數即可。
注意:initializer_list是系統自定義的類模板,該類模板中主要有三個方法:begin()、end()迭代器以及獲取區間中元素個數的方法size()
3、變量類型推導
1、為什么需要類型推導
??在定義變量時,必須先給出變量的實際類型,編譯器才允許定義,但有些情況下可能不知道需要實際類型怎么給,或者類型寫起來特別復雜
int main() {short a = 32670;short b = 32670;// c如果給成short,會造成數據丟失,如果能夠讓編譯器根據a+b的結果推導c的實際類型,就不會存在問題short c = a + b;std::map<std::string, std::string> m{ {"apple", "蘋果"}, {"banana","香蕉"} };// 使用迭代器遍歷容器, 迭代器類型太繁瑣std::map<std::string, std::string>::iterator it = m.begin();while (it != m.end()){cout << it->first << " " << it->second << endl;++it;}return 0; }C++11中,可以使用auto來根據變量初始化表達式類型推導變量的實際類型,可以給程序的書寫提供許多方便。將程序中c與it的類型換成auto,程序可以通過編譯,而且更加簡潔。
// 使用迭代器遍歷容器, 迭代器類型太繁瑣 可以使用auto //std::map<std::string, std::string>::iterator it = m.begin(); auto it = m.begin();2、decltype類型推導(了解)
為什么需要decltype
auto使用的前提是:必須要對auto聲明的類型進行初始化,否則編譯器無法推導出auto的實際類型。但有時候可能需要根據表達式運行完成之后結果的類型進行推導,因為編譯期間,代碼不會運行,此時auto也就無能為力。
decltype
decltype是根據表達式的實際類型推演出定義變量時所用的類型,比如
1、推演表達式類型作為變量的定義類型
2. 推演函數返回值的類型
4、final 與 override
final
1、final修飾類的時候,表示該類不能被繼承
class A final //表示該類是最后一個類 { private:int _year; }; class B : public A //無法繼承 {};
2、final修飾虛函數時,這個虛函數不能被重寫
override
檢查派生類虛函數是否重寫了基類某個虛函數,如果沒有重寫編譯報錯
class A { public:virtual void fun(){cout << "this is A" << endl;} private:int _year; }; class B : public A { public:virtual void fun() override{cout << "this is B" << endl;} };5、默認成員函數控制
在C++中對于空類編譯器會生成一些默認的成員函數,比如:構造函數、拷貝構造函數、運算符重載、析構函數和&和const&的重載、移動構造、移動拷貝構造等函數。如果在類中顯式定義了,編譯器將不會重新生成默認版本。有時候這樣的規則可能被忘記,最常見的是聲明了帶參數的構造函數,必時則需要定義不帶參數的版本以實例化無參的對象。而且有時編譯器會生成,有時又不生成,容易造成混亂,于是C++11讓程序員可以控制是否需要編譯器生成。
1、顯示缺省函數
在C++11中,可以在默認函數定義或者聲明時加上=default,從而顯式的指定編譯器生成該函數的默認版本(默認成員函數),用=default修飾的函數稱為顯式缺省函數。
class A { public:A() = default;//讓編譯器默認生成無參構造函數A(int year) //這樣不寫缺省值的時候,就不需要自己在去實現一個默認的無參構造函數:_year(year){}void fun(){cout << "this is A" << endl;} private:int _year; };2、刪除默認函數(禁止調用)
如果能想要限制某些默認函數的生成,在C++98中,是該函數設置private,并且不給定義,這樣只要其他人想要調用就會報錯。在C++11中更簡單,只需在該函數聲明加上=delete即可,該語法指示編譯器不生成對應函數的默認版本,稱=delete修飾的函數為刪除函數。
class A { public:A() = default;A(int a) : _a(a){}//C++11// 禁止編譯器生成默認的拷貝構造函數以及賦值運算符重載A(const A&) = delete;A& operator=(const A&) = delete; private:int _a;//C++98,設置成private就可以了A(const A&) = delete;A& operator=(const A&) = delete; };6、右值引用與移動語義
1、左值引用和右值引用
傳統的C++就有引用,稱為左值引用,C++11后,出了右值引用。無論是左值引用還是右值引用,都是給對象取別名(與對象共享一片空間)。
1、什么是左值?什么是左值引用?
??左值是一個表示數據的表達式(如變量名和解引用的指針),我們可以獲取它的地址,也可以對它賦值,左值可以出現在賦值符號的左邊,右值不可以出現在左邊。左引用加const修飾,不能對其賦值,但可取地址,是一種特殊情況。左值引用就是給左值取別名。
//以下都是左值 int* p = new int[10]; int a = 10; const int b = 20; //對左值的引用 int*& pp = p; int& pa = a; const int& rb = b;左值:
??1、可以取地址
??2、一般情況下可以修改(const修飾時不能修改)
2、什么是右值?什么是右值引用?
??右值也是一個表示數據的表達式,如:字面常量、表達式返回值、傳值返回函數的返回值(不能是左值引用返回)等,右值可以出現在賦值符號的右邊,但是不能出現在左邊。右值引用就是給右值取別名。
double x = 1.1, y = 2.2; //常見右值 10; x + y; add(1, 2); //右值引用 int&& rr1 = 10; double&& rr2 = x + y; double && rr3 = add(1, 2); //右值引用一般情況不能引用左值,可使用move將一個左值強制轉化為右值引用 int &&rr4 = move(x); //右值不能出現在左邊,錯誤 10 = 1; x + y = 1.0; add(1, 2) = 1;move:當需要用右值引用引用一個左值時,可以通過move函數將左值轉化為右值。C++11中,std::move()函數位于頭文件中,該函數名字具有迷惑性,它并不搬移任何東西,唯一的功能就是將一個左值強制轉化為右值引用,然后實現移動語義。
2、左值引用與右值引用比較
左值引用總結:
右值引用總結:
3、右值引用使用場景和意義
??左值引用既可以引用左值,可以引用右值,為什么C++11還要提出右值引用?因為左值引用存在短板,下面我們來看看這個短板以及右值引用是如何彌補這個短板的!
void fun1(bit::string s) {} void fun2(bit::string& s) {} int main() {bit::string s("1234");//fun1(s);//fun2(s);//左值引用提高了效率,不存在拷貝臨時對象的問題//可以使用左值引用返回,這個對象還在s += 'a';//不能使用左值引用返回,這個就是左值引用的一個短板//函數返回對象出了作用域就不在了,就不能用左值引用返回(因為返回的是本身地址,棧幀已銷毀)//所以會存在拷貝問題bit::string ret = bit::to_string(1234);return 0; }?右值引用彌補這個短板(右值引用場景1)
C++11移動語義的提出:將一個對象中資源移動到另一個對象中的方式。
??str在按照值返回時,必須創建一個臨時對象,臨時對象創建好之后,str就被銷毀了,str是一個將亡值,C++11認為其為右值,在用str構造臨時對象時,就會采用移動構造,即將str中資源轉移到臨時對象中。而臨時對象也是右值,因此在用臨時對象構造s3時,也采用移動構造,將臨時對象中資源轉移到ret中,整個過程,只需要創建一塊堆內存即可,既省了空間,又大大提高程序運行的效率。
這里我們就又可以對右值進行一個定義:
右值:1、純右值 10 a+b 2、將亡值,函數返回的臨時對象,匿名對象
此時我們將這一條語句分開寫,看看又是什么情況
bit::string ret; ret = bit::to_string(1234);//賦值重載多了一次拷貝構造
我們將移動賦值寫上,就會進行優化,少一次拷貝構造
總結一下:右值引用出來以后,并不是直接使用右值引用去減少拷貝,提高效率。而是支持深拷貝的類,提供移動構造和移動賦值,這時這些類的對象進行傳值返回或者是參數為右值時,則可以用移動構造和移動賦值,轉移資源,避免深拷貝,提高效率。
以上是右值使用的場景1
//左值,拷貝構造,使用左值引用 list<bit::string> lt; bit::string s("1234"); lt.push_back(s); //以下傳的都是右值,右值引用,所以是移動構造 lt.push_back("123"); lt.push_back(bit::string("2121")); lt.push_back(std::move(s));總結一下:右值引用使用場景二,還可以使用在容器插入接口函數中,如果實參是右值,則可以轉移它的資源,減少拷貝
7、完美轉發
完美轉發是指在函數模板中,完全依照模板的參數的類型,將參數傳遞給函數模板中調用的另外一個函數。
void Func(int x) {cout << x << endl; } template<typename T> void PerfectForward(T&& t) {Func(t); }??PerfectForward為轉發的模板函數,Func為實際目標函數,但是上述轉發還不算完美,完美轉發是目標函數總希望將參數按照傳遞給轉發函數的實際類型轉給目標函數,而不產生額外的開銷,就好像轉發者不存在一樣。
所謂完美:函數模板在向其他函數傳遞自身形參時,如果相應實參是左值,它就應該被轉發為左值;如果相應實參是右值,它就應該被轉發為右值。這樣做是為了保留在其他函數針對轉發而來的參數的左右值屬性進行不同處理(比如參數為左值時實施拷貝語義;參數為右值時實施移動語義)。
我們先來了解萬能引用
1、模板中的&&不代表右值引用,而是萬能引用,其既能接收左值又能接收右值。
2、模板的萬能引用只是提供了能夠接收同時接收左值引用和右值引用的能力
3、但是引用類型的唯一作用就是限制了接收的類型,后續使用中都退化成了左值
4、我們希望能夠在傳遞過程中保持它的左值或者右值的屬性,就需要用我們下面學習的完美轉發
C++11通過forward函數來實現完美轉發
void Func(int& x) { cout << "左值引用" << endl; } void Func(const int& x) { cout << "const 左值引用" << endl; }void Func(int&& x) { cout << "右值引用" << endl; } void Func(const int&& x) { cout << "const 右值引用" << endl; } template<typename T> void PerfectForward(T&& t) {//Func(t);//沒有使用forward保持其右值的屬性,退化為左值Func(forward<T>(t)); } int main() {PerfectForward(1);//右值int a = 10;PerfectForward(a);PerfectForward(move(a));const int b = 20;PerfectForward(b);PerfectForward(move(b));return 0; }右值引用的對象,再作為實參傳遞時,屬性會退化為左值,只能匹配左值引用。使用完美轉發,可以保持他的右值屬性
8、新的類功能
默認成員函數
原來C++類中,有6個默認成員函數:
重要的是前4個,后兩個用處不大。默認成員函數就是我們不寫編譯器會生成一個默認的。
C++11新增了兩個:移動構造函數和移動賦值逸算符重載。
C++11新增了兩個:移動構造函數和移動賦值運算符重載。
針對移動構造函數和移動賦值運算符重載有一些需要注意的點如下:
- 如果你沒有自己實現移動構造函數,且沒有實現析構函數、拷貝構造、拷貝賦值重載中的任意一個。那么編譯器會自動生成一個默認移動構造。默認生成的移動構造函數,對于內置類型成員會執行逐成員按字節拷貝,自定義類型成員,則需要看這個成員是否實現移動構造,如果實現了就調用移動構造,沒有實現就調用拷貝構造。
- 如果你沒有自己實現移動賦值重載函數,目沒有實現析構函數。接貝構造、拷貝賦值重載中的任意一個,那么編譯器會自動生成一個默認移動賦值。默認生成的移動構造函數,對于內置類型成員會執行逐成員按字節拷貝,自定義類型成員,則需要看這個成員是否實現移動賦值,如果實現了就調用移動賦值,沒有實現就調用拷貝賦值。(默認移動賦值跟上面移動構造完全類似)
- 如果你提供了移動構造或者移動賦值,編譯器不會自動提供拷貝構造和拷貝賦值。
沒有實現移動構造的情況
沒有實現移動賦值的情況
C++對于自定義類型成員變量非常的友好,默認成員函數都會恰當處理自定義類型成員
9、lambda表達式
之前我們要比較自定義類型的一個大小,需要自己實現一個類,并寫上仿函數,這樣有點復雜。
struct Goods {string _name;double _price; }; struct Compare {bool operator()(const Goods& gl, const Goods& gr){return gl._price <= gr._price;} }; int main() {Goods gds[] = { { "蘋果", 2.1 }, { "相交", 3 }, { "橙子", 2.2 }, {"菠蘿", 1.5} };sort(gds, gds+sizeof(gds) / sizeof(gds[0]), Compare());return 0; }之前自己寫的過于復雜,隨著lambda的推出,寫這種比較大小排序就比較簡單了。
int main() {Goods gds[] = { { "蘋果", 2.1 }, { "相交", 3 }, { "橙子", 2.2 }, {"菠蘿", 1.5} };sort(gds, gds + sizeof(gds) / sizeof(gds[0]), [](const Goods& l, const Goods& r)->bool{return l._price < r._price;});return 0; }上面的寫法,相當于是把函數直接寫到sort的第三個位置上,接下來我們來看一下lambda的語法。
lambda表達式語法
lambda表達式書寫格式:[capture-list] (parameters) mutable -> return-type { statement }
- [capture-list] : 捕捉列表,該列表總是出現在lambda函數的開始位置,編譯器根據[]來判斷接下來的代碼是否為lambda函數,捕捉列表能夠捕捉上下文中的變量供lambda函數使用。
- (parameters):參數列表。與普通函數的參數列表一致,如果不需要參數傳遞,則可以連同()一起省略
- mutable:默認情況下,lambda函數總是一個const函數,mutable可以取消其常量性。使用該修飾符時,參數列表不可省略(即使參數為空)。
- ->returntype:返回值類型。用追蹤返回類型形式聲明函數的返回值類型,沒有返回值時此部分可省略。返回值類型明確情況下,也可省略,由編譯器對返回類型進行推導。
- {statement}:函數體。在該函數體內,除了可以使用其參數外,還可以使用所有捕獲到的變量。
注意: 在lambda函數定義中,參數列表和返回值類型都是可選部分,而捕捉列表和函數體可以為空。
因此C++11中最簡單的lambda函數為:[]{}; 該lambda函數不能做任何事情。
通過上述例子可以看出,lambda表達式實際上可以理解為無名函數,該函數無法直接調用,如果想要直接調用,可借助auto將其賦值給一個變量。
捕捉列表描述了上下文中那些數據可以被lambda使用,以及使用的方式傳值還是傳引用。
- [var]:表示值傳遞方式捕捉變量var
- [=]:表示值傳遞方式捕獲所有父作用域中的變量(包括this)
- [&var]:表示引用傳遞捕捉變量var
- [&]:表示引用傳遞捕捉所有父作用域中的變量(包括this)
注意:
a. 父作用域指包含lambda函數的語句塊
b. 語法上捕捉列表可由多個捕捉項組成,并以逗號分割。
比如:[=, &a, &b]:以引用傳遞的方式捕捉變量a和b,值傳遞方式捕捉其他所有變量 [&,a, this]:值傳遞方式捕捉變量a和this,引用方式捕捉其他變量
c. 捕捉列表不允許變量重復傳遞,否則就會導致編譯錯誤。 比如:[=, a]:=已經以值傳遞方式捕捉了所有變量,捕捉a重復
int x = 10; auto add_x = [x, =](int a) mutable { x *= 2; return a + x; }; cout << add_x(10) << endl;d. 在塊作用域以外的lambda函數捕捉列表必須為空。
e. 在塊作用域中的lambda函數僅能捕捉父作用域中局部變量,捕捉任何非此作用域或者非局部變量都會導致編譯報錯。
f. lambda表達式之間不能相互賦值,即使看起來類型相同
從使用方式上來看,函數對象與lambda表達式完全一樣。
函數對象將rate作為其成員變量,在定義對象時給出初始值即可,lambda表達式通過捕獲列表可以直接將該變量捕獲到。
實際在底層編譯器對于lambda表達式的處理方式,完全就是按照函數對象的方式處理的,即:如果定義了一個lambda表達式,編譯器會自動生成一個類,在該類中重載了operator()。
10、可變參數列表(先學會基本特性)
C++11的新特性可變參數模板能夠讓您創建可以接受可變參數的函數模板和類模板,相比C++98/03,類模版和函數模版中只能含固定數量的模版參數,可變模版參數無疑是一個巨大的改進。
//可變參數,你傳int,char,還是自定義都會自動給你推導 可以包含0-任意個參數 template<class ...Args> void ShowList(Args... args) {cout << sizeof...(args) << endl;//計算個數 } int main() {ShowList(1, 2, 3);ShowList(1, 'a');ShowList(1, 'A', string("sort"));return 0; }
如果我們要對其取值,如何取那?
接下來我們在看看可變參數在列表初始化的應用
template<class ...Args> void ShowList(Args... args) {int arr[ ] = { args... };//可變參數初始化列表cout << endl; }
我們這里列表初始化內部都是一樣的數據,如果我們要傳不一樣的數據,該如何實現?
C++11,利用逗號表達式調用例外一個函數,最后的0留給數據。
template <class T> void PrintArg(T t) {cout << t << " "; } template <class ...Args> void ShowList(Args... args) {// 列表初始化// {(printarg(args), 0)...}將會展開成((printarg(arg1),0), (printarg(arg2),0), (printarg(arg3),0), etc... )int arr[] = { (PrintArg(args), 0)... };cout << endl; } int main() {ShowList(1, 2, 3);ShowList(1, 'a');ShowList(1, 'A', string("sort"));return 0; }
也可以給模板函數設置一個返回值
可變參數包結合完美轉發的好處
直接就是普通構造函數的形式,不存在移動構造或者拷貝構造,節省空間
11、包裝器
函數包裝器器其實就是函數指針,用了包裝器之后,函數模板只會實例化一次,這里我們了解其用法即可。
可調用對象的類型:函數指針、仿函數(函數對象)、lambda
// 函數模板會被實例化多次 template<class F, class T> T useF(F f, T x) {static int count = 0;cout << "count:" << ++count << endl;cout << "count:" << &count << endl;return f(x); }double func(double i) {return i / 2; }struct Functor {double operator()(double d){return d / 3;} };int main() {// 函數名cout << useF(func, 11.11) << endl;// 函數對象cout << useF(Functor(), 11.11) << endl;// lamber表達式cout << useF([](double d)->double{ return d / 4; }, 11.11) << endl;return 0; }這里我們可以看到靜態變量count,每次的地址都不一樣,說明函數模板實例化了3次。
我們可以通過包裝器只讓函數模板實例化一次
int main() { // 函數名 生成一個函數包裝器,f1就是函數指針 == double (*f1)(double)std::function<double(double)> f1 = func;cout << useF(f1, 11.11) << endl;// 函數對象std::function<double(double)> f2 = Functor();cout << useF(f2, 11.11) << endl;// lamber表達式std::function<double(double)> f3 = [](double d)->double{ return d / 4; };cout << useF(f3, 11.11) << endl;return 0; }可以看到count的值是累加的,說明函數模板只實例化了一次
總結:
std::function包裝各種可調用的對象,統一可調用對象類型,并且指定了參數和返回值類型。
為什么有std:function,因為不包裝前可調用類型存在很多問題:
1、函數指針類型太復雜,不方便使用和理解
2、仿函數類型是一個類名,沒有指定調用參數和返回值。得去看operator()的實現才能看出來。3、lambda表達式在語法層,看不到類型。底層有類型,基本都是lambda_uuid,也很難看
總結
以上是生活随笔為你收集整理的C++11特性(详细版)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: outlook2016关闭时最小化到任务
- 下一篇: 【C++】C++11 新特性