C++ 11學習和掌握 ——《深入理解C++ 11:C++11新特性解析和應用》讀書筆記(一)


  因為偶然的機會,在圖書館看到《深入理解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 finaloverride控制, 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委員會,也感謝本書作者詳細的闡述,受用無窮!

  參考資料:

  《深入理解C++11:C++ 11新特性解析與應用》


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM