因為偶然的機會,在圖書館看到《深入理解C++ 11:C++11新特性解析和應用》這本書,大致掃下,受益匪淺,就果斷借出來,對於其中的部分內容進行詳讀並親自編程測試相關代碼,也就有了整理寫出這篇讀書筆記的基礎。C++作為踏入編程的最初語言,一直充滿感情,而C++11作為新標准雖然推出一段時間了,卻因為總總原因直到現在才去開始真正了解,不過一句話回盪在腦中:當你認為為時已晚的時候,恰恰是最早的時候!從C++98到C++11, C++11標准經歷了10幾年的沉淀,以全新的姿態迎接新的挑戰,長話短說,進入正題,來一起了解吧。(注:本筆記中所有代碼在Code::Blocks下編輯,使用gcc-4.9.3 -std=c++11模式下編譯)
C++11的新基礎特性
1.1 用於兼容C99特性的宏,可以檢查編譯系統對標准C庫的支持情況,不過測試部分顯示未定義。
cout<<"Standard Clib: "<< __STDC_HOSTED__<<endl; //Standard Clib: 1(指定編譯器目標系統是否包含完整的C庫) cout<<"Standard C: "<<__STDC__<<endl; //__STDC__:1(指定編譯器目標系統是否與C標准一致) //cout<<"C Standard Version: "<<__STDC_VERSION__<<endl; //測試gcc 沒有定義(支持標准C庫的版本) //cout<< "ISO/IEC: "<<__STDC_ISO_10646__<<endl; //測試gcc 沒有定義
1.2 __func__ 用於獲得當前函數名字符串的宏
const char * Stanard_Macros(void) { //...... return __func__; //返回值:Stanard_Macros }
1.3 _Pragma 預處理操作符,與#pragma功能相同,不過因為支持傳遞字符串,所以可以用宏命令替代,如對於經常使用的頭文件單次包含。
//頭文件防止重復包含 #pragma once; _Pragma("once"); #define PRAGMA(x) _Pragma(#x) //宏命令中使用#可以實現替換字符串化 PRAGMA(once);
1.4 __VA_ARGS__ 變長參數的宏定義是指宏定義中參數列表的最后一個參數為..., 而實現部分可以用__VA_ARGS__替換
//__FILE__當前文件路徑 //__LINE__當前文本行 #define LOG(...){ \ fprintf(stderr, "%s: Line %d:\t", __FILE__, __LINE__);\ //輸出錯誤的文件及行號地址 fprintf(stderr, __VA_ARGS__);\ //輸出錯誤的數據 fprintf(stderr, "\n"); \ } LOG("%s", "NO ERR!"); // xxxx:xxxx: NO ERR!
1.5 新的整型long long/unsigned long long(長度不小於64位)
long long int lli = -90000000000LL; unsigned long long int ulli = 9000000000000ULL; cout<<"LLONG_MIN: "<<LLONG_MIN<<endl; //LLONG_MIN: -9223372036854775808 cout<<"LLONG_MAX: "<<LLONG_MAX<<endl; //LLONG_MAX: 9223372036854775807 cout<<"ULLONG_MAX: "<<ULLONG_MAX<<endl; //ULLONG_MAX: 18446744073709551615
1.6 斷言幫助開發者快速定位問題違反程序前提條件的錯誤,不過斷言只在程序運行時執行,這在某些情況下,是不可接受的,特別是對於模板實例化時出現的錯誤,應該在編譯器就確定。在C++11中引入了static_assert斷言來解決問題,它支持兩個參數輸入,一個是返回bool型的表達式,另一個是警告信息。
static_assert(sizeof(b)==sizeof(a), "the parameters of bit_copy must have same width"); //靜態斷言,編譯器確定,返回false則觸發告警信息
1.7 noexcept修飾符和noexcept操縱符是提供給庫作者使用,表示函數如果出現異常,不會拋出,編譯器會直接調用std::terminate()函數來終止程序運行,從而阻止了異常的的傳播和擴散。此外:C++11中類析構函數默認是noexcept(true),不過注意noexcept只會阻止異常的傳播和擴散(這對於定位錯誤很有幫助),而不會阻止異常(如throw語句產生異常的)的發生。
const char * Stanard_Macros(void) noexcept //相當於noexcept(true), 如果有異常會發生,不會傳播和擴散,編譯會調用std::terminate()來終止程序運行 const char * Stanard_Macros(void) noexcept(false) //noexcept中可以為結果為bool型的表達式
1.8 類成員變量的快速初始化和新的列表初始化,在C++11中,除了靜態變量,對於其它變量也允許使用等號或{}進行就地初始化。
class Mem{ public: Mem(int i): m(i){}; ~Mem(){}; const char *ShowMem(void){ std::cout<<"Mem: "<<m<<" "; return __func__; }; private: int m{0}; }; //快速初始化 //初始化列表優於就地的列表初始化 class Init{ public: Init(): a(0){}; Init(int d): val('G'), a(d){}; //初始化列表實際效果后作用,效果上優於列表初始化 // ~Init(){ throw 1; }; ~Init(){}; const char * showPara(std::string str); int a; private: char val{'g'}; //成員變量的快速初始化 Mem mem{1}; std::string name{"Init"}; }; //C++的成員變量快速初始化 Init init1{5}; //C++11的統一列表初始化 Init init2; init1.showPara("init1: "); //init1: 5 init1: G name :Init Mem: 1 mem :ShowMem init2.showPara("init2: "); //init2: 0 init2: g name :Init Mem: 1 mem :ShowMem
1.9 非靜態成員的sizeof, sizeof作為運算符,對於處理數組,類和對象時經常用到,不過在之前的C++98中,對於非靜態成員是不能直接編譯的,需要借用對像實例。
cout<<"Dyna Sizeof: "<<sizeof(((Init *)0)->a)<<endl; //C++98時借用實例對象獲得非靜態成員長度 cout<<"Dyna Sizeof: "<<sizeof(Init::a)<<endl; //C++11支持直接獲得
1.10 擴展的friend用法, friend時C++中比較特別的關鍵字,一方面它讓程序員省下了很多代碼,另一方面也破壞了OOP中的封裝性,在C++11中做了改進,以保證更好的運用。
class People; template<typename T> class Ploy; template<typename T> class Ploy{ public: friend T; //指定類的友元類,允許友元類訪問本地參數 private: string m{"smart"}; }; class People{ public: void ShowPara(Ploy<People> *p){ cout<<"Ploy Data: "<<p->m<<endl; } }; //friend的擴展用法 Ploy<People> ppe; People Pe; Pe.ShowPara(&ppe);
1.11 final和override控制, final用來限制基類虛函數的對應的派生類不能重寫該虛函數,從而避免了某些接口被重寫覆蓋; override則指定了函數必須重載基類的虛函數,否則編譯通不過,這就避免了某些輸入名或者原型不匹配等錯誤的發生。
class MathObject{ public: virtual double Arith() = 0; virtual void Print() = 0; }; class Printable:public MathObject{ public: double Arith() = 0; //純虛函數僅允許為0 void Print() final{ std::cout<<"Output is: "<< Arith() <<std::endl; } }; class Add2:public Printable{ public: Add2(double a, double b):x(a), y(b){} double Arith() override{ //override指定函數為派生類的override(覆蓋)函數,會進行檢查 return x+y; } // void Print(){} //編譯會報錯,因為父類聲明了final,子類不允許重載 private: double x, y; }
1.12 默認的模板參數,C++11中模板和函數一樣,支持默認參數。
//類模板 C++98就允許,不過定義有要求 template<typename T1, typename T2 = int>class DefClass1{}; //允許,指定類模板的默認模板參數 //template<typename T1 = int, typename T2> class DefClass2; //通不過編譯,多個默認模板參數指定默認值時,必須遵守從右向做的原則 //函數模板 C++11添加 template<typename T1, typename T2 = int>void DefFunc1(T1 a, T2 b); template<typename T1 = int, typename T2>void DefFunc2(T1 a, T2 b); //允許
1.13 外部模板, 外部模板實現依賴於C++98已有的特性,顯示實例化。這樣就可以實現一次實例,多次使用。
//sundry.h template <typename T>void fun(T) {} //模板函數定義 //sundry1.cpp template void fun<int>(int); //模板實例化聲明 //sundry2.cpp extern template void fun<int>(int); //模板外部聲明
1.14 局部或者匿名類型做模板實參, C++11支持匿名或者局部類型作為模板的實參,提供了更多的使用方法。
template<typename T> class X{}; template<typename T> void TempFunc(T t){}; struct {int i;}b; typedef struct{int i;}B; struct C{} c; X<B> x1; //匿名結構體作為實參,不過只支持別名,不支持匿名結構體直接作為實參 X<C> x2; //局部變量作為實參 TempFunc(b); //匿名類型變量,C++11允許 TempFunc(c); //局部類型變量,C++11允許
到現在為止,C++11的新基礎特性中比較重要的部分差不多講完了,從這些改動可以看出,C++11向着更方便,更強大的方向穩步前進,而且這些改動只是滄海一粟,如新的lambda表達式,類的構造函數的新實現,更加常態化的SFINAE,這些都值得研讀,不過今天有點晚了,先到此為止,后面我會一邊測試一邊總結,感謝C++11委員會,也感謝本書作者詳細的闡述,受用無窮!
參考資料: