深刻理解C++11

深刻理解C++11ios

一、不少 現實 的 編譯器 都 支持 C99 標準 中的__ func__ 預約 義 標識符 功能, 其 基本 功能 就是 返回 所在 函數 的 名字。c++

  編譯器 會 隱式 地 在 函數 的 定義 以後 定義__ func__ 標識符。程序員

const char* hello() 
{ 
    static const char* __func__ = "hello";
    return __func__; 
}

  __func__ 能夠用於構造函數中。ide

#include < iostream> 
using namespace std;

struct TestStruct {
  TestStruct () : name(__ func__) {}
  const char *name;
};

int main()
{
  TestStruct ts;
  cout << ts. name << endl; // TestStruct
}
// 編譯 選項: g++ -std= c++ 11 2- 1- 3. cpp

  不過 將__ fun__ 標識符 做爲 函數參數的默認值不容許 的,函數

void FuncFail( string func_ name = __func__) {};// 沒法 經過 編譯

 

 二、在 C99 標準 中, 程序員 能夠 使用 變長參數的宏定義。 變長 參數 的 宏 定義 是 指在 宏 定義 中 參數 列表 的 最後 一個 參數 爲 省略號, 而 預 定義 宏__ VA_ ARGS__ 則 可 以在 宏 定義 的 實現 部分 替換 省略號 所 表明 的 字符串。優化

#define PR(...) printf(__ VA_ ARGS__)

  一個應用的例子。this

#include < stdio. h> 

#define LOG(...) {\
  fprintf( stderr,"% s: Line %d:\ t", __FILE__, __LINE__);\
  fprintf( stderr, __VA_ ARGS__);\
  fprintf( stderr,"\ n");\
}

int main() {
  int x = 3; // 一些 代碼...
  LOG(" x = %d", x); // 2- 1- 5. cpp: Line 12: x = 3
}
// 編譯 選項: g++ -std= c++ 11 2- 1- 5. cpp

 

三、long long 是C99標準。C++11中才將其定爲正式標準。事實上,在C++11之前,不少編譯器也支持了long long。spa

  long long 整型 有 兩種: long long 和 unsigned long long。 在 C++ 11 中, 標準 要求 long long 整型 能夠 在 不一樣 平臺 上有 不一樣 的 長度, 但 至少 有 64 位。c++11

  咱們 在 寫 常數 字面 量 時, 能夠 使用 LL 後綴( 或是 ll) 標識 一個 long long 類型 的 字面 量, 而 ULL( 或 ull、 Ull、 uLL) 表示 一個 unsigned long long 類型 的 字面 量。code

long long int lli = -9000000000000000000LL; 
unsigned long long int ulli = -9000000000000000000ULL;

   下面 的 類型 是 等價 的: long long、 signed long long、 long long int、 signed long long int; 而 unsigned long long 和 unsigned long long int 也是 等價 的。

  對於 printf 函數 來講, 輸出 有 符號 的 long long 類型 變量 能夠 用 符號% lld, 而無 符號 的 unsigned long long 則 能夠 採用% llu。

 

四、__cplusplus

  好比 在 C++ 03 標準 中,__ cplusplus 的 值 被 預約 爲 199711L, 而在 C++ 11 標準 中, 宏__ cplusplus 被 預 定義 爲 201103L。

  這點 變化 能夠 爲 代碼 所用。 好比 程序員 在想 肯定 代碼 是 使用 支持 C++ 11 編譯器 進行 編譯 時, 那麼 能夠 按下 面的 方法 進行 檢測:

  

#if __cplusplus < 201103L #error "should use C++ 11 implementation" #endif

 

五、assert,運行時檢查 

  在 C++ 中, 標準 在< cassert> 或< assert. h> 頭 文件 中爲 程序員 提供 了 assert 宏, 用於 在 運行時 進行 斷言。

  在 C++ 中, 程序員 也能夠 定義 宏 NDEBUG 來 禁用 assert 宏。 這對 發佈 程序 來講 仍是 必要 的。

#ifdef NDEBUG 
    # define assert( expr) (static_ cast< void> (0)) 
#else 
    ... 
#endif

  能夠 看到, 一旦 定義 了 NDBUG 宏, assert 宏 將被 展開 爲 一條 無心義 的 C 語句( 一般 會被 編譯器 優化 掉)。

 

六、static_assert,靜態檢查。

  c++11中引入了static_assert。在過去,須要本身手動實現,或使用boost提供的功能 BOOST_STATIC_ASSERT。一個可能的手動實現以下:

#define assert_ static( e) \ 
    do { \ 
    enum { assert_ static__ = 1/( e) }; \ 
} while (0)

  一個使用樣例以下:

static_ assert( sizeof( int) == 8, "This 64- bit machine should follow this!"); 
int main() 
{
     return 0; 
} 

   static_ assert 的 斷言 表達式 的 結果 必須 是在 編譯 時期 能夠 計算 的 表達式, 即 必須 是 常量 表達式。 若是 讀者 使用 了 變量, 則 會 致使 錯誤,

int positive( const int n) 
{
    static_ assert( n > 0, "value must > 0"); 
} // 編譯 選項: g++ -std= c++ 11 -c 2- 5- 6. cpp

 

七、noexcept

  在 excpt_ func 函數 聲明 以後, 咱們 定義 了 一個 動態 異常 聲明 throw( int, double), 該 聲明 指出 了 excpt_ func 可能 拋出 的 異常 的 類型。 事實上, 該 特性 不多 被 使用, 所以 在 C++ 11 中 被棄 用了( 參見 附錄 B), 而 表示 函數 不會 拋出 異常 的 動態 異常 聲明 throw() 也 被 新的 noexcept 異常 聲明 所 取代。

  如下的語法已被拋棄:

void excpt_ func() throw( int, double) { ... }

  noexcept 的用法:

void excpt_ func() noexcept (常量 表達式);

   下面的第二個 noexcept 就是 一個 noexcept 操做 符。 當 其 參數 是 一個 有可能 拋出 異常 的 表達式 的 時候, 其 返回 值 爲 false, 反之 爲 true。

template < class T> void fun() noexcept( noexcept( T())) {}

   C++ 11 標準 中 讓 類 的 析構函數 默認 也是 noexcept( true) 的。 固然, 若是 程序員 顯 式 地 爲 析 構 函數 指定 了 noexcept, 或者 類 的 基 類 或 成員 有 noexcept( false) 的 析 構 函數, 析 構 函數 就 不會 再 保持 默認值。

#include < iostream> using namespace std; 
struct A { ~ A() { throw 1; } };
struct B { ~ B() noexcept( false) { throw 2; } };
struct C { B b; };
int funA() { A a; }
int funB() { B b; }
int funC() { C c; }
int main() {
  try { funB(); } catch(...){ cout << "caught funB." << endl; // caught funB. }
  try { funC(); } catch(...){ cout << "caught funC." << endl;
// caught funC. }
  try { funA();
// terminate called after throwing an instance of 'int' } catch(...){ cout << "caught funA." << endl; } } // 編譯 選項: g++ -std= c++ 11 2- 6- 2. cpp

  不管是 析 構 函數 聲明 爲 noexcept( false) 的 類 B, 仍是 包含 了 B 類型 成員 的 類 C, 其 析 構 函數 都是 能夠 拋出 異常 的。 只有 什麼 都沒 有 聲明 的 類 A, 其 析 構 函數 被 默認 爲 noexcept( true), 從而 阻止 了 異常 的 擴散。

 

八、在 C++ 98 中, 支持 了 在 類 聲明 中 使用 等號「=」 加 初始 值 的 方式, 來 初始化 類 中 靜態成員常量。 這種 聲明 方式 咱們 也稱 之爲「 就地」 聲明。 就地 聲明 在 代碼 編寫 時 很是 便利, 不過 C++ 98 對 類 中就 地 聲明 的 要求 卻 很是 高。 若是 靜態 成員 不知足 常量 性, 則 不能夠 就地 聲明, 並且 即便常量 的 靜態 成員 也 只能 是 整型 或者 枚舉型 才能 就地 初始化。 而非 靜態 成員 變量 的 初始化 則 必須 在 構造 函數 中 進行。

class Init{ 
    public: Init(): a( 0){} 
    Init( int d): a( d){} 
private: 
    int a; 
    const static int b = 0;
    int c = 1; // 成員, 沒法 經過 編譯 
    static int d = 0; // 成員, 沒法 經過 編譯 
    static const double e = 1. 3; // 非 整型 或者 枚舉, 沒法 經過 編譯     
    static const char * const f = "e"; // 非 整型 或者 枚舉, 沒法 經過 編譯 
}; // 編譯 選項: g++ -c 2- 7- 1. cpp

   使用 g++ 的 讀者 可能 發現 就地 初始化 double 類型 靜態 常量 e 是 能夠 經過 編譯 的, 不過 這 實際 是 GNU 對 C++ 的 一個 擴展, 並不 聽從 C++ 標準)。

  在 C++ 11 中, 標準 還 容許 使用 等號= 或者 花 括號{} 進行 就地 的 非 靜態 成員 變量 初始化。

struct init{ int a = 1; double b {1. 2}; };

   就地初始化 和 初始化 列表 並不 衝突。 程序員 能夠 爲 同一 成員 變量 既 聲明 就地 的 列表 初始化, 又在 初始化 列表 中 進行 初始化, 只不過 初始化 列表 老是 看起來「 後 做用於」 非 靜態 成員。 也就是說, 初始化 列表 的 效果 老是 優先於 就地 初始化 的。

  

#include < string> using namespace std; 
class Mem { public: Mem( int i): m( i){} private: int m; };

class Group {
 public:
  Group(){} // 這裏 就不 須要 初始化 data、 mem、 name 成員 了
  Group( int a): data( a) {}
// 這裏 就不 須要 初始化 mem、 name 成員 了
  Group( Mem m) : mem( m) {}
// 這裏 就不 須要 初始化 data、 name 成員 了
  Group( int a, Mem m, string n): data( a), mem( m), name( n){}
 private:
  int data = 1;
  Mem mem{ 0};
  string name{" Group"};
};
// 編譯 選項: g++ 2- 7- 4. cpp -std= c++ 11 -c

  對於 非 常量 的 靜態 成員 變量, C++ 11 則 與 C++ 98 保持 了 一致。 程序員 仍是 須要 到頭 文件 之外 去 定義 它, 這 會 保證 編譯 時, 類 靜態 成員 的 定義 最後 只 存在 於 一個 目標 文件 中。

 

九、sizeof

  不過 在 C++ 98 標準 中, 對 非靜態成員變量 使用 sizeof 是 不能 夠 經過 編譯 的。

#include < iostream> 
using namespace std;
struct People { 
  public: 
    int hand; 
    static People * all; 
}; 
int main() { 
    People p; 
    cout << sizeof( p. hand) << endl; // C++ 98 中 經過, C++ 11 中 經過 
    cout << sizeof( People:: all) << endl; // C++ 98 中 經過, C++ 11 中 經過 
    cout << sizeof( People:: hand) << endl; // C++ 98 中 錯誤, C++ 11 中 經過 
} // 編譯 選項: g++ 2- 8- 1. cpp

  而在 C++ 98 中, 只有 靜態 成員, 或者 對象的實例 才能 對其 成員 進行 sizeof 操做。 所以 若是 讀者 只有 一個 支持 C++ 98 標準 的 編譯器, 在 沒有 定義 類 實例 的 時候, 要 得到 類 成員 的 大小, 咱們 一般 會 採用 如下 的 代碼:

sizeof((( People*) 0)-> hand);

 

十、而在 C++ 11 中, 咱們 無需 這樣 的 技巧, 由於 sizeof 能夠 做用 的 表達式 包括了 類 成員 表達式。

而在 C++ 11 中, 咱們 無需 這樣 的 技巧, 由於 sizeof 能夠 做用 的 表達式 包括了 類 成員 表達式。

 

十一、friend

  C++98 中沒法對 typedef 進行 friend. C++11中能夠,而且C++11中省略了 class.

class Poly; 
typedef Poly P; 
class LiLei { 
    friend class Poly; // C++ 98 經過, C++ 11 經過 
}; 
class Jim { 
    friend Poly; // C++ 98 失敗, C++ 11 經過 
}; 
class HanMeiMei { 
    friend P; // C++ 98 失敗, C++ 11 經過 
}; // 編譯 選項: g++ -std= c++ 11 2- 9- 1. cpp

  C++11 中能夠爲類模板聲明友元,這在C++98中是沒法作到的。

class P; 
template < typename T>
class People { friend T; };
People< P> PP; // 類型 P 在這裏 是 People 類型 的 友 元
People< int> Pi;
// 對於 int 類型 模板 參數, 友 元 聲明 被 忽略
// 編譯 選項: g++ -std= c++ 11 2- 9- 2. cpp

 

十二、final / override

  C++98 中沒有方法阻止 vritual 函數被重載。

  

  C++11 中添加了 final 關鍵字來實現此功能。

  

  C++11 中,繼承類重載時,最好加上 override 關鍵字,有了override 關鍵字,編譯器能夠幫助檢測重載是否成功。

struct Base { 
    virtual void Turing() = 0; 
    virtual void Dijkstra() = 0; 
    virtual void VNeumann( int g) = 0; 
    virtual void DKnuth() const; 
    void Print(); 
}; 

struct DerivedMid: public Base { 
    // void VNeumann( double g); // 接口 被 隔離 了, 曾 想 多 一個 版本 的 VNeumann 函數 
}; 

struct DerivedTop : public DerivedMid { 
    void Turing() override; 
    void Dikjstra() override; // 沒法 經過 編譯, 拼寫 錯誤, 並不是 重載 
    void VNeumann( double g) override; // 沒法 經過 編譯, 參數 不一致, 並不是 重載 
    void DKnuth() override; // 沒法 經過 編譯, 常量 性 不一致, 並不是 重載 
    void Print() override; // 沒法 經過 編譯, 非 虛 函數 重載 
; // 編譯 選項: g++ -c -std= c++ 11 2- 10- 3. cpp

 

1三、函數模板的默認參數

  C++98中,只有類模板參數能夠有默認值,而函數模板不行。C++11中,這一限制已經解除了。

void DefParm( int m = 3) {} // c++ 98 編譯 經過, c++ 11 編譯 經過 

template < typename T = int> 
class DefClass {}; // 類模板參數,c++ 98 編譯 經過, c++ 11 編譯 經過 template 

< typename T = int> 
void DefTempParm() {}; // 函數模板參數,c++ 98 編譯 失敗, c++ 11 編譯 經過

  類模板的參數必須從右到左,而函數模板則無此規定。

template< typename T1, typename T2 = int> 
class DefClass1; 

template< typename T1 = int, typename T2> 
class DefClass2; // 沒法 經過 編譯 

template< typename T, int i = 0> 
class DefClass3; 

template< int i = 0, typename T> 
class DefClass4; // 沒法 經過 編譯 

template< typename T1 = int, typename T2> 
void DefFunc1( T1 a, T2 b); 

template< int i = 0, typename T> 
void DefFunc2( T a); 

 

1四、模板的顯式實例化、外部模板聲明。

  C++98中已經有了模板顯式實例化的功能。

// 對於如下模板
template < typename T> void fun( T) {}

// 像下面這樣顯式實例化
template void fun< int>( int);

  C++11中加入了外部模板聲明的功能。外部 模板 的 聲明 跟 顯 式 的 實例 化 差很少, 只是 多了 一個 關鍵字 extern。

extern template void fun< int>( int);

   若是 外部模板聲明 出現於某個編譯單元 中, 那麼與之對應的顯示實例化必須出現於另外一個編譯單元中或者同一個 編譯 單元 的 後續 代碼 中; 外部 模板 聲明 不能用於靜態函數( 即 文件域函數), 但能夠 用於 類靜態成員函數( 這一點 是 顯而易見 的, 由於 靜態 函數 沒有 外部 連接 屬性, 不可能 在 本 編譯 單元 以外 出現)。

  外部 模板 定義 更應該 算做 一種 針對 編譯器 的 編譯時間及空間 的優化手段。 不少 時候, 因爲 程序員 低估 了 模板 實例 化 展開 的 開銷, 所以 大量 的 模板 使用 會在 代碼 中產 生 大量 的 冗餘。 這種冗餘, 有的時候 已經 使得 編譯器和連接器 力不從心。

 

1五、局部、匿名類型做模板實參

  局部的類型匿名的類型 在 C++ 98 中 都不 能作 模板類的實參

template < typename T> 
class X {}; 

template < typename T> 
void TempFun( T t){}; 

struct A{} a; 
struct {int i;} b; // b是 匿名類型變量 
typedef struct {int i;} B; // B是 匿名類型 

void Fun() { 
    struct C {} c; // C是 局部類型 
    
    X< A> x1; // C++ 98 ok, C++ 11 經過 
    X< B> x2; // C++ 98 fail, C++ 11 經過 
    X< C> x3; // C++ 98 fail, C++ 11 經過 
    
    TempFun( a); // C++ 98 ok, C++ 11 經過 
    TempFun( b); // C++ 98 fail, C++ 11 經過 
    TempFun( c); // C++ 98 fail, C++ 11 經過 
}

  除了 匿名的結構體(anonymous struct)以外, 匿名的聯合體(anonymous union) 以及 枚舉類型(union), 在 C++ 98 標準 下 也都是沒法作 模板的實參的。 

  現在看來這都是沒必要要的限制。 因此 在 C++ 11 中標準容許了以上類型作模板參數的作法。雖然如此,但如下寫法是不被接受的。

template < typename T> 
struct MyTemplate { }; 
int main() {
    MyTemplate< struct { int a; }> t; // 沒法 編譯 經過, 匿名類型的聲明不能在模板實參位置 
    return 0; 
} // 編譯 選項: g++ -std= c++ 11 2- 13- 2. cpp
相關文章
相關標籤/搜索