C++11是曾經被叫做C++0x,是對目前 C++ 語言的擴展和修正,C++11 不僅包含核心語言的新機能,而且擴展了 C++ 的標準程序庫(STL),併入了大部分的 C++ Technical Report 1(TR1)程序庫(數學的特殊函數除外)。
C++11 包括大量的新特性:包括lambda表達式,類型推導關鍵字auto、 decltype,和模板的大量改進。
本文將對 C++11 的以上新特性進行簡單的講解,以便大家能夠快速瞭解到 C++11 對 C++ 的易用性方面起到的巨大作用。
auto
在C++11之前,auto 關鍵字用來指定存儲期。在新標準中,它的功能變爲類型推斷。auto 現在成了一個類型的佔位符,通知編譯器去根據初始化代碼推斷所聲明變量的真實類型。各種作用域內聲明變量都可以用到它。例如,名空間中,程序塊中,或是for 循環的初始化語句中。
auto a; // 錯誤,auto是通過初始化表達式進行類型推導,如果沒有初始化表達式,就無法確定a的類型
auto i = 42; // i is an int
auto l = 42LL; // l is an long long
auto p = new foo(); // p is a foo*
auto d = 1.0;
auto str = "Hello World";
auto ch = 'A';
auto func = less<int>();
vector<int> iv;
auto it = iv.begin();
使用auto 通常意味着更短的代碼(除非你所用類型是 int,它會比auto 少一個字母)。試想一下當你遍歷
STL 容器時需要聲明的那些迭代器(iterator)。現在不需要去聲明那些 typedef 就可以得到簡潔的代碼了。
爲了在遍歷容器時支持“foreach”用法,C++11 擴展了for 語句的語法。用這個新的寫法,可以遍歷 C 類型的數組、初始化列表以及任何重載了非成員的
begin() 和 end() 函數的類型。
如果你只是想對集合或數組的每個元素做一些操作,而不關心下標、迭代器位置或者元素個數,那麼這種 foreach 的 for 循環將會非常有用。
template<typename T>
void printVec1D(const vector<T>& vec) {// 打印一維數組
// for(typename vector<T>::const_iterator it=vec.begin(); it != vec.end(); it++)
// cout << *it << " ";
for(const auto& val : vec)
cout << val << " ";
}
map<string, vector<int> > Map;
for(auto it = Map.begin(); it != Map.end(); ++it) {
//do something with 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
decltype
int x = 3;
decltype(x) y = x;
有什麼實際應用呢?舉個例子,上面代碼中auto
compose(T1 t1, T2 t2)->decltype(t1+
t2) 用 decltype 解析返回值類型。nullptr
nullptr 是爲了解決原來 C++ 中 NULL 的二義性問題而引進的一種新的類型,因爲 NULL 實際上代表的是 0,但由於 0 可以被隱式類型轉換爲整型,這就會存在一些問題。關鍵字nullptr 是 std::nullptr_t 類型的值,用來指代空指針。nullptr 和任何指針類型以及類成員指針類型的空值之間可以發生隱式類型轉換,同樣也可以隱式轉換爲bool 型(取值爲 false)。但是不存在到整型的隱式類型轉換。
void F(int a) {
cout<<a<<endl;
}
void F(int *p) {
assert(p != NULL);
cout<< p <<endl;
}
int main() {
int *p = nullptr;
int *q = NULL;
bool Equal = ( p == q ); // Equal的值爲 true,說明 p 和 q 都是空指針
int a = nullptr; // 編譯失敗,nullptr 不能轉型爲 int
F(0); // 在 C++98 中編譯失敗,有二義性;在C++11中調用 F(int)
F(nullptr);// 調用 f(int*)
return 0;
}
爲了向前兼容,0 仍然是個合法的空指針值。
override 和 final
我總覺得 C++ 中虛函數的設計很差勁,因爲時至今日仍然沒有一個強制的機制來標識虛函數會在派生類裏被改寫。vitual 關鍵字是可選的,這使得閱讀代碼變得很費勁。因爲可能需要追溯到繼承體系的源頭才能確定某個方法是否是虛函數。爲了增加可讀性,我總是在派生類裏也寫上 virtual 關鍵字,並且也鼓勵大家都這麼做。即使這樣,仍然會產生一些微妙的錯誤。看下面這個例子:
class Base {
public:
virtual void f(short) {
std::cout << "Base::f" << std::endl;
}
};
class Derived : public Base {
public:
virtual void f(int) {
std::cout << "Derived::f" << std::endl;
}
};
因爲這裏形參不同,所以這裏並不是重寫,而是派生類 Derived 中新定義一個虛函數。
因爲這裏形參不同,所以這裏並不是重寫,而是派生類
Derived 中新定義一個虛函數。
但我的意圖是重寫,幸運的是現在有一種方式能描述你的意圖。新標準加入了兩個新的標識符(不是關鍵字):override
和 final
- override,表示函數應當重寫基類中的虛函數。(如果指定override,就表示這個函數是重寫的,如果我們函數寫的不符合,就會編譯出錯提示)
- final,表示派生類不應當重寫這個虛函數。
class Derived : public Base {
public:
// 這裏加上 override 表示這個函數是重寫的,因爲形參表不同,無法重寫,編譯提示錯誤:error: 'virtual void Derived::f(int)' marked override, but does not override|
virtual void f(int) override {
std::cout << "Derived::f" << std::endl;
}
};
//=======================================================
class B {
public:
virtual void f(int) {
std::cout << "B::f" << std::endl;
}
};
class D : public B {
public:
// 這裏加上 final 表示函數 f(int) 不可以被其派生類重寫
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;
}
};
在派生類中,可以同時使用override 和final
標識。
Strongly-typed enums 強類型枚舉
傳統的 C++ 枚舉類型存在一些缺陷:它們會將枚舉常量暴露在外層作用域中(這可能導致名字衝突,如果同一個作用域中存在兩個不同的枚舉類型,但是具有相同的枚舉常量就會衝突),而且它們會被隱式轉換爲整形,無法擁有特定的用戶定義類型。
在 C++11 中通過引入了一個稱爲強類型枚舉的新類型,修正了這種情況。強類型枚舉由關鍵字 enumclass 標識。它不會將枚舉常量暴露到外層作用域中,也不會隱式轉換爲整形,並且擁有用戶指定的特定類型(傳統枚舉也增加了這個性質)。
enum class Options {
None, One, All
};
Options o = Options::All;// 需要使用作用域。
Lambdas
匿名函數(也叫 lambda)已經加入到 C++中,並很快異軍突起。這個從函數式編程中借來的強大特性,使很多其他特性以及類庫得以實現。你可以在任何使用函數對象或者函子(functor)或 std::function 的地方使用 lambda。
Lambda的語法如下:[ 函數對象參數 ]( 操作符重載函數參數 )->返回值類型{ 函數體 }
vector<int> iv{5, 4, 3, 2, 1};
int a = 2, b = 1;
std::for_each(iv.begin(), iv.end(), [b](int &x){cout<<(x + b)<<endl;});// (1)
std::for_each(iv.begin(), iv.end(), [=](int &x){x *= (a + b);});// (2)
std::for_each(iv.begin(), iv.end(), [=](int &x)->int{return x * (a + b);});// (3)
1. [ ] 內的參數指的是 Lambda 表達式可以取得的全局變量。(1)函數中的
b 就是指函數可以得到在 Lambda 表達式外的全局變量,如果在[ ] 中傳入= 的話,即是可以取得所有的外部變量,如(2)和(3)Lambda 表達式
2. ( ) 內的參數是每次調用函數時傳入的參數。
3. -> 後加上的是 Lambda 表達式返回值的類型,如(3)中返回了一個int 類型的變量
變長參數的模板
我們在C++中都用過pair,pair可以使用 make_pair 構造,構造一個包含兩種不同類型的數據的容器。比如
auto p = make_pair(1, "C++ 11");
由於在 C++11 中引入了變長參數模板,所以發明了新的數據類型:tuple,tuple 是一個 N 元組,可以傳入 1 個, 2 個甚至多個不同類型的數據器。比如
auto t1 = make_tuple(1, 2.0, "C++ 11");
auto t2 = make_tuple(1, 2.0, "C++ 11", {1, 0, 2});
更優雅的初始化
在被引入 C++ 之前,只有數組能使用初始化列表,其他容器想要使用初始化列表,只能用一下方法:
int arr[3] = {1, 2, 3};
vector<int> v(arr, arr + 3);
在 C++11 中,我們可以使用如下方式初始化:int arr[3]{1, 2, 3};
vector<int> iv{1, 2, 3};
map<int, string>{{1, "a"}, {2, "b"}};
string str{"Hello World"};
參考這個地方和這個地方