c++ 11 常用語法

1 新類型

C++ 11新增了long long和unsigned long long,以支持64bit寬度; 

新增char16_t和char32_t以支持16位和32位字符表示; 

增加了“原始”字符串。

 

2 初始化

C++ 11擴展了大括號{}的適用範圍,既可以用於基本類型,也可以用於自定義類型:

int x = {5};

couble y{3.5};

short quar[5]{1, 2, 3, 4, 5};

int* p = new int[5]{1, 2, 3, 4, 5};

 

創建對象時,也可以使用大括號鏈表來調用構造函數:

class Stump

{

Public:

    Stump(int r, double w) : roots(r), weight(w){}

Private:

    int roots;

    double weight;

};

 

Stump s1(3, 4.5); // old style

Stump s2{3, 4.5}; // new style

Stump s3 = {3, 4.5};     // new style

 

注意:使用初始化列表可以防止向下轉型,如:

char c1 = 1.57e27;   // double-to-char, undefined behavior

char c1{1.57e27}; // double-to-char, compile error

 

std::initializer_list可以作爲函數參數,如果一個類中的構造函數使用了這種方法,則其他函數將不能再使用其作爲參數,這是爲了防止調用二義性。

Initializer_list模板類提供了兩個成員函數begin()和end(),用來指定列表的範圍,如:

double sum(std::initializer_list<double> il)

{

    double total = 0;

    for(auto p = il.begin(); p != il.end(); p++)

       total += *p;

    return total;

}

double total = sum({1, 2.5, 8.3, 2.2, 1.0});

 

3 聲明

C++提供了多種簡化聲明的方式,尤其在使用模板時更加方便。

 

1 auto:用來實現自動類型推斷,如:

auto i = 112;     // i is type int

auto pt = &i;     // pt is type int*

double fm(double, int);

auto pf = fm;     // pf if type double(*)(double, int);

 

用於模板時形式簡潔,如:

for(std::initializer_list<double>::iterator p = il.begin(); p != il.end(); p++)

改寫爲:

for(auto p = il.begin(); p != il.end(); p++)

 

2 decltype:將變量的類型指定爲表達式的類型。

decltyte (x) y; 將y設爲與x相同的類型,其中x是一個表達式。

 

3 返回類型後置:在函數名和參數列表後面指定返回類型。

double f1(double int);        // return double type

auto f2(double, int) -> double;   // new syntax, return double type

如果結合上模板表示返回類型,那就更好了,如下:

template<typename T, typename U>

auto eff(T t, U u) -> decltype(T * U)

{

}

 

4 模板別名:using =

C++中創建別名一般用typedef,如:

typedef std::vector<std::string>::iterator itType;

還可以這樣做:

using itType = std::vector<std::string>::iterator;

 

二者的差別在於,using可以使模板具體化,如:

using arr = std::array<T, 12>;,此時typedef不行。

 

5 nullptr:空指針

之前,C++使用0表示空指針,同樣的0既可以表示整型,又可以表示空指針,比較混亂;新增的nullptr是指針類型,不能轉換爲整型。爲了兼容性,C++目前仍然允許0表示空指針,即nullptr == 0的結果爲true。

 

4 智能指針

C++ 11摒棄了auto_ptr,新增了三種:unique_ptr、shared_ptr、weak_ptr。

 

5 異常

之前C++的語法中可以指出函數可能引發哪些異常,如:

void f1(int) throw(bad_alloc);  // 可能拋出bad_alloc異常

void f2(long long) throw();     // 不拋異常

 

C++摒棄了異常規範,新增瞭如下規則:

Void f3(short, short) noexcept; // 不拋異常

 

6 作用域內枚舉

傳統的C++枚舉的作用域在所屬的域內,就是說同一作用域內不能出現兩個同名的枚舉變量。

C++ 11新增了一種枚舉,使用class或者struct定義:

enum Old{yes, no};   // old style

enum class New{yes, no}; // new style

enum struct New{yes, no};   // new style

由於允許同名存在,因此引用時需要使用枚舉名限定:New::yes

 

7 對類的修改

在擴展類的設計方面,C++ 11很多改進,比如允許構造函數被繼承、彼此調用、移動構造函數、移動賦值運算符等。

7.1 顯示轉換運算符

早期的C++會導致自動類型轉換,比如:

class Plebe

{

    Plebe(int);

    explicit Plebe(double);

    ...

};

 

Plebe a, b;

a = 5;        // 發生隱式類型轉換,實則調用Plebe(5);

b = 0.5;      // 不允許

b = Plebe(0.5);   // 允許

 

C++ 11擴展了explicit,使得可以如下這樣做:主要是針對轉換函數:

比如:

operator int() const;

explicit operator double() const;

 

int n = a;    // allow

double x = b;     // not allow

x = double(b);    // allow

 

7.2 類內成員初始化

class Session

{

    int mem = 10;

    double mem2{2.35};

    ...

};

 

8 模板和STL方面

爲了改善模板和標準化方面的易用性,C++ 11做了多個改進:

8.1 改進的for循環

double prices[5] = {1, 2, 3, 4, 5};

for(double x : prices)

for(auto x : prices)

 

如果要在循環中修改數組或容器中的每個元素,可以使用引用:

for(auto& x : prices)

 

8.2 新增的STL容器

C++ 11新增了forward_list、unordered_map、unordered_multimap、unordered_set、unordered_multiset。

新增了模板array,實例化時指定元素類型和個數:std::array<int, 10> ar; // 10個int

 

8.3 新增STL方法

cbegin()、cend()、crbegin()、crend()。這幾個方法將容器元素視爲const。

 

8.4 valarray升級版

對於valarray模板,C++ 11新增了兩個方法begin()和end()。

 

8.5 摒棄了export

C++ 98增加的關鍵字export可以讓程序員將模板定義放在接口文件中,實現文件中放置方法體,但是實踐證明這一點也不現實,因此C++ 11把它摒棄了。

 

8.6 尖括號

舊時的C++要求在定義嵌套的模板時,兩個尖括號之間必須有空格,比如:vector<list<int> >,但是C++ 11中不再需要這樣了,比如vector<list<int>>也可以通過的。

 

9 右值引用

C++新增了右值引用,使用&&表示。【相對於左值,右值表示字面常量、表達式、函數的非引用返回值等】

如:

int&& r1 = 12;

int x = 5;

int y = 8;

int&& r2 = x + y;

 

我們可以通過r1來修改12,很方便。

 

10 移動語義和右值引用

10.1 爲何需要移動語義

移動語義就是爲了避免多餘的複製工作,就是說與其複製,還不如將源地址傳給使用者,因爲有時複製工作確是沒什麼用。

要實現移動語義,需要採用某種措施讓編譯器知道到底什麼情況下需要複製,什麼情況下不必複製。這時,右值引用就可以配上用場了。

傳統的複製構造函數執行深複製,並且不改變實參,因此,參數爲const類型;

移動構造函數只是更改了引用記錄,並且可能會改變實參,因此,參數必須是非const類型;

 

如下實例:

class Use

{

public:

    Use();

    Use(const Use& f);   // copy constructor

    Use(Use&& f);        // move constructor

    ...

private:

int n;

char* pc;

};

...

 

Use::Use(const Use& f) : n(f.n) // 深複製

{

    pc = new char[n];

    for(int i = 0; i < n; i++)

       pc[i] = f.pc[i];

}

 

Use::Use(Use&& f) : n(f.n)

{

    pc = f.pc;    // 移動構造,轉移控制權

    f.pc = nullptr;

    f.n = 0;

}

 

10.2 移動構造解析

雖然移動構造定義好了,但是如何調用呢,也就是說什麼情況下才會發生移動構造呢?

必須使用右值引用調用

Use two = one;           // match Use::Use(const Use&);

Use four(one + three);  // match Use::Use(Use&&);

因爲one是左值,而one + three是右值。

 

移動賦值情況類似,不記錄了。

 

10.3 強制移動

移動構造和移動賦值使用右值引用,如果需要讓他們操作左值呢?

答案是使用static_cast<>將對象強制轉換爲Use&&即可。不過在C++ 11種提供了更便捷的操作,在頭文件utility中聲明的函數std::move(...)。

如果使用了std::move()函數調用,但是類中卻沒有定義移動相關函數,那麼編譯器會調用傳統版本的移動構造函數和移動賦值操作符。

 

11 新的類功能

假設你爲類定義了構造函數,那麼類就不會自動提供默認的構造函數了,然而,如果你仍然想使用類提供的默認版本,那麼可以使用default關鍵字:

class Some

{

public:

    Some(Some&&);

    Some() = default; // use default constructor

    ...

};

 

相反地,如果要禁用編譯器提供的默認函數,可以使用delete:

class Some

{

public:

    Some(Some&&);

    Some() = default; // use default constructor

    Some(const Some&) = delete; //disable copy constructor

    ...

};

 

當然要想禁用某個編譯器提供的函數也可以顯式聲明爲private,但是使用delete更方便且不易出錯。

 

注意:default關鍵字只能用於6個特殊函數,而delete卻能夠用於任何成員函數。

 

委託構造

如果一個類包含多個構造函數,C++ 11允許在一個構造函數中的定義中使用另一個構造函數,但這必須通過初始化列表進行操作,如下:

class Notes

{

    int k; double x; string s;

public:

    Notes(int kk, double xx, string ss) : k(kk), x(xx), s(ss){ }

    Notes(int kk) : Notes(0, 0.5, “benxin”){ k = kk; }

    ...

};

 

繼承構造

C++ 11允許派生類繼承基類的構造函數。C++ 98提供了一種讓某個名稱空間中的同名重載函數都可用的語法,如下:

namespace Box

{

    int fn(){}

    int fn(int){}

    int fn(double){}

}

using Box::fn;該語句使得fn的所有重載版本都可用。我們一般使用這種方法在派生類中調用基類的同名函數。之所以提供這種語法,就是因爲覆蓋是以函數名爲基礎的,不論參數是否對應都將被覆蓋。

C++ 11將這種語法用於構造函數中,使得派生類將繼承基類的構造函數(默認構造函數、複製構造函數、移動構造函數除外)。

 

管理虛方法:override和final

傳統的虛函數是爲了實現多態調用,但這必須是派生類與基類的虛函數簽名完全一致的情況下才會發生多態,如果不一致,假設如下:

複製代碼
 1 class Base
 2 {
 3 public:
 4     virtual void Fn(){ cout << "Base::Fn()" << endl; }
 5 };
 6 
 7 class Derived : public Base
 8 {
 9 public:
10     virtual void Fn(int){ cout << "Derived::Fn()" << endl; }
11 };
12 
13 int main()
14 {
15     Base* d = new Derived;
16     d->Fn();
17 
18     return 0;
19 }

複製代碼

結果如上,編譯器竟然會根據指針的靜態類型發生了調用,並無多態發生。

 

好在C++ 11提供了override指出了該虛函數是爲了覆蓋基類的虛函數而存在的,此時如果不小心與基類中的虛函數不一致了,那麼編譯器不會讓你通過的。

複製代碼
1 class Derived : public Base
2 {
3 public:
4     virtual void Fn(int) override{ cout << "Derived::Fn()" << endl; }
5 };
複製代碼

 

如果想禁止派生類覆蓋基類的虛函數,可在基類的虛函數參數列表後加上final。

複製代碼
 1 class Base
 2 {
 3 public:
 4     virtual void Fn() final{ cout << "Base::Fn()" << endl; }
 5 };
 6 
 7 class Derived : public Base
 8 {
 9 public:
10     virtual void Fn() override{ cout << "Derived::Fn()" << endl; }
11 };

複製代碼

最後需要指出的是:override和final並非關鍵字,而是具有特殊含義的標識符,編譯器會根據上下文確定其到底是否代表特殊性。

 

12 Lambda函數

如下示例使用了三種方法給STL算法傳遞信息:函數指針、函數符、lambda函數。出於方便性,我們將其統稱爲函數對象。

 

假設生成一個包含1000個數的容器,並判斷其中有多少個可以被3整除,有多少個可以被13整除:

vector<int> numbers(1000);

generate(numbers.begin(), numbers.end(), rand);

說明:generate函數前兩個參數指定容器區間,並將每個元素設置爲第三個參數返回的值。

 

bool f3(int x){ return x % 3 == 0; }

bool f13(int x){ return x % 13 == 0; }

 

接下來可以藉助於count_if函數,該函數前兩個參數也是指定區間,第三個參數是一個返回true或false的函數對象,該函數計算使得返回true的元素個數。

int count = count_if(numbers.begin(), numbers.end(), f3);

如上述所示,rand和f3都是作爲函數指針傳遞過去的。

 

下面看一下函數符如何使用:

我們知道函數符是一個類對象,主要利用重載()操作符來完成任務:

class f_mod

{

public:

    f_mod(int d = 1) : dv(d){ }

    bool operator()(int x){ return x % dv == 0; }

 

private:

    int dv;

};

 

創建對象:

f_mod obj(3);       // set dv to 3

使用:

bool rv = obj(7);   // same as obj.operator()(7);

                         // return 7 % 3 == 0

因此:

int count = count_if(numbers.begin(), numbers.end(), f_mod(3));

 

最後看看如何使用lambda:C++ 11中,對於接受函數指針或函數符的函數,也可以使用匿名函數定義(lambda)作爲其參數:

bool f3(int x){ return x % 3 == 0; }

上述函數f3用lambda來表示即爲:

[](int x){ return x % 3 == 0; }

 

lambda和一般函數的區別如下:

使用[]代替函數名;沒有返回類型。返回類型相當於使用decltype推斷而得。如果表達式中不包括返回語句,則推斷出類型爲void但要特別注意,僅當lambda表達式中僅包含一條返回語句時,自動推斷類型才起作用;否則,需要使用新增的返回類型後置語法,如下:

[](double x) -> double { int y = x; return x – y; }

int count = count_if(numbers.begin(), numbers.end(), [](int x){ return x % 3 == 0; });

 

我們也可以給lambda指定一個名稱,如:

auto mod3 = [](int x){ return x % 3 == 0; }

int count = count_if(numbers.begin(), numbers.end(), mod3);

 

當然,我們也可以像普通函數一樣使用lambda,如:mod3(z); ---> return z % 3 == 0;

 

還有,lambda還可以操作變量,如:

[count]:表示以傳值方式訪問變量;

[&count]:表示以引用方式訪問變量;

[count1, &count2]:以傳值方式訪問count1,以引用方式訪問count2;

[&, count]:表示以傳值方式訪問count,以引用方式訪問其他變量;

[=]:表示以傳值方式訪問所有變量;

[&]:表示以引用方式訪問所有變量;

複製代碼
1   int count3 = 0;
2     int count13 = 0;
3     vector<int> numbers(100);
4     generate(numbers.begin(), numbers.end(), rand);
5     for_each(numbers.begin(), numbers.end(), [&](int x){ count3 += x % 3 ==         0;  count13 += x % 13 == 0;});
6 
7   cout << count3 << "     " << count13 << endl;

複製代碼

注意:

C++引入lambda的主要目的是爲了簡化程序的編寫,典型的lambda是測試表達式或者比較表達式,因爲它們一般只有一條返回語句,因此可以使用返回類型自動推斷。

 

13 可變參數模板

C++ 11提供了一個用省略號表示的元運算符,使得可以聲明表示模板參數列表,其語法如下:

template<typename... Args>

void show(Args... args){ }

其中Args表示模板參數列表,args表示函數參數列表。

 

這裏有一個問題,我們如何訪問具體的某個參數呢?使用args[2]嗎?答案是否定的,我們在可變參數中不能使用索引來訪問,這裏需要遞歸地訪問每個參數:

template<typename T, typename... Args>

void show(T value, Args... args)

{

    cout << value << “, ”;

    show(args...);

}

每次調用show,可變參數將減少一個,直到調用show()時,調用參數爲空,不接受,則退出遞歸。

 

14 斷言

C++提供了調試工具assert,這是一個宏,用於在運行階段對斷言進行檢查,如果爲true,則顯示一條消息,否則調用abort();

C++ 11新增了關鍵字static_assert,可用於在編譯階段對斷言進行測試。

 

發佈了30 篇原創文章 · 獲贊 67 · 訪問量 21萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章