C++ 11中引入了許多簡化編程工作的語法上的新特性,我們暫且美其名曰“語法甜點”。下面一一進行介紹。
語法甜點1:序列for循環
序列for循環是一種簡化的for循環,可用於遍歷一組序列,包括各種容器、string、數組、初始化列表以及由begin和end函數定義的序列。示例代碼如下:
2 for (auto a : vctTemp)
3 {
4 cout << a << endl;
5 }
語法甜點2:委託構造函數
在引入C++ 11之前,如果某個類有多個重載的構造函數,且這些構造函數中有一些共同的初始化邏輯,通常都需要再編寫一個帶參數的初始化函數,然後在這些構造函數中調用這個初始化函數。在C++ 11中,再也不用這麼麻煩了。我們可以實現一個最基礎的構造函數,其他構造函數都調用這個構造函數。示例代碼如下:
2 {
3 public:
4 CPerson() : CPerson(0, "") { NULL; }
5 CPerson(int nAge) : CPerson(nAge, "") { NULL; }
6 CPerson(int nAge, const string &strName)
7 {
8 stringstream ss;
9 ss << strName << "is " << nAge << "years old.";
10 m_strInfo = ss.str();
11 }
12
13 private:
14 string m_strInfo;
15 };
語法甜點3:統一的初始化語法
在引入C++ 11之前,有各種不同的初始化語法。在C++ 11中,仍可以使用這些初始化語法,但也可以選擇使用新引入的統一的初始化語法。統一的初始化語法用一對大括號{}表示,使用{}初始化語法還可有效地避免窄轉換。示例代碼如下:
2 char c{'X'};
3 int p[5] = {1, 2,3, 4, 5};
4 vector<int> vctTemp{1, 2, 3};
5 CPerson person{10, "Mike"};
6 int b = 5.3; // b賦值成5,發生了窄轉換
7 int d{5.3}; // 會提示編譯錯誤,避免了窄轉換
語法甜點4:nullptr
nullptr是C++ 11中新加的一個關鍵字,用於標識空指針。引入nullptr後,可以解決某些函數重載時的二義性問題。示例代碼如下:
2 {
3 cout << a << endl;
4 }
5
6 void F(char *p)
7 {
8 assert(p != NULL);
9
10 cout << p << endl;
11 }
12
13 int main(int argc, _TCHAR* argv[])
14 {
15 int *p = nullptr;
16 int *q = NULL;
17 bool bEqual = (p == q); // 兩個指針值是相等的,bEqual爲true
18 int a = nullptr; // 編譯失敗,nullptr不是轉換爲int
19
20 F(0); // 在C++ 98中編譯失敗,有二義性;在C++ 11中調用F(int)
21 F(nullptr); // 調用F(char *)
22
23 getchar();
24 return 0;
25 }
語法甜點5:成員變量初始化
與Java和C#中的用法一樣,可以對成員變量進行就地初始化。示例代碼如下:
2 {
3 private:
4 int m_nAge = 10;
5 string m_strName = "Mike";
6 };
語法甜點6:默認或禁用函數
當我們定義了自己的帶參數的構造函數時,編譯器將不再生成默認的構造函數,如果此時想使用默認的構造函數,則必須顯式地聲明並定義不帶參數的構造函數。在C++ 11中,我們可以使用default關鍵字來表明我們希望使用默認的構造函數。類似的,當我們不想外部使用編譯器自動生成的構造函數或賦值函數時,我們一般需要將其聲明成protected或private的。在C++ 11中,我們可以使用delete關鍵字來表明我們不希望編譯器生成默認的構造函數或賦值函數。示例代碼如下:
2 {
3 public:
4 CPerson() = default;
5 CPerson(const CPerson &person) = delete;
6 };
語法甜點7:繼承的構造函數
當一個派生類的某個函數隱藏了基類中的某個同名函數時,如果我們想在派生類中導出基類中的這個同名函數,可以通過using Base::Func的方式將基類中的這個同名函數引入到派生類的作用域內。當該方法只對普通成員函數有效,不能用於構造函數。在C++ 11中,如果派生類認爲基類的構造函數已經足夠,則也可以使用using Base::Base的方式將基類的構造函數引入到派生類的作用域內。但需要注意的是,此時派生類中的成員變量並沒有進行初始化,所以應當對這些成員變量進行就地初始化。示例代碼如下:
2 {
3 };
4
5 class CDerived : public CBase
6 {
7 public:
8 using CBase::CBase;
9 CDerived(int nData) : m_nData(nData) { NULL; }
10
11 private:
12 int m_nData = 10;
13 };
語法甜點8:模板右邊雙括號
在C++ 98中,vector<vector<int>> vctTemp是一個非法的表達式,編譯器會認爲右邊的>>是一個移位操作符,因此必須修改爲vector<vector<int> > vctTemp,即在右邊的兩個>中間添加一個空格。在C++ 11中,這將不再是一個問題,編譯器將能夠識別出右邊的雙括號是兩個模板參數列表的結尾。
語法甜點9:static_assert
靜態斷言static_assert由一個常量表達式和一個字符串構成。在編譯期間,將計算常量表達式的值,如果爲false,字符串將作爲錯誤信息輸出。示例代碼如下:
2 static_assert(sizeof(a)==4, "a is not an integer.");
語法甜點10:初始化列表
在引入C++ 11之前,只有數組能使用初始化列表。在C++ 11中,vector、list等各種容器以及string都可以使用初始化列表了。初始化列表對應的類爲initializer_list,vector、list等各種容器以及string之所以可以使用初始化列表,是因爲它們重載了參數類型爲initializer_list的構造函數(稱爲初始化列表構造函數)和賦值函數(稱爲初始化列表賦值函數)。下面是一些使用初始化列表的例子。
2 {
3 for (auto a : ilData)
4 {
5 cout << a << endl;
6 }
7 }
8
9 int main(int argc, _TCHAR* argv[])
10 {
11 vector<int> vctNum = {1, 2, 3, 4, 5};
12 map<string, string> mapID2Name = {{"92001", "Jack"}, {"92002", "Mike"}};
13 string strText{"hello world"};
14 Print({});
15 Print({1, 2});
16 Print({1, 2, 3, 4, 5});
17
18 getchar();
19 return 0;
20 }