久久国产成人av_抖音国产毛片_a片网站免费观看_A片无码播放手机在线观看,色五月在线观看,亚洲精品m在线观看,女人自慰的免费网址,悠悠在线观看精品视频,一级日本片免费的,亚洲精品久,国产精品成人久久久久久久

分享

C++11新標(biāo)準(zhǔn)

 dinghj 2015-04-15



          C++剛?cè)腴T,,以下為自己學(xué)習(xí)時整理的資料,,寫出來的是自己的理解,只是想加深自己的印象和以后方便自己查閱,,可能有不正當(dāng)?shù)牡胤健?br>
1,、新增算術(shù)類型
     longlong,最小不比long小,,一般為64位,。

2、列表初始化
      int units_sold = {0};或者 int units_sold{0};非11標(biāo)準(zhǔn)下的C++中,,只有特定的情況下才能使用該形式,。  比如數(shù)組的初始化,類構(gòu)造函數(shù)的初始化,,結(jié)構(gòu)體的填充,。相比傳統(tǒng)的賦值初始化,如果右側(cè)的數(shù)值類型相對于左側(cè)類型更大的話,,側(cè)對于這種窄化現(xiàn)象,,編譯器會 報錯。如:int k = {3.14};一個double是8個字節(jié),,int一般是4個字節(jié),,這時編譯器就會報錯,。

      在容器中,也支持這種方法,,比如std::map容器,,比如我們定義一個std::map<string,int> m_map,以前我們需要對它進(jìn)行插入,,一般做法是 m_map.insert(value_type("test",1));,這樣用一個函數(shù)來生成pare<string,int>再進(jìn)行插入,但現(xiàn)在有種更方便的方法,,讓我們能在初始化時進(jìn)行賦值,像下面這樣:

map<string,int> m_map = {{"test",1},{"test2",2}};

     每一組值都用{}括起來,,跟列表初始化使用上是一致的,。


3、列表初始化返回值
     如果我們有一個函數(shù),,它要求我們返回一個vector<string>類型的對象,,那么我們或者是這樣寫的:
     vector<string> test()
     {
           vector<string> temp("1","2","3");
           return temp;
     } 
     函數(shù)體內(nèi)定義一個臨時的vector變量,返回時直接返回臨時變量(PS:別說什么生命周期,,這不是引用,,這是值復(fù)制)。而C++11定義了列表初始化返回值:
     return {}
上面的函數(shù)可以寫成如下方式:
    vector<string> test()
     {
           return{"1","2","3"};
     } 
這樣直接返回一個初始化列表,,用于初始化vector變量,,這樣同樣會生成臨時變量,但卻可以減少代碼量,。

4,、商一律向0取整(即直接切除小數(shù)部分)
21 % -5;    /* 結(jié)果是 1 */
      運(yùn)算符%俗稱"取余"或"取模"運(yùn)算符,負(fù)責(zé)計算兩個整數(shù)相除所得的余數(shù),,參與取余運(yùn)算的運(yùn)算對象必須是整數(shù)類型:
  1. int ival = 42;  
  2. double dval = 3.14;  
  3. ival % 12;          // 正確:結(jié)果是6  
  4. ival % dval;            // 錯誤:運(yùn)算對象是浮點(diǎn)類型

       在除法運(yùn)算中,,如果兩個運(yùn)算對象的符號相同則商為正(如果不為0的話),否則商為負(fù),。C++語言的早期版本允許結(jié)果為負(fù)值的商向上或向下取整,,C++11新標(biāo)準(zhǔn)則規(guī)定商一律向0取整(即直接切除小數(shù)部分)。

        根據(jù)取余運(yùn)算的定義,,如果m和n是整數(shù)且n非0,,則表達(dá)式(m/n)*n+m%n的求值結(jié)果與m相等(這里的又乘又除的,無非是去掉小數(shù)部分),。隱含的意思是,,如果m%n不等于0,,則它的符號和m相同,。C++語言的早期版本允許m%n的符號匹配n的符號,而且商向負(fù)無窮一側(cè)取整,,這一方式在新標(biāo)準(zhǔn)中已經(jīng)被禁止使用了,。除了 m導(dǎo)致溢出的特殊情況,,其他時候(-m)/n和m/(-n)都等于-(m/n),m%(-n)等于m%n,,(-m)%n等于-(m%n),。具體示例如下:
  1. 21 % 6;         /* 結(jié)果是3 */      21 / 6;         /* 結(jié)果是3 */  
  2. 21 % 7;         /* 結(jié)果是0 */      21 / 7;         /* 結(jié)果是3 */  
  3. -21 % -8;   /* 結(jié)果是-5 */         -21 / -8;   /* 結(jié)果是2 */  
  4. 21 % -5;    /* 結(jié)果是1 */      21 / -5;    /* 結(jié)果是-4 */

5、nullptr常量
       其實(shí)這個常量跟NULL一樣,,都是0值,,只是NULL是預(yù)處理變量,在C++11中,,應(yīng)該盡量減少NULL的使用,。宏定義往往會導(dǎo)致非程序員本身需要的目 的(大部分是運(yùn)算符優(yōu)先級問題,比如#define duplicate(x) x*x,如果你是想算兩個x+1后的結(jié)果再相乘,,如果你寫成duplicate(x+1),編譯器展開后會是 x+1*x+1,這樣明顯與原來的意圖完全不同),,而且,排除稍困難,,一般來說,,都會盡量避免使用。

6,、constexpr
      constexpr可以說是對const變量的擴(kuò)展,,它只是約束const變量的右側(cè)的式子必須能在編譯時就能算出值。要理解這點(diǎn),,我們首先要搞清楚 const和constexpr之間的同異處.首先,,無論是constexpr或者const變量,值都是定義后不能改變的,,而且,,它們等號右側(cè)同樣都可 以為一個表達(dá)式;兩者的一個很重要的區(qū)別是const常量能在運(yùn)行時獲得值,,而constexpr卻是帶有約束性質(zhì)的,,它約束右側(cè)的式子只有在編譯時就能 算出值,如果不能的話,,編譯器側(cè)會報錯,。比如:const int max = 20; const int limit = max + 1;它們都是一個const變量同時,右側(cè)也是一個常量表達(dá)式,。但const int sz = str.size();它是一個在運(yùn)行時取得具體值的const變量,,但在編譯時卻無法取得具體值,所以右側(cè)的表達(dá)式明顯不是一個常量表達(dá)式,。往往程序員 希望某一變量必須是常量以防止在運(yùn)行過程中產(chǎn)生非意料之外的值(該過程大都由粗心導(dǎo)致的疏忽),,所以C++11中才會加入一個新的constexpr關(guān)鍵 字,這樣的話,,當(dāng)定義constexpr int sz = str.size();時,,就會由編譯器發(fā)出提示,,而避免上述情況的發(fā)生。


7,、constexpr函數(shù)

     constexpr函數(shù)是指能用于常量表達(dá)式的函數(shù),。比如上面的如果constepr int size = size() + 1;成立的話,size(int max)就是一個constexpr函數(shù),它定義或者如果下:

          int size() { return 30;}

     對于constexpr函數(shù),,有以下幾點(diǎn)需要注意:

     1)constexpr只能有一個return;

     2)因?yàn)樾枰诰幾g時展開constexpr函數(shù),,所以constexpr函數(shù)默認(rèn)為inline函數(shù)。

     3)主要語句不會在運(yùn)行時被執(zhí)行注意,,是運(yùn)行時不會被執(zhí)行,,別想著a = 1;這種也算,這是運(yùn)行時才被執(zhí)行的語句,,是無法通過的,,說到底,只有第4點(diǎn)的語句才會合法),,constexpr函數(shù)體內(nèi)可包含任意數(shù)量語句,。

     4)constexpr函數(shù)體內(nèi)可以包括空語句,類型別名和using聲明,,主要是編譯能執(zhí)行的語句都可以,。

     本質(zhì)上,constexpr函數(shù)跟constexpr表達(dá)式一樣,,只是用于編譯時進(jìn)行常量檢測,。

   

8、constexpr構(gòu)造函數(shù)

      在說這個前,,我們應(yīng)該要注意的重要的一點(diǎn)是:constexpr構(gòu)造函數(shù)無論名稱怎么變,,但它仍然需要遵守constexpr函數(shù)的規(guī)則。這些規(guī)則總結(jié)為以下幾點(diǎn):

1)constexpr構(gòu)造函數(shù)可以聲明為=default或者刪除函數(shù)的形式,。前者相當(dāng)于編譯器合成的默認(rèn)構(gòu)造函數(shù),,一般情況下幾乎不執(zhí)行任務(wù)初始化工作,所以不會與constexpr函數(shù)的約束沖突,;后者相當(dāng)于屏蔽類的某個功能,,現(xiàn)在先不用管,后面會提到,。

2)對于第1種情況外的constexpr構(gòu)造函數(shù),,constexpr的限制就比較多了:它既要滿足構(gòu)造函數(shù)的限制條件,也必須得滿足自身的限制條件,。前者讓它不能有返回語句(構(gòu)造函數(shù)是沒有返回值的),,后者限制了函數(shù)體內(nèi)的內(nèi)容必須是能在編譯時確定的,也即是說,,除了返回語句外其它語句都不成立(當(dāng)然還可以有類型別名之類的語句,,但這些語句真的想不出有什么必要聲明在一個構(gòu)造函數(shù)中),所以通常它的函數(shù)體都是空的,。

3)constexpr構(gòu)造函數(shù)必須初始化所有數(shù)據(jù)成員,,而且初始值只能是常量,比如一個constexpr函數(shù)的委托構(gòu)造函數(shù)的形式或者一條常量表達(dá)式,。

// 如C++ Primer中的例子

class Debug

{

     public:

          constexpr Debug(bool b = true):hw(b),io(b),other(b){}

          constexpr Debug(bool n,bool i,bool o):hw(h),io(i).other(0){}

private:

      bool hw,io,other;

}

可以看出,,初始化列表中的變量都是常量,true和false都是常量表達(dá)式,。如果是int類型,,如果賦值是一個字面常量也是可以的,初始化列表中的右值主要是常量表達(dá)式都是正確的,。


9,、類型別名
     類型別名是C和C++一樣存在的,以前的typedef的用法為typedef typeA typeB,,把typeA定義為typeB的一個別名,,這種用法,更像是宏定義的用法,,并沒有等號表達(dá)式直觀,;C++11中新增一種定義類型別名的方法, 該方法更符合我們平時的使用習(xí)慣:using typeA = typeB;這種方法在平時可能并無法表現(xiàn)多大的優(yōu)越性,,但如果用于函數(shù)指針時,,卻是很直觀的。比如有如下函數(shù) int add(int a,int b);如果定義指針,,以前的做法是typedef int (add*)(int a,int b);這種語法,,對于新手來說,是很難理解的,,如果再復(fù)雜點(diǎn),,返回值還是一個函數(shù)指針的話,那更難以理解了,。新手一種會按我們上面說的那種基本語法去理 解,,typedef typeA typeB,一個函數(shù)指針,很難找出那部分是對應(yīng)typeA,那部分是對應(yīng)typeB,,事實(shí)上,,typeA就是add,它可以是任何名稱,只是我這里剛好 與函數(shù)名是一樣而已,,如果寫作typedef int (addptr*)(int a,int b);你就會知道,,定義一個函數(shù)指針與函數(shù)名完全無關(guān),而只是與返回類型和參數(shù)類型有關(guān)(甚至可以沒有參數(shù)名稱:typedef int (add*)(int,int);),,但無論怎么說,,這種方法仍然是不太直觀的,,現(xiàn)在C++11中可以 這樣用了。
using addptr = int (int,int);它很明確指出,,addptr就是一個函數(shù)指針,,它指向的函數(shù)類型為:返回值是int,并帶有兩個int形參的所有函數(shù)。

10,、auto類型
     auto是C++11中新增加的特性,,對于我們平時編寫代碼,我建議是少用應(yīng)該盡量少用,,只有在寫代碼時知道我每步會產(chǎn)生的結(jié)果時才會寫出健壯的程序,,僅 auto來為我們判斷類型,事實(shí)與我們靜態(tài)語言的原意相悖了,;auto更多應(yīng)用于泛型編程中,,它能減少你編寫特化模板的工作量。對于auto,,我們只需要 記住以下幾點(diǎn):
1)auto是編譯時得到的類型,,也就是說,它是讓編譯器替我們分析表達(dá)式所屬的類型的,,所以有時候auto產(chǎn)生的值,,如果對它沒有足夠的理解,你會被弄糊涂,。
2)auto只能推斷一種類型,,比如當(dāng)一條語句中聲明多個變量時,如果變量類型不同,,是會產(chǎn)生錯誤的:auto sz = 0,pi = 3.14;一個為int型,,一個為double型,這時auto是不能正常工作的,。
3) 當(dāng)右值是一個引用類型時,,auto的類型不是一個引用類型,而是引用類型的值,。比如double &PI = pi;auto ref = PI;ref的類型不是double&而是跟pi的類型一樣,,為一個double類型。當(dāng)我們確實(shí)需要一個引用類型時,,我們可以寫成這樣auto &ref = PI;
4)說這點(diǎn)前,,先說明下C++ Primer中提到的頂層const和底層const的概念。當(dāng)一個const變量自身無法改變時,,我們稱為頂層const,,當(dāng)一個const變量本身可 改變,但關(guān)聯(lián)的變量無法改變時我們稱為底層const。也即是頂層也就是表面,,底層也就是表面掩住的內(nèi)容,。如果:const double pi = 3.14;double *const ptr = &pi;(注意,不要寫成double const *ptr,這個是跟const double* ptr一樣的), 這 2個都是頂層const,,他們本身的值無法改變;const double *ptr2 = &pi;;const double PI = &pi;這2個都是底層const,,事實(shí)上,指向常量的指針和聲明引用的const都是底層const;  auto會忽略掉頂層const的性質(zhì),,但保留底層const;比如auto x = pi;這時x的類型為double而非const double,,同樣地,,當(dāng)auto x = ptr時,,x的類型為double*;而auto x = ptr2,此時的x為const double*類型,,它說明指向的值不可改變,。如果我們想保留頂層const,只能手動寫上符號,如auto const x = &pi;
總的來說,,auto能判斷內(nèi)置類型,,能判斷指針類型,但卻無法正確判斷出引用類型和頂層const類型,,對于頂層const類型和引用類型(所有的const引用類型都是頂層const類型)只能通過手動增加的方法保留特性,。

5)由編譯器確定動態(tài)分配數(shù)據(jù)的類型,C++11的auto在new中也有新的定義,,之前我們必須確實(shí)地指定new類型和指針類型才能正確地在堆上分配內(nèi)存空間,,現(xiàn)在,C++11允許由編譯器推斷分配類型,,用法如下:

auto p1 = new auto(std::string); // p1為指向string類型的指針


11,、decltype
      我覺得C++11新增的這個特性,比auto更奇葩,,要理解這個東西,,說真的,難度不是一般的大,,說不得,,真想深入研究,你應(yīng)該重新翻翻編譯原理,。以下幾點(diǎn),,是我們必須注意的:
1)decltype是推斷類型的,它允許變量和表達(dá)式,,但無論是那一種,,它都忽略值而只關(guān)注類型的,,比如表達(dá)式,,并不會調(diào)用表達(dá)式而只是推斷當(dāng)表達(dá)式被調(diào)用時,,會返回一個什么樣的類型,。
2)decltype 在處理頂層const跟auto不同,它返回的類型是包括頂層const的,也就是說當(dāng)double *const ptr = &pi;時auto x = ptr;類型是double* ,但如果用decltype(ptr)得到類型卻是完整的double *const類型,。對于引用,,也是一樣的,。但引用必須注意,,如果const int &x = pi;decltype(x)后得是必須要初始化的,,因?yàn)橐妙愋捅仨毝x的同時初始化。
3)注意這種情況:decltype(x + 0)——這里我們假定x是int&類型,引用類型進(jìn)行decltype是作為一條賦值語句的左值的,。最后的結(jié)果應(yīng)該是int類型,。
4) 這點(diǎn)是最重要的:加不加括號,,我們得到的類型可能是會不同的,。比如int i = 0;當(dāng)我們調(diào)用decltype(i)時,,這很容易理解,得到的類型肯定是int類型,;但如果我們加上一重或多重括號如:decltype((i)),,我 們得到的是int&類型。事實(shí)上,,變量也是可以作為一種賦值語句左值的特殊表達(dá)式,,從C++的初始化就可以看出,比如int i(0);,可以看出,,這是一個函數(shù)調(diào)用的形式,,事實(shí)上,這個比較難理解,,我也不明白,,而這也僅僅是一個數(shù)學(xué)到的概念而已,a+b = 0是代數(shù)式,,那a和b都是代數(shù)式,。反正decltype是C++11新增的機(jī)制。

5) decltype可用于返回類型:比如下面這段代碼:(選自C++ primer)

   int odd[] = {1,3,5,7,9};

   int even[] = {0,2,,4,,6,8};

 // 返回一個指針,,該指針指向含有5個整數(shù)的數(shù)組

 decltype(odd) * arrPtr(int i)

{

    return (i%2) ? &odd:&even; // 返回一個指向數(shù)組的指針

}

 這里是特別需要注意的,,函數(shù)的返回的是數(shù)組的地址,但decltype(odd)得到的類型卻是一個含有5個元素的數(shù)組,,它不會幫我們把數(shù)組轉(zhuǎn)換成指針類型,,所以還要在addPtr前加上*表示返回類型的是一個指向含有5個元素的數(shù)組的指針,。

      主要是我們記住,,如果加了括號,declstype的類型永遠(yuǎn)是引用,。最后總結(jié)是,,這樣的:
如果e是一個沒有外層括弧的標(biāo)識符表達(dá)式或者類成員訪問表達(dá)式,那么decltype(e)就是e所命名的實(shí)體的類型,。
如果e是一個函數(shù)調(diào)用或者一個重載操作符調(diào)用(忽略e的外層括?。敲磀ecltype(e)就是該函數(shù)的返回類型,。
否則,,假設(shè)e的類型是T:若e是一個左值,則decltype(e)就是T&,;若e是一個右值,,則decltype(e)就是T。

    
12,、范圍for
     看C#,,JAVA,,都有for...each語法,javaScript有for...in語法,,OC也有自己的迭代遍歷語法,,總的來說,對于特定的數(shù)據(jù) 的遍歷,,這些方法確實(shí)足夠簡單,,但說真的,這些方法方便的地方在于能動態(tài)判斷類型,,但我個人認(rèn)為,,這種范圍for對于C++來說,作用真不算強(qiáng)大,,因?yàn)? C++不像C#這些一樣,,一個數(shù)組內(nèi)能混合不同的類型,C#的同一個數(shù)組內(nèi)能加入基本類型,,也能加入類類型,,但C++只能加入特定一種類型,所以,,每個數(shù) 組都是特定類型,,用auto其實(shí)作用不大,而傳統(tǒng)的for語句,,跟使用范圍for相比,,并沒有多大的不方便吧。
C++11新加入for(auto value:data);

for (auto c : str)    //對于str中的每個字符
cout<<c<<endl;    // 輸出當(dāng)前字符,,后面緊跟一個換行符



13,、cbegin()和cend()【容器】
     傳統(tǒng)的C++中,標(biāo)準(zhǔn)庫中的所有容器都有begin()和end()函數(shù),,分別取得容器的首元素和最后元素的下一次位置,。但以上兩個函數(shù),返回的都是 iterator類型,。而新增加的cbegin()和cend()函數(shù),,返回的是const_iterator類型。

14,、beign()和end()【標(biāo)準(zhǔn)庫】
     比如有如下數(shù)組:
     int arr[] = {1,2,3,4,5,.........};
     傳統(tǒng)C++要遍歷會作出如下操作:
     for(int i = 0; i != sizeof(arr) / sizeof(arr[0]); ++i){};
     或者
     for(int *p = arr[0]; p != arr[0] + sizeof(arr) / sizeof(arr[0]; ++i){};
     前者在某種程度上是安全的,,但對于后者,要取得有效數(shù)據(jù)的下一個位置時,,卻是極易出錯的,。所以,為了讓指針操作更安全,,C++11提供了begin()和end()函數(shù),,分別指向數(shù)據(jù)的起始和結(jié)束位置的下一位置【他們都是指針類型】,。我們現(xiàn)在可以更安全地遍歷數(shù)組等數(shù)據(jù)了:
     for(int *p = begin(arr); p != end(arr); ++p){};
甚 至,這兩個函數(shù)也提供了跟迭代器一樣的操作,,退兩個迭指針相減的結(jié)果就是它們之間的與數(shù)據(jù)類型相關(guān)的距離,,比如,arr如果有5個元素,,則auto diff = end(arr) - begin(arr),diff的值為5,,而且,diff的類型是標(biāo)準(zhǔn)庫類型ptrdiff_t,,這是一種帶符號的類型,,因?yàn)椴钪悼赡軙樨?fù)數(shù)。

15,、initializer_list類
     傳統(tǒng)的可變參數(shù)一般是用va_list的一組宏來解決的?,F(xiàn)在,C++11新增了模板類initializer_list,,在便利性上得到提升,。它跟 va_list最大區(qū)別是,va_list中的va_arg的參數(shù)可以為任意類型,,但 initilizer_list只允許未知的形參使用相同的類型,。
     對于initializer_list,一個最重要的概念是,,initializer_list內(nèi)所有的元素都是常量,,這與vector這些容器是不一樣的。
     initializer_list因?yàn)橛幸环N構(gòu)造方法為initializer_list<T> lst{a,b,c.....};使得它可以實(shí)現(xiàn)可變參數(shù)(我知道,,vector,,list一樣可以這樣做,但問題是,,vector和list總是一開始 就分配內(nèi)存空間的,,相比較下,initializer_list是輕量級的,。PS:這貨不支持下標(biāo)操作的,只能用指針,,或者在標(biāo)準(zhǔn)庫中叫迭代器更好點(diǎn)),,大概方法如下:
void CoutList(initializer_list<string> il)
{
   // 一般為了方便,我們不清楚il.begin()的類弄,,可以 用auto代替,,但事實(shí)上, begin返回的是一個以模板實(shí)例相同  的const類型,。
    for(auto beg = il.begin(); beg != il.end(); ++beg)

     cout << *beg << endl;

調(diào) 用時可以這樣,,CoutList({"1","2","3"});,,相較傳統(tǒng)的va_list宏而言,這種方法雖然簡結(jié)了,,但對于傳統(tǒng)的C++程序員來 說,,多了個花括號,可能已經(jīng)不算正規(guī)的函數(shù)調(diào)用了,。不過,,它為我們提供了一種方法,如果上面的函數(shù),,用va_list是無法達(dá)到的,,原因就 是,va_list必須至少帶一個參數(shù),,以作為va_start的起始點(diǎn),,上面的函數(shù),我們可以增加一個表示傳入?yún)?shù)數(shù)量的形參,,可以可以這樣寫:
void CountList(int nNumber,...)
{
     va_list lt;  //定義一個 va_list 參數(shù)表
     va_start(lt,nNumber);  // 初始化va_list,,并讓其指針指向參數(shù)表中的第一個參數(shù)

     // 因?yàn)槭菞#詶5拙褪堑谝粋€參數(shù),,+1是從棧頂向棧低取值,,用第一個參數(shù)判斷循環(huán)結(jié)束是可以的
     for(int i = 0; i != nNumber; ++i)
     {
         // 取得參數(shù)表中的第i個參數(shù),總設(shè)置string類型,,此時lt會自動指向當(dāng)前位置的下一位置(ap是先移動一個位置再返回值的,。
          string tmp = va_arg(lt,string); 
          cout << tmp << endl;
     }

     va_end(lt); // 用完清空是好習(xí)慣
}
調(diào)用時可以用如下方法:CoutList(3,"1","2","3");

#define _INTSIZEOF(n)   ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )  // 這個是字節(jié)對齊,自己參考文檔,。(http://yangtaixiao.blog.163.com/blog/static/42235441201441233325695/

#define _crt_va_start(ap,v)  ( ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v) )
#define _crt_va_arg(ap,t)    ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
#define _crt_va_end(ap)      ( ap = (va_list)0 )

但總體來說,,因?yàn)閕nitializer_list是輕量的,所以使用它應(yīng)該更方便一點(diǎn),。


16,、尾置返回類型
      想像一下,當(dāng)你要定義一個函數(shù),,當(dāng)你需要一個函數(shù),,它接受一個int類型參數(shù),并返回一個指向含有10個int的數(shù)組,,你應(yīng)該怎么寫,?我就犯過傻,聰明地 直接返回數(shù)組,,接收時卻也是用數(shù)組,,但這是錯誤的,數(shù)組是不能拷貝的,,明顯是不能直接返回數(shù)組,,只能返回數(shù)組的指針或者引用,,而且,一般來說,,返回指針,, 在語法上會更煩瑣點(diǎn)。要寫一個返回指向10個int數(shù)組的指針,,一般會用如下方法:
      1)定義別名
     typedef int arrT[10]; 或者用新標(biāo)準(zhǔn) using arrT = int[10];
     //定義函數(shù)
    arrT* func(int);
    2) 不定義別名
    int (*func(int i))[10];
   這個函數(shù)的語法本身就讓人難以理解,,所以為了解決這種復(fù)雜的返回類型(我覺得C和C++之所以難學(xué),這語法的復(fù)雜度也是其實(shí)一個因素了),,C++新增了尾置返回類型,,就是把返回類型提后,我們只需要把返回類型清晰地標(biāo)明就可以了,,比如func就能寫成這樣:
   auto func(int i)->int(*)[10];
另外,,尾置法有一個特別的地方在于:普通的函數(shù),函數(shù)的返回值是會比函數(shù)的參數(shù)先確定類型的,,但尾置法卻是在確認(rèn)函數(shù)參數(shù)后再確認(rèn)返回類型的,。

17、default生成默認(rèn)構(gòu)造函數(shù)
      我們都清楚,,如果我們自己本身沒有對類定義構(gòu)造函數(shù),,編譯器會為我們合成一個構(gòu)造函數(shù)。但事實(shí)上,,這個簡單的構(gòu)造函數(shù),,在定義時可能根本不會調(diào)用,更是別 指望它會幫你初始化成員變量(自己試下就知道了,,除了全局的內(nèi)置變量,,C++任務(wù)位置的變量,都不會自動幫你初始化),。而當(dāng)我們自己定義一個構(gòu)造函數(shù)后,, 編譯器就不會再為我們合成默認(rèn)構(gòu)造函數(shù)了,如果我們在需要自定義的構(gòu)造函數(shù)時,,卻仍然希望編譯器為我們合成默認(rèn)構(gòu)造函數(shù),,我們就需要用到新標(biāo)準(zhǔn)中的 default關(guān)鍵字了。用法如下:
class A
{
  public:
      void A(int a):iNumber(a){}
      A() = default; // 當(dāng)在類內(nèi)部定義時,跟合成的默認(rèn)構(gòu)造函數(shù)一樣,,它是inline的
      A(){}  // 當(dāng)我們存在合成默認(rèn)構(gòu)造函數(shù)時,我們手動定義就會提示重定義了,,這個應(yīng)該都清楚
}
類外定義
class A
{
  public:
      A() ;
}
A::A() = default; // 類外定義時,該合成默念構(gòu)造函數(shù)就不再是inline的了,。

18,、類內(nèi)初始值
       我會告訴你我很討厭這個嗎,?我是感覺這個完全破壞了構(gòu)造函數(shù)存在的意義。
       C++11中,,允許通過以符號=或者花括號為類內(nèi)一個變量提供類內(nèi)初始值,。如:

       class B
       {
           private:
                int Size = 0;
                std::vector<int> vt{1,2,3};
       };


      當(dāng)然,,我們也可以在構(gòu)造函數(shù)中進(jìn)行初始化,只是這樣或者更直觀一點(diǎn),。

19,、委托構(gòu)造函數(shù)
      看到這個名字時,我希望大家不要跟我一樣直接想到IOS的委托和C#的委托,,其實(shí)根本不是這么一回事,。C++11新擴(kuò)展的這個函數(shù)功能是使委托構(gòu)造函數(shù)可以執(zhí)行它所屬類的其它構(gòu)造函數(shù),。
      具體方法如下:

      class A
      {
       public:
             A(int size):iSize(size){}
             A():A(30)
            {
                 area = 90;
            }
      private:
             int iSize;
             double area;
      };


     委托構(gòu)造函數(shù)在語法上類似派生類調(diào)用基類構(gòu)造函數(shù),,而且,同樣地,,是先執(zhí)行被委托的構(gòu)造函數(shù),,再執(zhí)行自身括號內(nèi)的內(nèi)容。

20,、文件流對象的文件名支持標(biāo)準(zhǔn)庫string
       舊版本的標(biāo)準(zhǔn)庫中的fstream只支持C風(fēng)格字符數(shù)組,,如果我們用標(biāo)準(zhǔn)庫中的string還得string.c_str()轉(zhuǎn)換一下,但現(xiàn)在C++11的fstream已經(jīng)允許直接使用string字符串了,。

21,、標(biāo)準(zhǔn)庫新增forward_list和array類型
       forward_list是單向鏈表,在鏈表的任何位置插入/刪除數(shù)據(jù)速度都很快,。
       array行為跟內(nèi)置數(shù)組一樣,,大小也是固定的,也不支持添加/刪除或者改變?nèi)萘看笮〉牟僮鳌?br>
22,、容器列表初始化【容器】
       C++11支持以花括號形式的列表初始化方法,,同時,標(biāo)準(zhǔn)庫幾乎所有的容器都能享受此種新特性,。
      如:vector<int> vt("1","2","3");舊標(biāo)準(zhǔn)只能用這種方式函數(shù)構(gòu)造函數(shù)進(jìn)行初始化,。而C++11中能使用如下方式:
            vector<int> vt = {
"1","2","3"};
     這種方式的初始化,其實(shí)也不過是調(diào)用vector的賦值構(gòu)造構(gòu)造函數(shù)。

23,、提供了swap函數(shù)的非成員版本
【標(biāo)準(zhǔn)庫】
     舊的C++的標(biāo)準(zhǔn)庫版本只提供成員函數(shù)版本的swap,,而現(xiàn)在也提供了非成員版本的swap函數(shù)。它在泛型中使用會更方便,。(不太了解泛型,,也不明白這個……)

24、改進(jìn)的inser操作
【容器】
      新標(biāo)準(zhǔn)中,,insert數(shù)據(jù)后,,它會返回一個指向第一個新增加元素的迭代器,不同于以往,,它是返回一個void值,。

25、emplace_front,、emplace和emplace_back
【容器】     
        C++11新增加的這個新成員,,它允許我們通過提供數(shù)據(jù)的值來直接初始化容器內(nèi)的數(shù)據(jù)。比如我們有如下一個類:

class em
{
   public:
           em(string name,int age,bool sex)
           {
                  m_name = name;
                  m_age = age;
                  m_sex = sex;
           }
  private:
          string m_name;
          int m_age;
          bool m_sex;
};


如果我們用一個vector裝載它,,如果我們要在后面插入一個新成員,,我們一般做法是這樣:
   vector<em> vt;
   vt.pushback(em("stupid",30,0));
這樣做首先是構(gòu)造一個臨時的局部em對象,然后再通過拷貝的方式把這個臨時對象拷貝到容器內(nèi),。但如果我們使用新的emplace函數(shù),,我們可以這樣做:
  vt.emplace
("stupid",30,0);
這樣做的結(jié)果是直接在容器內(nèi)的內(nèi)存空間中創(chuàng)建對象,而不必要浪費(fèi)再構(gòu)建臨時變量和進(jìn)行拷貝了,。
很明顯,,emplace比傳統(tǒng)的插入函數(shù)具有更高的效率。

26,、shrink_to_fit
【容器】
         C++11標(biāo)準(zhǔn)為有預(yù)分配內(nèi)存功能的容器(vector,deque,string)提供一個名為shrink_to_fit的函數(shù),,他能將容器的 capacity()減少為與size()相同大小,即讓容器把多出來的內(nèi)存空間釋放,,但該函數(shù)并不一定保證能100%釋放這些多余的空間,。

27、string數(shù)值轉(zhuǎn)換【標(biāo)準(zhǔn)庫】
         舊版C++中,,要把一個數(shù)值字符轉(zhuǎn)化成字符串,,我們只能使用itoa()的C函數(shù)或者sprintf()等諸如此類要手動事先分配內(nèi)存的方法。但 C++11標(biāo)準(zhǔn)為string提供了一組函數(shù)(也是定義在string頭文件內(nèi)),,使得能把幾乎所有的數(shù)值類型轉(zhuǎn)化為string,而不需要我們事先手動 分配內(nèi)存空間,。同樣地,也定義了一組從string轉(zhuǎn)換為數(shù)個類型的函數(shù),。如:
         int i = 30;
         string s = to_string(i); //數(shù)值轉(zhuǎn)為string,,to_string()是一個重載函數(shù)
         double pi = stod(s); // 把字符串"30"轉(zhuǎn)化為一個double值
如果string不能轉(zhuǎn)換為一個數(shù)值,,拋出invalid_argument異常;如果得到的數(shù)值無法用任何類型來表示,,則拋出一個out_of_range類型,。

 函數(shù) 說明
to_string
這個函數(shù)是重載函數(shù),,能滿足所有內(nèi)置類型,,而且,有相對應(yīng)的寬字節(jié)to_wstring
stoi(s,p,b)
p 表示一個size_t*的指針類型,,默認(rèn)為空值,。當(dāng)它不為空,轉(zhuǎn)換成功時就表示第一個為非數(shù)值字符的下標(biāo),,一般情況下,,因?yàn)樗侵苯觕har型指針,通過 把最后非數(shù)值字符的地址值和起始地址值相減的,所以也即表示了成功轉(zhuǎn)換的字符的數(shù)量,,如"10"轉(zhuǎn)成功為數(shù)值10時,,這個值為2。b表示轉(zhuǎn)換基準(zhǔn),,這個跟 atoi一樣,,默認(rèn)是10,表示10進(jìn)制,。s則重載了string和wstring版本,。
stol(s,p,b)
同上
 stoul(s,p,b)同上 
stoll(s,p,b)
同上 
stoull(s,p,b)
同上 
stof(s,p)
p的含義同上
stod(s,p)
 p的含義同上
stold(s,p)
 p的含義同上


28、lambda
     C++中的閉包命名為lambda,,或者匿名函數(shù),,他遵守閉包的特性,子函數(shù)可以使用父函數(shù)中的局部變量,。
     我們要記住一點(diǎn),,lambda是一個類,而且,,沒有帶合成默認(rèn)構(gòu)造函數(shù)的類,,在我們定義時,編譯器會為我們自動生成這樣一個類,。
     它格式如下:[capture list](parameter list)->return type {function body}
                           [捕獲列表](形參列表)->返回類型 {函數(shù)體}
    首先,,這種格式不是固定的,其中某些項(xiàng)能省略,,或者隱式調(diào)用,,但無論如何,捕獲列表和函數(shù)體是必須要永遠(yuǎn)包含的,,那怕捕獲列表和函數(shù)體為空,。lambda在函數(shù)體內(nèi)或函數(shù)體外都能定義,。例如:
     auto fun = []{return 0;}; // 不要忘記最后的分號
    為了說明lambda的本質(zhì),有必要把某些內(nèi)容提前進(jìn)行說明,。
      lambda的本質(zhì),,我們可以這樣理解:編譯器以lambda表達(dá)式為基礎(chǔ)生成一個未命名的類。該類中的成員數(shù)據(jù)就是捕獲列表中羅列的所有數(shù)據(jù),。但lambda表達(dá)式產(chǎn)生的類,,卻與傳統(tǒng)的類有點(diǎn)細(xì)致區(qū)別,主要表現(xiàn)為:
   1)lambda產(chǎn)生的類不含默認(rèn)構(gòu)造函數(shù),、賦值運(yùn)算符及默認(rèn)析構(gòu)函數(shù),。
   2)lambda產(chǎn)生的類含有默認(rèn)拷貝構(gòu)造函數(shù)、默認(rèn)移動構(gòu)造函數(shù),。當(dāng)然,,這得視捕獲列表中的變量的數(shù)據(jù)類型決定,一般地,,一個類如果沒有定義自身的拷貝 構(gòu)造函數(shù),,而且,本身的非static成員變量都能移動的話,,就會合成默認(rèn)移動構(gòu)造函數(shù),,反之,則合成默認(rèn)拷貝構(gòu)造函數(shù),。
     比如有這樣一個lambda表達(dá)式:[sz](const string&s) {return s.size() >= sz;};如果sz為size_t類型,,則編譯器為我們生成類似這樣的一個未知名的類(別以為就是這樣,這只是類似而已,,并非就是一模一樣,,這點(diǎn)要清楚 的):
     class unnamed
     {
      public:
           // 它不會合成默認(rèn)構(gòu)造函數(shù),但卻會根據(jù)捕獲列表生成相應(yīng)的構(gòu)造函數(shù),,用以初始化成員變量
            unnamed(size_t  data):sz(data){}
           // 它會根據(jù)lambda表達(dá)式的函數(shù)體的return語句推斷返回類型,,并重載調(diào)用運(yùn)算符,值捕獲時一般都是const成員函數(shù),,這是lambda所要求的——值捕獲到的值一般不會改變基值,。  
            bool operator()  (const string&s) const // 因?yàn)樾螀⑹莄onst類型,所以成員函數(shù)也聲明為const成員函數(shù)
            {
                   return s.size() >= sz;  
            }

            private:
                   size_t sz;
     }
   現(xiàn)在我們應(yīng)該已經(jīng)理解了,,lambda就是一個未命名的類,,它重載了調(diào)用運(yùn)算符。  
    下面分步來解釋lambda的幾個要點(diǎn):
  [形參列表]:
    1)如果沒有提供形參表,,就表示指定一個空參數(shù)列表,;lambda的形參不能帶有默認(rèn)值。
  [捕獲列表]:
   1)如果捕獲列表為空,,則表示不捕獲任何局部變量,。如果沒有提供返回類型,,但lambda能根據(jù)函數(shù)體的代碼來推斷返回類型:1.如果有return,則根據(jù)return后的表達(dá)式來推斷,;2.如果沒有return就是void,。
   2)如果捕獲列表為空,則我們不能在函數(shù)體內(nèi)定義對局部變量進(jìn)行任何操作,。下面這樣會報錯:

     int GetBiger(double first, double second)
     {     
             double result = (first > second) ? first : second;
             auto func = []{return result; };
             return func();
     }


     當(dāng)然,,上面的函數(shù)完全是沒有意義的,但這個是演示,,我們在lambda里使用了GetBiger的名叫result的臨時變量,,但卻沒有在捕獲列表里列出,所以這是非法的,,編譯器會報錯;如果我們確實(shí)想使用,,就應(yīng)該把局部變量放于捕獲列表中:

    int GetBiger(double first, double second)
     {     
             double result = (first > second) ? first : second;
             auto func = [result]{return result; };
             return func();
     }


這樣就沒有問題了,。
[值捕獲]:
     lambda如果采用值捕獲(即不帶引用符號)時,與傳值參數(shù)行為一致,,即由lambda表達(dá)式生成的新類型(下面會說,,lambda本質(zhì)是一個未命名的 類類型)中的成員數(shù)據(jù)在構(gòu)造時會拷貝父函數(shù)中局部臨時變量的一個副本,在這種情況下,,更改lambda內(nèi)的值不會影響父函數(shù)變量的值,,同樣地,更改父函數(shù) 局部變量的值也不會影響lambda內(nèi)的捕獲值,。如:

   void func()
   {
        size_t v1 = 42; // 函數(shù)內(nèi)局部臨時變量
        // 捕獲列表為值捕獲,在創(chuàng)建lambda時(生成類時調(diào)用構(gòu)造函數(shù))就已經(jīng)拷貝
        auto f2 = [v1]{return v1;};
        v1 = 0;
       auto j = f2(); 
   }


   因?yàn)槭侵悼截?,所以修改父函?shù)的局部變量,不會影響lambda生成的類的成員變量,,所以j的值仍然為42,。
[ 引用捕獲]:
如果上面的式子改寫成如下形式

void func()
   {
        size_t v1 = 42; // 函數(shù)內(nèi)局部臨時變量
        auto f2 = [&v1]{return v1;}; // 在創(chuàng)建lambda時時引用類型,格式為引用符號寫在捕獲變量前面
        v1 = 0;
       auto j = f2(); 
   }


   因?yàn)槭且贸跏蓟?,所以改變局部變量的值,,lambda表達(dá)式生成的未命名的類內(nèi)數(shù)據(jù)成員也跟著改變值。結(jié)果是j的值為0,。


[隱式捕獲]:
      有時我們在式子中要用到某些變量,,但為了節(jié)約時間,我們不愿意把變量一個個寫上去,,或者說,,我們要用到的變量很多,不想一個個寫上去,。那么我們這時就可以使用隱匿捕獲,,讓編譯器根據(jù)我們的函數(shù)體來推斷我們將要用到那些變量,。隱式捕獲有三種方式:
1) 隱式值捕獲

   void func()
   {
        size_t v1 = 42;
        auto f2 = [=]{return v1;};  // 值捕獲
        v1 = 0;
       auto j = f2(); 
   } 


   上面的式子,捕獲列表中,,我們用=代替了我們要捕獲的變量,。
   =表示采用值捕獲方式。
2)隱式引用捕獲

   void func()
   {
        size_t v1 = 42;
        auto f2 = [&]{return v1;}; // 引用捕獲
        v1 = 0;
       auto j = f2(); 
   } 


   上面的式子,,捕獲列表中,,我們用&代替了我們要捕獲的變量。
   %表示采用值捕獲方式,。
3)混合使用隱式捕獲和顯式捕獲

    void func()
   {
        size_t v1 = 42;
        size_t v2 = 30;
        auto f2 = [&,v2]{return v1 + v2;}; // 混合使用隱式和顯示捕獲
        v1 = 0;
       auto j = f2(); 
   }  


    這里v2顯式地寫在捕獲列表中,,則其它的變量都是隱式引用捕獲。在使用混合捕獲時必須知道幾點(diǎn):
3.1) 捕獲列表中第一個元素必須是一個&或=,。
3.2) 顯式捕獲的變量的捕獲方式必須與隱式捕獲的方式不同,。如果上面如果我們寫成auto f2 = [&,&v2]{return v1 + v2;};這就是一個錯誤的lambda表達(dá)式,因?yàn)椴东@列表中,,隱式的變量和顯示的變量都是引用方式,。

[可變lambda]:
     1)當(dāng)lambda表達(dá)式的一個形參是值拷貝的話,lambda是不會改變其內(nèi)的初始值的(還記得上面提到的那個未命名的類類型嗎,?里面的調(diào)用重載函數(shù)是 const類型的,,它是不會改變類內(nèi)的變量的值的,而lambda只有一個可調(diào)用重載函數(shù)是允許用戶調(diào)用,,所以這個const的成員函數(shù)沒有權(quán)限改變 lambda內(nèi)的值,。)如果下面的表達(dá)式:

   void func()
   {
        size_t v1 = 42;
        auto f2 = [v1]{return ++v1;}; // 值捕獲
        v1 = 0;
       auto j = f2(); 
   } 


  這種情況下,如果我們?nèi)绻厦嬉粯?,改變v1變量的值,,那么編譯器會提示你必須是可修改的左值;要想確實(shí)修改這個值,,我們可以用一個關(guān)鍵字解決:mutable,。
(順便說下C++中最容易讓人忘記的幾個關(guān)鍵字吧,
第一個explicit:
        這貨我們平時很少用,,但如果你看過MFC的代碼,,你會發(fā)現(xiàn),其實(shí)這貨還是很上鏡的,。這個關(guān)鍵字是必須在類內(nèi)構(gòu)造函數(shù)中使用,,而且,它還有幾個限制條 件:1,、構(gòu)造函數(shù)必須只允許接受一個形參,,當(dāng)然,如果你有3個,,而其它的都帶有默認(rèn)值,,也是可以的,。2、explicit只能在類內(nèi)定義,。如果你想在類外 explicit A::A(int a)這樣定義是不允許的,。explicit是阻止了數(shù)據(jù)的隱式轉(zhuǎn)換,比如,,A(double a){},當(dāng)我們int a = 10;A(a);時,,會把int型的a類型上升為double,但如果explicit A(double a){}的話,,A(a)則是不允許的,。當(dāng)然,如果你要A((double)a)的話,,這又是可以的,。

第二個mutable:
        當(dāng)某數(shù)據(jù)定義成mutable后,無論在任何時候他都是可變的,。包括const類——事實(shí)上,,const類也只能調(diào)用const成員變量和成員函數(shù),好 吧,這個說了其實(shí)沒有意義,,一句話,其實(shí)才是我想說的:就算是const成員函數(shù)中,,它都是可變的,。另外,mutable只能作用于非const的變量,, 如果mutable const int a; 這樣是不允許的。正因?yàn)檫@個關(guān)鍵字破壞了類的封裝性,所以我們是比較少用的,。

第三個volatile:
        這個是C引過來的,。或者做移植時經(jīng)常用到,,但說真的,,平時根本用不上,它主要是告訴編譯器,,不要對被它定義的變量進(jìn)行優(yōu)化,,因?yàn)闆]有用過,所以沒有什么好說的,。
)
說完就回正文吧,。
在我們把上面的lambda的參數(shù)列表首加上mutable,改為:

   void func()
   {
        size_t v1 = 42;
        auto f2 = [v1] () mutable {return ++v1;}; // 值捕獲
        v1 = 0;
       auto j = f2(); 
   } 


這樣是完全沒有問題的。上面的參數(shù)列表就算是空,,你也必須帶上(),,不然你編譯不過,。
    2)當(dāng)我們?yōu)橐貌东@時,變量應(yīng)該說是任何時候都是可變的,,但有個例外,,當(dāng)你的捕獲類型本身是const類型時,你的變量是無法改變的,。例如:

   void func()
   {
        const size_t v1 = 42;
        auto f2 = [&v1] {return ++v1;}; //引用捕獲
        v1 = 0;
       auto j = f2(); 
   }  


      這樣是錯誤的,。或者聰明的你會想著,,我可以加上mutable呀,,這樣不就行了嗎?好吧,,如果你是新手,,會這樣想,說明你還是學(xué)得很快的,。但事實(shí)是這樣 的,,值捕獲時,我們是直接拷貝父函數(shù)臨時變量的值的,,我們lambda生成的類內(nèi)的變量不是const的(雖然我們的調(diào)用重載函數(shù)是const的),,所以 我們加上mutable實(shí)際上只是對我們lambda的類內(nèi)變量有影響,跟父函數(shù)的臨時變量沒有半毛錢關(guān)系,。引用卻是不同的,,當(dāng)我們定義一個引用時,比如 下面代碼:
int a = 10; int b = 20; int &ref = a; ref = b; // 這樣我們是可行的,,大家都能理解,。
const int a = 10; const int b = 20; int &ref = a; ref = b; // const變量只能用const引用,紅色部分是不允許的,,所以如果父函數(shù)的臨時變量是一個const變量,,那么lambda的捕獲列表中的參數(shù)是不能更改的。
而一個const的變量,,是無法加上mutable關(guān)鍵字的,。

[指定lambda返回類型]:
     
前面說到了,如果lambda內(nèi)有一條return語句,,則lambda會根據(jù)表達(dá)式推斷返回類型,;如果沒有返回語句,就會返回void類型,,比如下面的例子:
[](int i){return (i > 0) ? i : -i;}
它能很好工作,,但如果變成這樣呢?
[](int i){ (i > 0) ? i : -i;}
這樣的話,沒有return,它只返回void,,完全與我們意圖不符合,。
如果改成這樣:

[](int i){ if(i > 0) return i; else return -i;}或者[](int i){ if(i > 0) return i; return -i;}
這樣是無法編譯通過的(有些編譯器還是可以通過的,VS2013就可以),。我們要說的只是指定返回類型,。
我們可以這樣指定lambda表達(dá)式返回特定的類型:

[](int i)->int { if(i > 0) return i; else return -i;}

29、bind函數(shù)【標(biāo)準(zhǔn)庫】
       在C++的標(biāo)準(zhǔn)庫中,,所有與查找或者比較有關(guān)的算法函數(shù),,都會接受一個所謂的謂詞(其實(shí)它就是一個函數(shù)指針的形參),這個謂詞一般情況下都會是一元謂詞 (即只接受一個形參的函數(shù)),,但往往只接受一個形參的函數(shù)并不能滿足我們的需求,。比如,我們調(diào)用find_if,,查找某個string內(nèi)所有滿足大于6個 字母的單詞,,這時我們可以定義一個比較函數(shù)(C++ Primer)。
bool check_size(const string &s,string::size_type sz)
{
       return s.size() >= sz;
}
我們需要調(diào)用find_if,,但這個函數(shù)的謂詞是一元謂詞,,我們根本無法讓它指向一個只含一個形參的函數(shù)式。而C++11中提供了一個新的函數(shù)——bind;它會為我們生成一個類,,跟lambda類似,,它可能(我猜的)也含有一個重載了的調(diào)用運(yùn)算符。它的語法如下(為什么這貨跟sokcet的bind同名呀……)使用它要包含頭文件#include <functional>,而且,,它重載了很多個版本
            auto newCallable = bind(callable,arg_list);
     bind()函數(shù)能作為通用適配器是因?yàn)樗小罢嘉环?。它的占位符以_n的形式表示,數(shù)值n表示生成的可調(diào)用對象中參數(shù)的位置:_1表示第一個參數(shù),。_2表示第二個,依此類推,。
     我們可以這樣綁定check_size函數(shù),,讓它“轉(zhuǎn)”化為只需要一個形參就可以調(diào)用的函數(shù)。
     auto check6 = bind(check_size,_1,6);
     這個表達(dá)式,,用bind綁定了,,生成一個新的函數(shù)。bind()內(nèi)的語句,,我們可以這樣理解:首先,,我們要綁定的是check_size這個函數(shù),然后,, 它的第一個參數(shù)用了一個占位符,,表示我們要求在check6中有一個形參,用來代替_1的位置(即check_size的第一個參數(shù));最后的6表示第二 個參數(shù),。
所以我們可以這樣調(diào)用check6:
string s = "test";
bool b1 = check6(s);
所以調(diào)用find_if可以這樣:
auto wc =  find_if(words.begin(),words.end(),bind(check_size,_1,sz));
或者
auto wc =  find_if(words.begin(),words.end(),check6(sz));
1)我們再來看下占位符還有什么特性,。占位符位于placeholders名稱空間,。
     而占位符最特別的地方是可以重整參數(shù)順序,比如有一面這樣一條函數(shù):
     auto g = bind(f,a,b,_2,c,_1);
     這個函數(shù),,g,,只有兩個參數(shù),當(dāng)我們g(A,B)時,,第一個參數(shù)會被_1占用,,而第二個參數(shù)會被_2占用,所以g(A,B)實(shí)際上就是f(a,b,B,c,A),,這樣調(diào)用的,。利用這個原理,可以實(shí)現(xiàn)一個函數(shù),,可以比較大于,,也可以比較小于。比如有兩個過濾函數(shù),,是比較小于的,,但我們當(dāng)前有這樣一個函數(shù):
bool compare(int a,int b) { return a > b;}
如果bind(_2,_1),把a(bǔ)和b反過來,,那就相當(dāng)于比較a < b,。
2)bind的非占位符,默認(rèn)情況下,,他們都是拷貝復(fù)制的,,但有時我們確實(shí)需要他們進(jìn)行引用復(fù)制,我們可以用ref函數(shù)或者cref函數(shù)來解決非占位符參數(shù)的引用傳值,ref和cref都會生成相關(guān)的,。
ostream& print(ostream &os,const string &s,char c)
{
     return os << s << c;
}
如果我們這樣綁定:
auto f = bind(print,os,_1,'');
這就是一個錯誤的表達(dá)式,,因?yàn)閛stream類型不允許復(fù)制,必須引用傳參,。我們可以用ref函數(shù)解決,。
auto f = bind(print,ref(os),_1,'');
跟ref一樣,我們還有一個cref函數(shù),,只不過,,它是生成一個保存const引用的類。

30,、無序容器【容器】
      我們都知道,,維護(hù)元素的序列所花費(fèi)的代價是很高的,在我們學(xué)數(shù)據(jù)結(jié)構(gòu)時,,以最簡單的數(shù)組為例,,我們?nèi)绻迦胍粋€數(shù)組,并且需要保持其序列性,就算有好的算法,,但也難免要進(jìn)行多次的比較(注意,,一般是通過比較運(yùn)算符進(jìn)行的),當(dāng)數(shù)據(jù)結(jié)構(gòu)復(fù)雜,,數(shù)據(jù)量大時,,這個代價就是很大的。C++11為我們定義了4個無序關(guān)聯(lián)容器:
unordered_set,、unordered_multiset,、unordered_map和unordered_multimap。他們跟其它有序容器比較,,多了一個叫哈希策略的操作,。事實(shí)上,無序容器在組織元素不再是按比較運(yùn)算符進(jìn)行了,,而是通過一個哈希函數(shù)(即使每個元素都有一個自變量,,而能通過某一式子可以通過自變量得到結(jié)果的這個式子就叫哈希函數(shù))來組織的,所以,,它也是散列的,。這里不給具體的例子,這些容器的操作方式也是跟有序的大同小異的,,具體自行參考MSDN.

31,、智能指針
【標(biāo)準(zhǔn)庫】   
      還記得前座賣文胸的山田同學(xué)嗎……額,說錯,,還記得以前我們要寫一個內(nèi)置有指針數(shù)據(jù)類型的類有多痛苦嗎,?
    
比如我們定義一個類,類內(nèi)需要一個能裝很多string類型的成員變量,,它或者是讀取下載圖片的每個JSON的地址,,如果我們有1000個圖片,就要維護(hù)1000個這樣的數(shù)據(jù),,量是很大的,,如果我們腦殘地在類內(nèi)把該成員定義成vector<string>,想像下,,如果我們要賦值,如果我們要拷貝復(fù)制,,那要浪費(fèi)多少算法時間和內(nèi)存空間呀,,或者你已經(jīng)想到,你可以重載賦值和拷貝復(fù)制函數(shù),,直接傳遞一個指針,,對,就是這樣,但我們要先解決引用的問題,,如果我們這個類A test; A test2(test); 如果你這樣做,,不處理好引用計數(shù)問題,當(dāng)我們test超過生命周期了,,但test2是復(fù)制test的內(nèi)容的,,所以指針的值也一起復(fù)制了;這樣,,test2內(nèi)的數(shù)據(jù)成員就指向一個未知的內(nèi)存地址了,。所以,我們需要智能指針,,如果引用計數(shù),,當(dāng)拷貝復(fù)制時,我們需要這個引用計數(shù)+1,,這樣,,當(dāng)我析構(gòu)時,我就能判斷,,當(dāng)前引用計數(shù)數(shù)量,,如果仍然有其它對象引用我當(dāng)前的內(nèi)存地址,我則不能馬上析構(gòu),。
      以前的解決方案一般分為1,、在每個類中定義一個含有引用計數(shù)的指針類,它種方式叫值類型,。當(dāng)然,,為了這個值類型(它一般定義成我們自定義類中的私有成員)能完整操作我們類內(nèi)的成員,完全可以定義成某一個類的友元,,這樣就能訪問類內(nèi)私有成員了,。2、定義一個叫句柄類的類,,這個類并非像值類型一樣,,在每個需要智能指針的類內(nèi)都要定義一個能用性較差的類,而這個類主要目的僅僅是提供引用計數(shù)的只剩,。句柄類的行完全是一個普通指針的行為(通過重載一系列的操作符實(shí)現(xiàn)),,當(dāng)它定義為模板類時,也解決了通用性的問題,。而C++11標(biāo)準(zhǔn)庫中,,就為我們新增加了這樣的句柄類型的智能指針類:shared_ptr,unique_ptr和weak_ptr。
1,、shared_ptr
     這個類就是一個中規(guī)中紀(jì)的句柄類,,它的行為大致上跟一個指針完全是一樣的,,只是多了幾個函數(shù)。(這部分不說了,,自己看下頭文件或者查下資料吧,,要說太長,也沒有必要)這個類主要與一個位于memory中的標(biāo)準(zhǔn)函數(shù)庫中的,,make_shared的類配合使用,,以做到最安全的分配(make_shared的返回類型就是一個與模板類型相關(guān)的shared_ptr類型)。詳細(xì)的類定義,,不會寫,,因?yàn)殡S便網(wǎng)上找一大堆,我只是記錄學(xué)習(xí)筆記的而已,,用法一般這樣,。
class A
{
...........
private:
    shared_ptr<vector<string>> = make_shared<vector<string>("1","2")>;
};
2、unique_ptr
    這個類的行為,,跟舊版本的auto_ptr有部分相似,,但我并不想說他們的區(qū)別,畢竟我以前就幾乎沒怎么用過auto_ptr,,真要用到智能指針,,也是手動自己寫一個功能有限的。
   unique_ptr正如其名,,同一時刻,,它永遠(yuǎn)只能得到一個對象的所有權(quán)。我們要記住一個名詞,,就是當(dāng)對它賦值時,,我們不叫某某指針指向什么值,我們應(yīng)該習(xí)慣性地說某某unique_ptr擁有某個值的所有權(quán),,因?yàn)檫@樣我們更容易理解它的一些方法 ,。
   首先,當(dāng)unique_ptr擁有某一個值的所有權(quán)時,,我們不能簡單地通過賦值或者拷貝的方法對其進(jìn)行重新綁定其它值,,我們只能通過它內(nèi)的release或reset改變其所有權(quán)。如:
unique_ptr<int> p1(new int(0)); // unique_ptr的構(gòu)造接受的是一個指針變量
unique_ptr<int> p2 (new int(1));
p2 = p1; // 這是不允許的
我們可以用如下方法改變其所有權(quán),。 
1) 在構(gòu)建p2時傳一個指針,,而這個指針是由p1釋放后返回的

// 記住這里,p1.release()返回的是p1這個智能指針容器所保存的指針
unique_ptr<int> p2 (p1.release());
注意:release()只是切斷p1這個智能指針容器對new int(0)這個語句分配的指針的控制,,但它只是返回這個指針的值,,而并不會自動delete這個指針,所以當(dāng)我們直接p1.realease()這樣操作,,不僅不會釋放掉new int(0)這個內(nèi)存塊,,還會造成我們無法再次找回這個指針的地址。
2)利用reset釋放
      reset()有一個可選參數(shù),,這個參數(shù)是可以釋放指針?biāo)赶虻膶ο蟛⒅弥羔槥榭?比如p1.reset()就會釋放p1所指向的內(nèi)存塊,,并讓p1這個指針為null,這就是可以解決上述紅色部分的問題,。當(dāng)它帶一個參數(shù)時,,這個參數(shù)表示重新指向給定的指針,如:
p2.reset(p1.release());
這個表達(dá)式就是先釋放p2所指向的內(nèi)存塊,,再重新指向p1的內(nèi)存地址,。或者重新分配地址,。
p2.reset(new int(3));
3,、weak_ptr
     現(xiàn)在的高級語言,,我們通常都會聽到所謂的“弱指針”,,比如OC,經(jīng)常需要用到weak,,strong來給變量定義一個屬性。現(xiàn)在C++11也引進(jìn)了這個“弱”的概念,,雖然跟OC還是有點(diǎn)區(qū)別,,但其實(shí)作用卻是一樣的,都是為了更安全地處理指針,。
      一個weak_ptr必須與一個shared_ptr綁定才能使用,而且,,就算綁定了也不會增加shared_ptr的任何引用計數(shù),。正因?yàn)檫@種與其綁定或者說共享的對象的關(guān)系是如此不密切,,所以才說它是"弱",,就是說,他只是象征性指向一個對象,,而不論這個對象是否已經(jīng)銷毀,。
auto p = make_shared<int>(42); // 創(chuàng)建一個shared_ptr對象
weak_ptr<int> wp(p); // wp綁定p,,p引用計數(shù)不變
正是因?yàn)樗辉黾?所以我們明顯不能直接無所顧忌地使用它對我們的指針對象進(jìn)行操作(果然是弱呀,這跟我們內(nèi)置的指針差不多,,唯一的好處就是作為一個類,,還是有不少函數(shù)可以使用,大大減少我們的寫代碼的數(shù)量,,如果我們需要臨時的指針指向shared_ptr,而又不想改變原有對象的生命周期(引用計數(shù)是影響對象的生命周期的),,我們就可以用用這個弱得很的智能指針了),,我們需要先用lock成員函數(shù)來判斷wp所綁定的對象是否仍然存在。當(dāng)存在時就返回這個shared_ptr對象,,否則就返回一個空的shared_ptr對象,。
if(shared_ptr<int> np = wp.lock()) // 返回一個shared_ptr對象,if這個對象不為空時才執(zhí)行下面的語句
{
    // 對wp返回的shared_ptr進(jìn)行操作
}

32,、定位new
     舊標(biāo)準(zhǔn)的C++,,當(dāng)我們new一個內(nèi)存區(qū)域時,如果空間不夠,,會直接拋出一個bad_alloc的錯誤,,并中斷程序,而指針的值側(cè)為null?,F(xiàn)在,,C++11有一種新形式,我們稱之為定準(zhǔn)new,,它我們向new傳遞額外的參數(shù),如:
int *p = new (nonthrow) int; // 當(dāng)分配失敗時,,不拋出bad_alloc,而且,,p指針為nullptr
上面的表達(dá)式,,向new傳遞一個nonthrow的參數(shù),,告訴new,,不拋出異常。

33,、類內(nèi)初始值
       創(chuàng)建對象時,類內(nèi)初始值將用于初始化數(shù)據(jù)成員,。沒有初始值的成員將被默認(rèn)初始化,。如果我們沒有提供任何的初始值,,也沒有在構(gòu)造函數(shù)里定義任何相關(guān)的初始化操作,。那就按舊版本一樣,,全局的內(nèi)置類型由系統(tǒng)決定初始值,,局部的仍然是不確定的值,。

class CC
{
public:
CC() {}
~CC() {}
private:
int a = 7; // 類內(nèi)初始化,,C++11 可用
}


34,、允許使用作用域運(yùn)算符來獲取類成員的大小
        在標(biāo)準(zhǔn)C++,,sizeof可以作用在對象以及類別上,。但是不能夠做以下的事:

struct ST
{

int a = 0; // 聲明時就必須要定義
static int b;
};

cout << sizeof(ST::b) << endl; // 靜態(tài)成員本來就是這樣取值的,,所以跟新的有沖突,,只有非staic可以

cout << sizeof(hasY::a) << endl; // 直接由hasY型別取得非靜態(tài)成員的大小,,C++03不行,。 C++11允許

      以前我們要取一個數(shù)據(jù)的大小,,必須要在有類生成了相應(yīng)的實(shí)例后才行,。

35、=default
    舊版本的C++中,,當(dāng)一個類內(nèi)我們沒有定義構(gòu)造函數(shù)、拷貝構(gòu)造函數(shù),、拷貝賦值運(yùn)算符和析構(gòu)函數(shù)時,,則編譯器會為我們定義一個合成的版本以讓我們的類能正常運(yùn)行,。對于默認(rèn)構(gòu)造函數(shù),,當(dāng)我們定義了自己版本的構(gòu)造函數(shù),編譯器則不會再為我們合成默認(rèn)構(gòu)造函數(shù)了,,而對于另外的三個成員函數(shù),,無論我們是否定義了自己的版本,,編譯器都會為我們合成一個,。(事實(shí)上,,當(dāng)一個類需要我們自己來控制其析構(gòu)時,,也幾乎肯定了必須要我們定義自己的拷貝構(gòu)造函數(shù)和拷貝賦值運(yùn)算符)。
   C++11為我們提供了一個=default來顯式地要求編譯器生成合成的版本,。
class test
{
public:
  // 這個構(gòu)造函數(shù)跟編譯器合成的默認(rèn)構(gòu)造函數(shù)完全一樣,但它100%不是我們編譯器為我們合成的而是我們自己寫上的,,所以我們需要=default讓編譯器生成合成的版本,,這樣這個構(gòu)造函數(shù)的作為就跟編譯器默認(rèn)為我們合成的合成構(gòu)造函數(shù)的位為一樣,。
  test() {} = default;
  test(bool b) = default
  {
      cout << b << ednl;
  }
  test(const test&) = default; // 拷貝構(gòu)造函數(shù)
  test(const test& T,int i = 0);{}// 拷貝構(gòu)造函數(shù),他們能重載,,但這個不能加=default后面會說
// 下面這個只是一個帶兩參數(shù)的構(gòu)造函數(shù),因?yàn)镃++中對拷貝構(gòu)造函數(shù)定義是這樣的:如果一個構(gòu)造函數(shù)的第一個參數(shù)是自身類類型的引用,,且任何額外參數(shù)都有默認(rèn)值,,則此構(gòu)造函數(shù)是拷貝構(gòu)造函數(shù),。這個對于下面說的移動拷貝構(gòu)造函數(shù)同樣適合,。
  test(const test& T,double i);{}
  test& operator=(const test&); // 拷貝賦值運(yùn)算符
 ~test() = default; // 析構(gòu)函數(shù)
};
test& test::operator=(const test&) = default; // 類外指定,即在類定義時指定=default
=default在類內(nèi)出現(xiàn)時,,所修飾的成員函數(shù)都為inline版本,,而類外出現(xiàn)時,,則為非inline版本,。
嗯,我剛接觸這個時,,做了一件很腦殘的事,。上面有注譯,。這是編譯過不去的,不多說,,我只是腦殘地試了下,。
test(const test&,,int i = 0) = default; // 錯誤
當(dāng)然,,如果沒有=default,這個成員函數(shù)是正確的,,它是一個拷貝構(gòu)造函數(shù),當(dāng)這種除了第一個參數(shù)是本身類的引用外,,如還帶有額外參數(shù)時,,這個額外的參數(shù)必須要有默認(rèn)實(shí)參——如果它不帶默認(rèn)實(shí)參的話,就相當(dāng)于是有兩個(這點(diǎn)對拷貝賦值運(yùn)算符是無效的,,因?yàn)閛perator=對參數(shù)是有要求的),,但無論如何,,它都不是正確的由編譯器合成的拷貝構(gòu)造函數(shù)的格式。
=default可以肯定地說,,它只能用于能合成的成員函數(shù),。
注意:上述的我們一般只聲明而不定義,,因?yàn)槿绻覀兌x的話就跟=default有沖突,,=default完全是說明由編譯器生成我們成員函數(shù)內(nèi)的其它代碼,所以它只是聲明而非定義,。


36、刪除函數(shù)
     如果我們用過單例這種設(shè)計模式時,,又剛好需要定義一個類,,這個類不能多次構(gòu)造,只能在整個程序中只有一個實(shí)例存在,,這時我們一般都會阻止這個類有構(gòu)造函數(shù)。還記得我們一般做法是如何的嗎,?是的,,我們通過先寫一個構(gòu)造函數(shù),通過這個函數(shù)阻止了編譯器的合成構(gòu)造函數(shù),,再把這個構(gòu)造函數(shù)定義為private讓它不能在類外調(diào)用,;然后再小心翼翼地處理拷貝和拷貝賦值運(yùn)算符。讀過這種代碼的人都清楚,,如果沒有人告訴你這個是一個單例,,我估計你真得愣個10來秒才能看明白意圖。
    現(xiàn)在C++11為我們提供了更直觀的方法(C++11新增了很多后置的關(guān)鍵字,,包括后面會提到的override,都是使語言看起來更簡潔,,C++開始不斷向簡潔的語言風(fēng)格進(jìn)化著)。通過使用=delete來刪除能合成的成員函數(shù),。=delete有一個跟=default不同的地方,即=delete必須定義在類的聲明中,,以確保編譯器能盡早知道那個函數(shù)是我們指定刪除的。而類是否合成默認(rèn)版本,,則是編譯器在類定義時決定的,。所以=default和=delete這種差異,并不與C++的原本邏輯相悖,。

class test
{
public:
  test() {} = delete;
  test(const test&) = delete; // 拷貝構(gòu)造函數(shù)
  test& operator=(const test&) = delete; // 拷貝賦值運(yùn)算符
 ~test() = delete; // 析構(gòu)函數(shù)
};


用這個方法,,在設(shè)計類似單例模式這種類時,我們可以不需要管我們的類的屬性是否為private,,直接后置=delete就行了,。你也可以把拷貝和賦值都干掉,,這個實(shí)例類連引用計數(shù)都可以不用了。但這個類卻只有簡單的處理邏輯的功能了,。這是題外話,,不多說。
另外,,當(dāng)我們把一個析構(gòu)函數(shù)定義為不可訪問或者刪除函數(shù),,我們的合成構(gòu)造函數(shù)和拷貝構(gòu)造函數(shù)(拷貝賦值運(yùn)算符仍然能合成,,因?yàn)橛袝r我們雖然不能構(gòu)造A對象,,但如果B派生了A,,在B有構(gòu)造函數(shù)的話,,仍然可以進(jìn)行賦值操作的,,當(dāng)然,A現(xiàn)在無法構(gòu)造,,這個前提也不成立,,但C++語義就是允許合成拷貝賦值運(yùn)行符)是被設(shè)置成=delete的,因?yàn)椴荒茚尫艑ο?,所以編譯器也不允許我們創(chuàng)建對象,,但我們?nèi)匀豢梢宰远x構(gòu)造函數(shù),,達(dá)到創(chuàng)建對象的目的,,但這樣的話,,如果存在指針對象,,就會無法釋放了。

class hasY
{
public:
hasY() = default;
hasY(const hasY&)
{
cout << "copy" << endl;
}

hasY& operator=(const hasY&)
{
cout << "=copy" << endl;
return *this;
}
~hasY() = delete;
};


hasY hy;
hasY hy2 = hy;
hasY hy3;
hy3 = hy;


C++11新標(biāo)準(zhǔn)——學(xué)習(xí)筆記 - 天蓋領(lǐng)域 - 天蓋領(lǐng)域的博客
       當(dāng)我們定義了一個移動構(gòu)造函數(shù)和/或一個移動賦值運(yùn)算符時,,則該類的合成拷貝函數(shù)和拷貝賦值運(yùn)算符將會被定義為刪除的,。但100%情況是當(dāng)需要自定義移動操作函數(shù)時,我們都需要先定義拷貝操作函數(shù),。總而言之,,移動操作函數(shù)和拷貝操作函數(shù)是相類的,。
*拷貝構(gòu)造函數(shù)和拷貝賦值函數(shù)的不同之處
        這個不算C++11的內(nèi)容,,是老生常談了,但我還是想寫,,主要是如果不弄明白,,移動操作函數(shù)和拷貝操作函數(shù)之間的相互關(guān)系可能就會亂了(注意那個藍(lán)色的和/或),。在C++中,列表初始化,,初始化,,賦值,賦值初始化是要區(qū)分的:

int i(0); // 舊版初始化
int i{0};或int i ={0}; // C++11列表初始化
int i = 0; // 這是賦值初始化,,是初始化
int j; j = i; // 這個才是賦值,,而拷貝操作函數(shù)也有相同性質(zhì)。

class hasY
{
public:
hasY() = default;

hasY(const hasY&)
{
cout << "copy" << endl;
}

hasY& operator=(const hasY&)
{
cout << "=copy" << endl;
return *this;
}
};


1,、hasY hy; //調(diào)用合成構(gòu)造函數(shù)
2,、hasY hy2 = hy; // 輸出copy,編譯器跳過=號,,相當(dāng)于hasY hy2(hy)
3,、hasY hy3; // 調(diào)用合成構(gòu)造函數(shù)
4、hy3 = hy; // 輸出=copy

      上面的代碼,,可能很多人會覺得2處是調(diào)用拷貝賦值運(yùn)算符,,其實(shí)這個跟前面的內(nèi)置類型一樣,這個是賦值初始化,,而是賦值,。有些書上會叫賦值構(gòu)造函數(shù),所以我為什么要叫拷貝賦值運(yùn)算符就是不想弄亂,。


37,、右值引用
     C++11為了支持移動操作,新引用了一種名為右值引用,。正如其名,,右值引用必須綁定到右值。對于左值和右值的定義,,可能一時是難以理解的,,按概念來說,它已經(jīng)不是以前C那樣以等號左側(cè)或右側(cè)來區(qū)分了,,所以也變得更艱澀難明了,。但我們只記住這樣一點(diǎn):
有名字且可以取地址的就是左值;沒有名字且無法取地址的就是右值,。(const左值引用算例外)
     這樣從代碼行為上來理解,,會更容易掌握點(diǎn)。
我用通過&&來獲取一個右值的引用,,如:
int i = 0;
int &r = i; // 正確,,左值引用
int &&rr = i; // 錯誤,i有名字能取地址,,是一個左值,,左值不能綁定右值引用

// 錯誤,i * 42這個表達(dá)式會生成一個臨時變量,,我們無法取地址,所以它是一個右值,,右值不能綁定左值引用int &r2 = i * 42;
int &&rr2 = i * 42; // 正確,,右值引用綁定一個右值
// 正確,但這個引用之所以成立,,是被const限制的,,const要求被綁定的值是一個右值,所以這個表達(dá)式成立,。const int &r3 = i * 42;
當(dāng)一個原則上不能再改變的右值被成功綁定后,,我們就能通過右值引用這個別名對其進(jìn)行與左值變量相同的操作,比如改變其值,。因?yàn)樗?dāng)于有名字了且可以取地址了,。
int &&rr2 = i * 42;
cout << rr2 << " " << ++rr2 << endl; // 輸出為42 43
盡管要求不能把一個左值綁定到一個右值引用,但C++11中仍然有方法解決這一問題,。
可以可以使用std::move(別落下std::,,下面會解釋的)來把一個左值顯式地轉(zhuǎn)換為右值。
int &&rr3 = std::move(r);
std::move的作用就是返回一個右值引用,,這個右值引用綁定的值是把move的參數(shù)r通過強(qiáng)制類型引用轉(zhuǎn)換成右值類型,。通過引用折疊機(jī)制,move的參數(shù)可以接受一個左值也可以接受一個右值,,但無論如何,,我們應(yīng)該都要遵守:盡量不使用一個移后源對象的值。其實(shí)看下頭文件我們也可以看到,,std::move就是把類型轉(zhuǎn)換下再返回而已(它本身是模板函數(shù),,類型會根據(jù)傳入的參數(shù)類型進(jìn)行實(shí)參推斷),但它返回的是與原來類型相關(guān)的右值引用,,即type&&類型,。
正因?yàn)橹皇沁M(jìn)行類型轉(zhuǎn)化,所以,,無論我們++i還是++r,都能改變i的值,,但不提倡這樣使用。(C++ primer這部分我真不理解,,郁悶,!不過,大家用了std::move后,,除了重新賦值和釋放這個變量外真不要作任務(wù)操作,,這是C++11的約定,大家應(yīng)該都遵守,如果你要搞特化,,別人看你代碼,不僅看得一頭霧水,,還要罵句SB的,。)
還有更重要的一點(diǎn):右值被引用后,即右值引用本身也是一個左值,。更詳細(xì)的請看下面的引用折疊,。


38、移動構(gòu)造函數(shù)
      移動構(gòu)造函數(shù)是C++11新引進(jìn)的概念,,目的在于讓當(dāng)以某個臨時變量,,或者說即將結(jié)束生命周期的對象(也就是右值)作為構(gòu)造參數(shù)時,可以達(dá)到比舊版的拷貝構(gòu)造更快的效率,。移動構(gòu)造函數(shù)跟拷貝構(gòu)造一樣,,要求每個參數(shù)必須為一個本身類類型的右值引用,任何額外的參數(shù)都必須有默認(rèn)實(shí)參,。先拿個例子比較下拷貝函數(shù)和構(gòu)造函數(shù)區(qū)別吧,。

class A
{
public:
     A(){cout << "construct" << endl;}      //1
A(const A&){cout << "copy" << endl;} //2

A(const A&&) {cout << "move" << endl;} //3
};


void main()

{

A a;

A b(a);

A c(std::move(b));

}

上面代碼輸出的結(jié)果如下圖:
C++11新標(biāo)準(zhǔn)——學(xué)習(xí)筆記 - 天蓋領(lǐng)域 - 天蓋領(lǐng)域的博客
      紅框里的輸出結(jié)果就是了。這里應(yīng)該很清晰了,,當(dāng)我們利用std::move把類b轉(zhuǎn)成一個右值時,,就調(diào)用了移動構(gòu)造函數(shù)。前面我們的例子太簡單了,,一般的移動構(gòu)造函數(shù)至少要遵守這樣的原由:必須讓被竊取的對象與原來的資源切斷聯(lián)系,這里特別要注意指針,。

class A
{
public:

// 這里不能用const,因?yàn)橄旅嬉膕ource內(nèi)的值

A(const A&& source) noexcept:a(source.a),b(source.b)

{

source.a = source.b = nullptr;

}


public:

int *a;

char *b;
};

上面的移動構(gòu)造函數(shù)的實(shí)質(zhì)是怎么樣的呢?

C++11新標(biāo)準(zhǔn)——學(xué)習(xí)筆記 - 天蓋領(lǐng)域 - 天蓋領(lǐng)域的博客
C++11新標(biāo)準(zhǔn)——學(xué)習(xí)筆記 - 天蓋領(lǐng)域 - 天蓋領(lǐng)域的博客
 
 從圖中可以看出,,移動構(gòu)造函數(shù)實(shí)質(zhì)就是“竅取”資源,,它本身不分配任何資源,所以也沒有調(diào)用到相應(yīng)的構(gòu)造函數(shù),。上圖這種情況,,當(dāng)我們“竅取”了b的資源后,所以b的資源的歸屬權(quán)已經(jīng)變更了,,根據(jù)移動函數(shù)的約定:當(dāng)移動后的源應(yīng)該是銷毀無害的,,但如果我們不處理的話,當(dāng)我們b生命周期結(jié)束,,肯定會影響到對象c的,。
事實(shí)上,移動構(gòu)造函數(shù)是解決指針,,特別是臨時指針在復(fù)制時,,如果數(shù)據(jù)量消耗效率的問題。比如上面的函數(shù),如果我們是一個內(nèi)置類型,,結(jié)果會如何呢,?:

class A{

public:

// 因?yàn)橛辛艘苿訕?gòu)造函數(shù),不會再合成默認(rèn)構(gòu)造函數(shù)了

A():a(0),b(new char('a')){}

A(A& source):a(source.a)

{

// 不像移動構(gòu)造函數(shù),,拷貝構(gòu)造函數(shù)一般都是另外分配內(nèi)存空間后再進(jìn)行賦值

b = new char(*(source.b));

}

// 這里不能用const,因?yàn)橄旅嬉膕ource內(nèi)的值
A(A&& source) noexcept:a(source.a),b(source.b)
{
source.b = nullptr;
}


public:
int a;

char *b;

};

void main()

{

A a;

cout << (void*)a.b << endl;

A b(std::move(a));

cout << &(a.a) << " " &(b.a) << endl;

cout << (void*)a.b << " " << (void*)b.b << endl;

cout << &(a.b) << " " << &(b.b) << endl;

}

    如果你前面讓我誤導(dǎo)了(我承認(rèn)我那圖很爛,,我們我只寫了堆和棧,但并不詳盡,,反正堆一般是new分配的),,認(rèn)為移動函數(shù)是一種機(jī)制,那輸出的結(jié)果就讓你哭了,。
C++11新標(biāo)準(zhǔn)——學(xué)習(xí)筆記 - 天蓋領(lǐng)域 - 天蓋領(lǐng)域的博客
    說到底,,移動函數(shù)也是符合C++語義的,如果你什么都不做,,它也是什么都不處理,。上面的輸出結(jié)果中,,a和b是兩個類,,有各自的內(nèi)存空間,所以輸出內(nèi)置型的數(shù)據(jù)時(它在棧上,包括b這個指針本身,,也是在棧上),,它因?yàn)槭莾蓚€對象分別控制的,,所以第二行輸出的地址不同;第三行就比較有意思,,它輸出的是移動后的源和當(dāng)前變量的指針地址,,它們是相同的,為什么第一個是空指針呢,?因?yàn)槲覀冊谝苿雍?,把移后源設(shè)置為nullptr,但我在第一行cout輸出了未清空的地址,。第四行,,它也是輸出棧上的指針變量,所以它的地址也是不同的,。
     考慮一下,,只是為了解決指針的復(fù)制問題,我們?nèi)匀豢梢岳每截悩?gòu)造函數(shù)執(zhí)行跟移動構(gòu)造函數(shù)一樣的操作(代碼一樣時),,但這樣雖然有移動構(gòu)造函數(shù)的功能,,但它有兩個問題(假設(shè)舊版本沒有std::move情況下):
1、它無法將一個臨時變量快速處理(即無法支持右值引用)
2,、假設(shè)我們對拷貝構(gòu)造函數(shù)這樣做了,,那很明顯,當(dāng)我們需要對象連堆都一起分離出來該怎么辦呢,?所以說,,這種情況下,我們這樣做是不現(xiàn)實(shí)的,。事實(shí)上它也一般定義為const,。C++引進(jìn)移動構(gòu)造函數(shù)后,,就魚和熊掌都可兼得了。

    最后,,我們注意到那個noexcept,,這也是C++11新引進(jìn)的。它位于參數(shù)列表和puvcwx列表開始的冒號之間,。它指示程序不拋出任何異常,,其實(shí)可以想像,我們只是“竅取”數(shù)據(jù),,并沒有進(jìn)行任何移動類的操作,,所以根本不會發(fā)生什么錯誤。標(biāo)準(zhǔn)庫中的vector類中的pushback也有這種東西,,失敗時并不會彈出一個異常,。

39、移動賦值函數(shù)
      移動賦值函數(shù)跟移動構(gòu)造函數(shù)差不多,,只是比后者多了兩個步驟:
1,、每次賦值前,都要先把原來的數(shù)據(jù)清空,。如果有數(shù)據(jù)成員指向分配好的內(nèi)存空間,,我們側(cè)要釋放這部分內(nèi)存。不然肯定會出現(xiàn)懸掛問題的,。
2,、解決自賦值問題,這個問題拷貝賦值運(yùn)算符也有的,,當(dāng)一個對象是其自身時(雖然我們一般不會遇到這種情況,,但仍然要小心有些程序員就是二愣子),我們不應(yīng)該進(jìn)行賦值,,特別是清空數(shù)據(jù)問題,,如果我們在賦值前清空了數(shù)據(jù),,則源對象因?yàn)槭亲陨恚矔R上清空數(shù)據(jù)的,,這樣我們的賦值不僅沒有意義,還要導(dǎo)致錯誤,。
以前面的例子為例,,我們弄一個簡單的移動賦值函數(shù),。

class A{

public:

// 因?yàn)橛辛艘苿訕?gòu)造函數(shù),,不會再合成默認(rèn)構(gòu)造函數(shù)了

A():a(0),b(new char('a')){}

A(A& source):a(source.a)

{

// 不像移動構(gòu)造函數(shù),,拷貝構(gòu)造函數(shù)一般都是另外分配內(nèi)存空間后再進(jìn)行賦值

b = new char(*(source.b));

}

// 這里不能用const,因?yàn)橄旅嬉膕ource內(nèi)的值
A(A&& source) noexcept:a(source.a),b(source.b)
{
source.b = nullptr;
}

A& operator=(A&& source) noexcept

{

if(this != &source) // 比較地址,,不能自身進(jìn)行賦值構(gòu)造

{

// 清空數(shù)據(jù)

free();

this->a = source.a;

this->b = source.b;

// 記得處理好移動后源的數(shù)據(jù),,指針的一定要清空,,保證它銷毀是無害的

source.b = nullptr;

}

return *this;

}


public:
int a;

char *b;

};


40、編譯器合成的移動構(gòu)造函數(shù)和移動賦值函數(shù)
        我們都知道,,在C++11中(舊版本也是一樣只是沒有=delete行為),除非我們顯式刪除拷貝構(gòu)造函數(shù)和拷貝賦值運(yùn)算符,,不然編譯器都會為我們合成一個默認(rèn)的版本,。但對于新增加的移動構(gòu)造函數(shù)和移動賦值函數(shù),,一般情況下,編譯器根本不會為我們合成這些移動操作的函數(shù),,但在特定的條件下,,卻還是會為我們合成移動操作的函數(shù)。這里我們還要加上另一條重要的信息:定義一個移動函數(shù)或者移動賦值運(yùn)算符的類必須也定義自己的拷貝操作法函數(shù),。否則,,這些成員默認(rèn)地被定義為刪除的,。
1、當(dāng)我們有任何版本的自定義拷貝構(gòu)造函數(shù),,拷貝賦值運(yùn)算符或者析構(gòu)函數(shù)時,,編譯器不會為我們合成移動操作類函數(shù)(有任何一個拷貝操作函數(shù)時都不會再合成移動操作函數(shù)),。對于存在拷貝構(gòu)造函數(shù)和拷貝賦值運(yùn)算符,,我們可能比較容易理解,,因?yàn)楫?dāng)我們定義了這兩個函數(shù)后,就算沒有移動操作函數(shù),,我們的類仍然能工作得很好:對于臨時變量,我們可以把臨時變量的堆數(shù)據(jù)完整拷貝過來,。說到低,移動操作類函數(shù),,只是為了讓類工作得更快,而非不可缺,。但對于析構(gòu)函數(shù),,我們可能會有所疑惑,我們應(yīng)該樣想:要析構(gòu)的地方,,都是我們的類存在指針時,,而當(dāng)類需要析構(gòu)函數(shù)時,幾乎也肯定了我們必須需要拷貝構(gòu)造函數(shù)和拷貝賦值運(yùn)算符了,。而也正因?yàn)閹в兄羔槻僮?,編譯器無法推斷我們需要做的操作,在不安全的前提下,,C++11規(guī)定(我說了,,移動操作函數(shù)只是為了類更好工作,而非不可缺)有析構(gòu)函數(shù)時不合成移動操作函數(shù)是明智的。
2,、當(dāng)類沒有定義任何版本的拷貝控制成員時(即有合成的卻沒有自定義的拷貝構(gòu)造函數(shù),、拷貝賦值運(yùn)算符或者析構(gòu)函數(shù)時),且類的每個非static數(shù)據(jù)成員都可以移動(前面說了,,這里移動其實(shí)就指“竅取”,,但有些成員是無法移動的,我們都知道,,盡管內(nèi)存空間可以不變,,但棧內(nèi)的指向該空間的指針變量是不同位置的,即移后源和當(dāng)前類棧內(nèi)的變量是不同的,,這樣應(yīng)該清楚了吧,,比如,類內(nèi)有自定義類時時,,而該類本身又沒有定義移動操作函數(shù)的這種情況就是其中一種存在不可移動數(shù)據(jù)成員的情況了,。),編譯器才會為我們合成移動構(gòu)造函數(shù)或移動賦值函數(shù),。
但無論如何,,編譯器都不會隱式把移動操作函數(shù)定義為刪除函數(shù)。
     再來討論下=default和=delete,,后者是幾乎用不上的,,它不同于拷貝操作函數(shù),就算有自定義的,,編譯器也會為我們合成,,移動操作函數(shù)原來能合成的條件就比較苛刻,所以真的幾乎用不上,。至于=default,,它的行為就有點(diǎn)奇怪了,就算我們顯示定義一個移動操作函數(shù)為=default時,,它卻在特定條件下,,無視=default而表示為刪除函數(shù),。情況有如下這些(以上條件都是在顯示=default時才會發(fā)生,,沒有的話只會發(fā)生前面提到的那2種情況):
1、當(dāng)顯示定義=default移動操作函數(shù)時,,但類本身卻有成員不能移動,,則編譯器會把移動操作定義為刪除函數(shù)。(這種情況幾乎都是類內(nèi)有類成員變量,,且該類沒有定義移動操作函數(shù)才出現(xiàn)的)如:

struct hasY
{
hasY() = default;

hasY(hasY&&) = default; // 因?yàn)閅,,它是=delete的

//假設(shè)這里的類Y定義有自己的拷貝構(gòu)造函數(shù)(不會再合成移動操作函數(shù)),

卻也沒有定義自己的移動操作法函數(shù)

  Y mem;

};

hasY hy,hy2 = std::move(hy); // 錯誤,,沒有移動構(gòu)造法函數(shù),,它是刪除的


2,、如果類成員內(nèi)有成員的移動構(gòu)造函數(shù)或移動賦值運(yùn)算符被定義為刪除的(用=delete顯示定義刪除,或編譯器隱式在特定條件下把=default改變?yōu)?delete,,再次說明,,編譯器永遠(yuǎn)不會在正常情況下把移動操作函數(shù)隱式定義為=delete)或者聲明為不可訪問的(即private或protected),則類的移動構(gòu)造函數(shù)或移動賦值運(yùn)算符被定義為刪除的,。
3,、類似拷貝構(gòu)造函數(shù)(33點(diǎn)說到)。如果析構(gòu)函數(shù)被定義刪除或不可訪問的,,則類的移動構(gòu)造函數(shù)被定義為刪除的,。移動賦值運(yùn)算符仍然有效,但卻無意義,。
4,、類似拷貝賦值運(yùn)算類。當(dāng)類中有const成員或者引用時,,則類的移動賦值運(yùn)算符定義為刪除的,。這里順便解釋下為什么拷貝賦值運(yùn)算符在類中有const成員或者引用時會被刪除合成的。我們都知道,,在C++中,,const變量和引用類型變量,都是在定義的同時要初始化的,,而類中,,const變量是在聲明時直接定義的,而引用類型則是是在構(gòu)造函數(shù)中進(jìn)行列表化初始化的,,所以這種情況下,,拷貝賦值運(yùn)算符不能復(fù)制const對象,他只會自己原生有一個const變量,,值不會變,。它也不能復(fù)制引用成員,因?yàn)樗仨氃跇?gòu)造函數(shù)的列表中給定,,所以編譯器為了更安全點(diǎn),,直接就是在遇到這種情況下,不再合成拷貝賦值運(yùn)算符,,而是交由程序員自己處理了,。移動賦值運(yùn)算也是相同原理。移動拷貝仍然可用,,跟拷貝構(gòu)造函數(shù)一樣,,合成的拷貝函數(shù),逐值拷貝,把拷貝源對象的值作為新構(gòu)造對象的值傳遞,。

C++11新標(biāo)準(zhǔn)——學(xué)習(xí)筆記 - 天蓋領(lǐng)域 - 天蓋領(lǐng)域的博客

   我們再談下關(guān)于拷貝操作函數(shù)和移動操作函數(shù)之間的交互,。如果一個類,本身沒有定義任何的移動操作函數(shù),,那么,,我們能否處理一個右值呢?假如有如下這樣一個類:
class A
{
public:
A() = default;
A(const A&) // 定義了拷貝構(gòu)造函數(shù)后,,編譯器不會再合成任何的移動構(gòu)造函數(shù)
{
cout << "copy" << endl,;
}
};
A x;
A y(x); // 拷貝構(gòu)造函數(shù),x為一個左值,,它能取地址,,有名稱的
A z(std::move(x));
//盡管std::move后的類型為A&&類型,但沒有移動操作函數(shù)時,,仍然是調(diào)用拷貝構(gòu)造函數(shù),,而且是肯定安
全的。這時會進(jìn)行隱匿的類型轉(zhuǎn)換,,把一個右值引用A&&轉(zhuǎn)換為一個const A&引用,,即使我們拷貝構(gòu)造函數(shù)
定義為A(&),它仍然能接受一個A&&的右值類型。
*前面說右值引用時提過的const變量應(yīng)該記得吧,?任務(wù)一個引用都要求右邊的值是左值,,但如果是const引
用,則右邊的值可以是一個右值,,因?yàn)閏onst要求一個常量值,,而常量值都是一個右值。構(gòu)造函數(shù)形參可以
是非const而安全接受右值,,其實(shí)原理也相當(dāng)簡單:對于構(gòu)造函數(shù)來說,,主要責(zé)任是構(gòu)造一個新對象,即便修改
了右值,,也無所謂,,反正臨時的東西,扔了就行了,。但是普通函數(shù),,目的不是為了別的,就是為了處理傳進(jìn)來
的值,,傳進(jìn)來的引用,,是既能in又能out的。所以這里如果非const引用一個右值,,那肯定會讓out的結(jié)果失效。
所以對于普通函數(shù)而言,要想接受一個右值必須是一個const類型的形參,;沒有所謂的機(jī)制,,只是編譯器是這
樣定義的。

void hit(const int &) //假如沒有const說明右邊可以是右值,,則std::move后的值不能用于參數(shù)

{
cout << "function" << endl;
}

int iv = 4;
hit(std::move(iv)); // 正確

類的輸出結(jié)果:
C++11新標(biāo)準(zhǔn)——學(xué)習(xí)筆記 - 天蓋領(lǐng)域 - 天蓋領(lǐng)域的博客
      有個提醒,,我不建議在除了類定義外的移動操作函數(shù)。因?yàn)樗浅2环€(wěn)定,,你不能確定移后源對象的確切狀態(tài),,是否仍然有其它的對象在使用它。我們說過,,移后源應(yīng)該是可以隨時釋放清空而不影響其它操作的,,而且,我們不能對其內(nèi)數(shù)據(jù)進(jìn)行操作,。但誰又能100%知道當(dāng)前移后源的狀態(tài)呢,,特別是大項(xiàng)目中,這個更是致命,。

41,、移動迭代器【標(biāo)準(zhǔn)庫】   
      新標(biāo)準(zhǔn)庫中新增加了一種移動迭代器適配器,它主要通過調(diào)用標(biāo)準(zhǔn)庫的make_move_iterator函數(shù)將一個普通迭代器轉(zhuǎn)換為一個移動迭代器,,而且,,返回后的迭代器的解引用運(yùn)算返回的是一個右值引用。還記得標(biāo)準(zhǔn)庫中新加入的begin()和end()嗎,?主要我們這樣操作:
     make_move_iterator(begin())就可以返回一個移動迭代器,,想像一下,如果你的類支持移動操作,,如果直接傳遞一個右值就可以達(dá)到優(yōu)化效率的效果,。

38、引用函數(shù)
      在說這個之前,,我們先回故一下類內(nèi)有關(guān)const區(qū)分重載版本的內(nèi)容,。
C++11新標(biāo)準(zhǔn)——學(xué)習(xí)筆記 - 天蓋領(lǐng)域 - 天蓋領(lǐng)域的博客
    看到了嗎?我們的const版本函數(shù),,就算跟普通成員名稱,,返回值和參數(shù)完全一致,但它仍然是一個重載版本,。而對于以const作為形參的函數(shù),。卻不是重載函數(shù),因?yàn)镃++規(guī)定,,頂層const作為形參時,,是會被忽略const標(biāo)識的,。
    還有一種情況。

string s1,s2;

s1 + s2 = "test";

    上面這段代碼,,s1+s2后是由s1返回一個臨時變量(即右值),,但我們卻對右值進(jìn)行了重新賦值。當(dāng)然,,在語法上是沒有問題,,但使用上卻幾乎無法重獲這個"test"字符串,除非我們std::move()了它,。這種用法是很普遍的,,但我們卻很想禁止這種用法。以前不行,,C++11為我們準(zhǔn)備了新的工具,。
    好了,現(xiàn)在我們說下新引進(jìn)的“引用函數(shù)”吧,。
1,、語法
      我們通過在移動操作函數(shù)或拷貝操作函數(shù)后增加引用限定符,就可以阻某些用法,。
      引用限定符(只能用于非static)有兩種,,分別是"&"和"&&",前者表示this(類對象本身)只可以指向一個左值,,而后者表示this只可以指向一個右值,。

class Foo
{
public:
Foo &operator= (const Foo&) &;
};
Foo& Foo::operator=(const Foo& )&
{
return *this;
}

  這個類的賦值運(yùn)算符只允許左側(cè)的操作數(shù)即類對象本身是一個左值。如果我們這樣操作:
   Foo f,i;
   std::move(f) = i;
   這是無法成立的,。

2,、重載和引用函數(shù)
    C++11中新增加的這種引用函數(shù),跟const成員函數(shù)一樣,,可通過引用限定符來區(qū)分重載版本,。
    前面已經(jīng)看到,我們定義const成員函數(shù)時,,可以定義兩個版本,,唯一罰沒是一個有const限定,一個沒有,。而對于有引用限定符的成員函數(shù),,有一點(diǎn)不一樣:*如果我們定義兩個(兩個是重載的最低要求,不然還叫什么重載)或兩個以上具有相同名字相同參數(shù)列表的成員函數(shù)(C++中,,返回值不作為重載的考量依據(jù))時,,就必須對所有重載的幾個函數(shù)都加上引用限定符,讓它變?yōu)橐孟薅愋偷暮瘮?shù),,或者所有不加作為普通成員函數(shù)定義,。

class Foo
{
public:
Foo storted() && ;
// 錯誤:要么把前面的變?yōu)槠胀ǔ蓡T函數(shù),,要么在const后面也加上&&(記得,是&&不是&,,引用限定符是分兩種的)
Foo sorted() const;
Foo sorted(Comp*);
Foo sorted(Comp*) const;
};


*注意點(diǎn):
1)當(dāng)一個成員函數(shù)帶有引用限定符,,那么無論是聲明還是定義,,它都必須要帶上,。
2)引用限定符必須寫在函數(shù)的最后位置,如果是const成員函數(shù),,就寫在const后面,。

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,,不代表本站觀點(diǎn),。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,,謹(jǐn)防詐騙,。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點(diǎn)擊一鍵舉報,。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多