C++11新特性集錦——新關鍵字

C++11新引入的特性比較多,這裏簡單記錄下每個特性的大致情況,無法將細節一一描述清楚——那樣每個特性幾乎都要花費一篇博客的篇幅來記錄。本文從新增的關鍵字新增的語義新增的標準庫三個方面來記錄這些新特性。原本打算將這三部分放到一篇博客中,後來發現太長了,還是分開吧。

0,重點特性概覽

個人覺得,auto、統一的初始化(使用“{}”)、右值引用、lambda是C++11裏面重量級的四個特性了,auto簡化了各種標識符的聲明,大括號統一了初始化代碼的形式,右值引用作用下的移動語義大大改善了代碼性能,lambda表達式則令代碼更加簡潔,lambda定義在函數體內可以使代碼可讀性提高很多、封裝也更符合日常思維。當然其他特性諸如可變參數模板多線程庫等也值得大書特書,但由於個人水平有限,只能將諸多特性做一些淺顯的記錄,謂之“集錦”。

1,新增的關鍵字

1.1 auto、decltype

事實上,在C++98裏已經使用過auto關鍵字,用來表示一個變量擁有自動的生命週期,但是平時基本用不到,C++11標準就把舊版的這個作用廢除了,將auto用作自動類型推斷。
auto的引入大大減少了代碼的長度——讓編譯器自己推斷標識符的類型而不是人工指定,大多數情況下,編譯器是清楚地知道標識符的具體類型的。

vector<int> arr;
......
vector<int>::iterator it = arr.begin();
auto it2 = arr.begin();//比上面的代碼簡直爽太多了。

不過,auto的引入也有小小的不利——犧牲了代碼的一部分可讀性,例如:

auto value = myFunction();

編譯器會清楚地知道value的類型,但程序員不一定——除非看一下myFunction的函數聲明。但總的來說,利是遠遠大於弊的。
當我們用一個值或者表達式來初始化一個變量時,可以用auto聲明該變量,編譯器會自動推斷出它的類型,但有些時候,我們不想用一個值或者表達式初始化變量——只想用值的類型來聲明一個變量,此時就可以用decltype關鍵字了:

//value的類型是myFunction的返回值的類型,此處並不會真正執行myFunction
decltype(myFunction()) value;

值得注意的是,decltype推斷的結果與表達式的形式密切相關:假如表達式外層有一層或多層小括號,得到的將是引用類型:

int a = 10;
decltype(a) b; //正確,b的類型是int,b未被初始化。
decltype((a)) c; //錯誤,c的類型是int&,必須被初始化。

1.2 =default、=delete、override、final

這幾個新增的關鍵字用與類的設計。=default、=delete兩個用於修飾那些編譯器會自動生成的函數(例如構造函數與賦值操作符重載函數),=default聲明使用編譯器生成的,=delete則聲明禁止編譯器自動生成。例如:

class Demo{
   public:
    Demo(const int a):m_a{a}{}
   private:
    int m_a;
};

此時編譯器將不再爲Demo生成無參構造函數,若想使用編譯器自動生成的無參構造函數,可以這樣:

class Demo{
public:
    Demo() =default;
    Demo(const int a):m_a{a}{}
private:
    int m_a;
};

像下面這樣便可禁止Demo2的拷貝構造:

class Demo2{
public:
    Demo(const Demo& obj) =delete;
private:
    int m_a;
};

overridefinal則用於繼承控制,override告訴編譯器子類要重寫父類的虛函數,並確保在子類中聲明的override函數跟基類的虛函數有相同的簽名,防止了想重寫父類虛函數卻不小心搞錯了參數列表而重寫無效的尷尬。final則有兩個作用:用來修飾一個類,則這個類不能再被繼承;用來修飾一個虛函數,則該虛函數無法被子類重寫。

class FinalBase final{}
class A:public FinalBase{} //錯誤:FinalBase不能被繼承
class B{
public:
	virtual int funcA(int x){}
	virtual int funcB(int x) final{}
}
class C:public B{
	public:
	virtual int funcA(int x) override{...} //Ok, 重寫funcA
	virtual int funcB(int x) {...}			//錯誤:funcB已經被聲明爲final
}

1.3 nullprt

引入了nullptr之後,應該摒棄使用NULL或者0來置空一個指針,NULL 與 0本質上都是整型,而不是指針類型。nullptr使類型檢查更加嚴謹。考慮如下代碼:

void f(void*){...}
void f(int){...}
int main(){
    f(NULL);//此調用存在二義性
    f(nullptr);//OK,調用void f(void*)
}

1.4 constexpr

這個關鍵字與const非常像,讓人傻傻分不清楚。const可用來表示編譯期常量也可以用來表示運行期常量,const更側重於“只讀”這個特點。而constexpr則只用來表示編譯期常量,告訴編譯期可以在編譯期計算出該表達式的值,讓編譯器儘量優化。

int func(int p){
	const int a = 5;		//a是個編譯期常量
	const int b = p;		//b是個運行期常量
	constexpr int c = 6;	//c是個編譯期常量
	constexpr int c = p;	//編譯報錯
	std::array<int, a> arr1;//Ok
	std::array<int, b> arr2;//編譯報錯
	std::array<int, c> arr3;//Ok
}

另外,假如一個函數的返回值被constexpr修飾,則編譯器會在編譯期儘可能計算出函數調用的結果:

constexpr int calcLength(const int& x){
	return x*5;
}
int calcLength2(const int& x){
	return x*5;
}
int calcLength2(const int& x){
	return x*5;
}
std::array<int, calcLength(2)>  arr10;//OK,arr10是一個10個元素的數組,10這個值在編譯期就計算好了,直接替換掉calcLength(2)
std::array<int, calcLength2(3)> arr15;//編譯出錯calcLength2不是 constexpr
int a{5};
int b = calcLength(a);				  //OK, 調用calcLength(a)返回的不再是一個constexpr,因爲a是變量
constexpr c = calcLength(a);		  //編譯出錯

另外,這兩這個都可以修飾指針,但意義不同。const修飾指針即可用作底層const(修飾指向的內容)又可用作頂層const(修飾指針本身);而constexpr修飾指針時只修飾指針本身不能修飾指針指向的內容:

int x = 2;
const int *a;				//a指向一個int常量
int* const b = &x;			//b指向一個int變量,b初始化之後不能更改指向——是個指針常量
constexp int *c = nullptr;	//c是個指針常量——必須在定義時初始化,初始化的值必須在編譯期即可確定,且之後不能改變。

1.5 noexcept

該關鍵字告訴編譯器,函數中不會發生異常,這有利於編譯器對程序做更多的優化。

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