C++11新特性集錦——新語意

本文是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);
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章