【系列】淺析C++11新特性-轉自王書博

吐血整理C++11新特性

本文整理一些C++11的新特性,歡迎補充。

auto關鍵字
在C++11之前,auto關鍵字用來指定存儲期。在新標準中,它的功能變爲類型推斷。auto現在成了一個類型的佔位符,通知編譯器去根據初始化代碼推斷所聲明變量的真實類型。各種作用域內聲明變量都可以用到它。例如,名空間中,程序塊中,或是for循環的初始化語句中。

auto i = 42;        // i is an int
auto l = 42LL;      // l is an long long
auto p = new foo(); // p is a foo*

使用auto通常意味着更短的代碼(除非你所用類型是int,它會比auto少一個字母)。試想一下當你遍歷STL容器時需要聲明的那些迭代器(iterator)。現在不需要去聲明那些typedef就可以得到簡潔的代碼了。

std::map<std::string, std::vector<int>> map;
for(auto it = begin(map); it != end(map); ++it) 
{
}

需要注意的是,auto不能用來聲明函數的返回值。但如果函數有一個尾隨的返回類型時,auto是可以出現在函數聲明中返回值位置。這種情況下,auto並不是告訴編譯器去推斷返回類型,而是指引編譯器去函數的末端尋找返回值類型。在下面這個例子中,函數的返回值類型就是operator+操作符作用在T1、T2類型變量上的返回值類型。

template <typename T1, typename T2>
auto compose(T1 t1, T2 t2) -> **decltype**(t1 + t2)
{
   return t1+t2;
}
auto v = compose(2, 3.14); // v's type is double

nullptr
以前都是用0來表示空指針的,但由於0可以被隱式類型轉換爲整形,這就會存在一些問題。關鍵字nullptr是std::nullptr_t類型的值,用來指代空指針。nullptr和任何指針類型以及類成員指針類型的空值之間可以發生隱式類型轉換,同樣也可以隱式轉換爲bool型(取值爲false)。但是不存在到整形的隱式類型轉換。

void foo(int* p) {}

void bar(std::shared_ptr<int> p) {}

int* p1 = NULL;
int* p2 = nullptr;   
if(p1 == p2)
{
}

foo(nullptr);
bar(nullptr);

bool f = nullptr;
int i = nullptr; // error: A native nullptr can only be converted to bool or, using reinterpret_cast, to an integral type

爲了向前兼容,0仍然是個合法的空指針值。

基於範圍的for循環
爲了在遍歷容器時支持”foreach”用法,C++11擴展了for語句的語法。用這個新的寫法,可以遍歷C類型的數組、初始化列表以及任何重載了非成員的begin()和end()函數的類型。
如果你只是想對集合或數組的每個元素做一些操作,而不關心下標、迭代器位置或者元素個數,那麼這種foreach的for循環將會非常有用。

std::map<std::string, std::vector<int>> map;
std::vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
map["one"] = v;

for(const auto& kvp : map) 
{
  std::cout << kvp.first << std::endl;

  for(auto v : kvp.second)
  {
     std::cout << v << std::endl;
  }
}

int arr[] = {1,2,3,4,5};
for(int& e : arr) 
{
  e = e*e;
}

Override和final
看下面這個例子:

class B 
{
public:
   virtual void f(short) {std::cout << "B::f" << std::endl;}
};

class D : public B
{
public:
   virtual void f(int) {std::cout << "D::f" << std::endl;}
};

D::f 按理應當重寫 B::f。然而二者的聲明是不同的,一個參數是short,另一個是int。因此D::f只是擁有同樣名字的另一個函數(重載)而不是重寫。當你通過B類型的指針調用f()可能會期望打印出D::f,但實際上則會打出 B::f 。
另一個很微妙的錯誤情況:參數相同,但是基類的函數是const的,派生類的函數卻不是。

class B 
{
public:
   virtual void f(int) const {std::cout << "B::f " << std::endl;}
};

class D : public B
{
public:
   virtual void f(int) {std::cout << "D::f" << std::endl;}
};

同樣,這兩個函數是重載而不是重寫,所以你通過B類型指針調用f()將打印B::f,而不是D::f。
幸運的是,現在有一種方式能描述你的意圖。新標準加入了兩個新的標識符:
override,表示函數應當重寫基類中的虛函數。
final,表示派生類不應當重寫這個虛函數。
第一個的例子如下:

class B 
{
public:
   virtual void f(short) {std::cout << "B::f" << std::endl;}
};

class D : public B
{
public:
   virtual void f(int) override {std::cout << "D::f" << std::endl;}
};

現在這將觸發一個編譯錯誤:
‘D::f’ : method with override specifier ‘override’ did not override any base class methods
另一方面,如果你希望函數不要再被派生類進一步重寫,你可以把它標識爲final。可以在基類或任何派生類中使用final。在派生類中,可以同時使用override和final標識。

class B 
{
public:
   virtual void f(int) {std::cout << "B::f" << std::endl;}
};

class D : public B
{
public:
   virtual void f(int) override final {std::cout << "D::f" << std::endl;}
};

class F : public D
{
public:
   virtual void f(int) override {std::cout << "F::f" << std::endl;}
};

被標記成final的函數將不能再被F::f重寫。

強類型枚舉
傳統的C++枚舉類型存在一些缺陷:它們會將枚舉常量暴露在外層作用域中(這可能導致名字衝突,如果同一個作用域中存在兩個不同的枚舉類型,但是具有相同的枚舉常量就會衝突),而且它們會被隱式轉換爲整形,無法擁有特定的用戶定義類型。
在C++11中通過引入了一個稱爲強類型枚舉的新類型,修正了這種情況。強類型枚舉由關鍵字enum class標識。它不會將枚舉常量暴露到外層作用域中,也不會隱式轉換爲整形,並且擁有用戶指定的特定類型(傳統枚舉也增加了這個性質)。

enum class Options {None, One, All};
Options o = Options::All;

智能指針
詳見博客《淺析C++中的智能指針

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章