本文是C++11新特性系列的第二篇們主要記錄C++11的新語意。
2,新的語義
2.1 大括號{}與初始化
C++11之前,有這麼幾種初始化的方式
- 默認初始化: A a; //調用默認構造函數
- 值初始化:A a=1; //調用單參構造函數
- 直接初始化: A a(1);//調用單參構造函數
- 拷貝初始化: A a2(a1); A a2 = a1;//調用拷貝構造函數
C++11之後,增加列表初始化,也叫統一初始化。上面的四種初始化可以用統一的形式了:
- 默認初始化: A a{};
- 值初始化:A a = {1}; //調用單參構造函數
- 直接初始化: A a{1};//調用單參構造函數
- 拷貝初始化: A a2{a1}; A a2 = a1;//調用拷貝構造函數
不過,值得注意的是,當用於初始化內置類型的變量時,若大括號中的值有丟失信息的風險,編譯器會給出報錯:
int a{1.5}; //編譯不通過
int b(1.5); //編譯通過,轉換成1
除了用於上述的初始化,大括號還可用於返回值(返回初始化列表)、初始化容器(前提是容器實現了以initialize_list爲參數的構造函數)
vector<string> strArr{"hello", "how", "are", "you"}
vector<string> func(){
return {"fine", "thanks"};
}
爽爆了有木有。
初始化方面,除了大括號這一利器,C++11還實現了委託構造函數,能夠實現構造函數的代碼複用:
class Demo{
public:
Demo(int _x, int _y):a{_x},b{_y}{} //大括號也可用與類的初始化列表
Demo():Demo(0,0){} //Demo()將構造操作委託給Demo(int,int)
Demo(int _x):Demo(_x, 0){}
private:
int a;
int b;
}
在C++11的委託構造函數之前,假如多個構造函數有一些相同的構造動作,一般都是獨立出一個init私有函數,現在有了委託構造函數,代碼更加直觀、優雅。
類內初始值也是一個令人欣喜的特性,C++11之前,成員變量在類內聲明時不能指定初始值——要放到類外進行定義,C++11的類內初始化則極大地方便了成員變量的初始值的指定:
class CC{
public:
CC() {}
~CC() {}
private:
int a{7}; // 類內初始化,C++11 可用
const int b{8}; // 類內初始化,C++11 可用
static int c{1}; // 不支持static的成員指定類內初始化值
static const int d{4}; // 但支持static const的成員指定類內初始化值
};
int CC::c{2}; // c依然需要像C++98一樣初始化。
2.2 右值引用
右值引用牽出了很多騷操作——引用摺疊、通用引用、移動語意(std::move)、完美轉發(std::forward)、移動構造函數、移動賦值操作符等,在這幾個特性的加持下,舊的C++代碼無需改動、只升級編譯器版本、升級STL庫即可大幅提升性能。
引用摺疊規則是通用引用(universal reference)的基礎,而通用引用則是STL實現move與forward等庫函數的基礎,這篇博客有介紹右值引用、move與forward的使用,這篇則介紹了引用摺疊與通用引用以及move與forward的的實現,本篇博文不再重複。
2.3 lambda表達式
lambda表達式代表一個可調用的代碼單元,可以將其理解爲一個匿名的內聯函數,特別的是,lambda可以定義在函數內部——又爽爆了有木有:-)。它有參數列表、返回值、函數體,這些都跟普通函數很像,但是lambda多了一個捕獲列表:
int func(){
int data{0};
auto myFuncA = [data](int a)->int{...};
auto myFuncB = []{...}; //參數列表跟返回值列表都是可省略的
}
中括號裏的內容便是捕獲列表,func定義的局部變量只有在捕獲列表中聲明瞭才能在一個lambda中使用:myFuncA 內部可以使用data但myFuncB不能,全局變量則可以在lambda中自由使用9。捕獲列表有以下幾種形式:
- [] : 空捕獲列表
- ]:按值或按引用捕獲
- [=] : 隱式捕獲列表,按值捕獲所有局部變量
- [&] : 隱式捕獲列表,按引用捕獲所有局部變量
- [&, a, b…] : a, b…按值捕獲,剩下的局部變量按引用捕獲
- [=, a, b…] : a, b…按引用捕獲,剩下的局部變量按值捕獲
2.4 尾置返回類型
C/C++裏函數的返回值都是放在函數名的前面的——前置的。當返回值的類型很複雜的時候,函數的原型就顯得十分不易閱讀,例如一個返回數組指針的函數:
int (*func(int param))[10]{
.....
}
這種形式的函數聲明看上去非常繞——得琢磨一會才能分析出參數、返回值等信息,尾置返回類型在此時就非常有優勢了:
auto func(int param) -> int (*) [10]{
......
}
跟上一節的lambda的定義方式很相似——都是把返回值放在"->"後面
2.6 範圍for
C++11的諸多特性使得C++代碼跟之前的代碼大不一樣了,滿眼的auto、T&&。。。還有範圍for。在C++11之前,我們遍歷一個普通數組、一個vector,要這樣子:
int arr[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
for (int i = 0; i < 10; i++)
cout << arr[i];
std::vector<int> vec;
......
for (std::vector<int>::iterator itr = vec.begin(); itr != vec.end(); itr++)
std::cout << *itr;
有了範圍for,就可以這樣子:
int arr[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
for (auto n : arr)
std::cout << n;
std::vector<int> vec {1,2,3,4,5,6,7,8,9,10};
for (auto n :vec)
std::cout << n;
又雙爽翻了!!!少敲N多代碼!!!不過在範圍for中,那些會使迭代器失效的操作要謹慎使用(老版本for遍歷時也有同樣問題)。
2.7 using、extern
using關鍵字存在很久了,最常見的“using namespace std;”。在C++11裏它被額外賦予了以下幾種作用:
類型別名 。早在C時代,就可以用#define、typedef來定義別名,不過二者使用起來讓人很彆扭:經常分不清那個新名稱哪個是已有的名稱。使用C++11的using,將新名稱放在了等號的左邊、已有的名稱放在右邊,則十分清晰、直觀、易讀。另外,using還可以用於模板的別名,typedef則做不到這點。
#define MyIntA int
typedef int MyIntB;
using MyIntC = int; //C++11
template<typename T>class TClass{ .......};
using TClass_Int = TClass<int>;
using的另一個新功能: 更改父類成員的可見性 。Drive通過私有繼承Base,原本Base中的a、b在子類中都會變成private,但是使用using可以更改二者的可見性:
class Base{
public:
int a;
protected:
int b;
private:
int c;
}
class Drive:private Base{
public:
using Base::a;//OK
protected:
using Base::b;//OK
using Base::c;//編譯出錯,無法訪問父類的私有成員,因而無法更改可見性
}
繼承父類構造函數也是C++11賦予using的新功能。子類可以繼承自己的直接父類的所有構造函數:
extern也是很早就有的關鍵字了,常見的用法有以下幾種:
- 具有外部連接的靜態存儲期說明。 即修飾全局變量、函數,告訴編譯器當前符號在本模塊中沒有定義但在別處有定義,編譯通過後鏈接器會找到該符號並正確鏈接。
- 語言連接說明。extern “C” {…}。告訴編譯器對當前函數禁止使用C++的函數名修飾。
- 顯示模板實例化聲明。 //C++11
在C++11中,他有了新的功能:顯示模板實例化聲明。
C++模板在使用時纔會被實例化,這意味着同一個模板可能在不同模塊中被實例化多次——有可能引起代碼膨脹,並拖慢編譯速度。使用顯示模板實例化聲明,告訴編譯器,該模板不必在當前模塊進行實例化,會在鏈接期間鏈接其他模塊實例化的該模板,如果沒有任何模塊實例化該模板,則鏈接失敗。這一特性大大加快了大型模板項目的編譯過程。
2.8 可變參數模板、sizeof…
可變參數模板應該是那些標準庫實現者的福音了,大大增強了模板編程的靈活性。先看下可變參數模板這一神奇特性的使用:
// Example program
#include <iostream>
#include <string>
template<typename... Ts>
int countArgs(const Ts&... args){
return sizeof...(args);
}
template<typename T>
T max(const T& a,const T& b){
return b > a ? b : a;
}
template<typename T, typename... RestT>
T max(const T& a, const RestT&... restArgs){
T temp = max(restArgs...);
return temp > a ? temp : a;
}
template<typename T>
void func(const T& t){
std::cout<<t;
}
template<typename T, typename... RestT>
void func(const T& t, const RestT&... restArgs){
std::cout<<t<<std::endl;
func(restArgs...);
}
int main(){
func(1, 'a', "hahah");
std::cout<<std::endl;
std::cout<<max(1,5,3,2,8,6,9,7,3)<<std::endl;
std::cout<<countArgs(1,"g", 0.0009);
}