1. 程式人生 > >C++11/14新特性

C++11/14新特性

C++11是自C++98十餘年來發布的一個新特性,擴充了很多C++的功能和特性,而C++14是對C++11的又一次補充和優化,這些新特性使得C++更貼近於一種現代化的變成語言。gcc版本大於5(clang版本大於3.8)已經全面支援C++14,並且在編譯時需要開啟-std=c++14選項用來支援c++14的特性,推薦在程式碼中直接使用c++14特性而不是c++11。

型別推導和判斷

型別推導

auto關鍵字早已存在於C++當中,假如一個變數型別不是register,那麼就是auto。但是隨著register的棄用(編譯器自動優化變數存放位置),auto的語意也顯得多此一舉了,因此c++11賦予關鍵字auto

新的語意實現自動型別推導,其推導階段在編譯期實現,而且由於編譯期間需要判斷左右值是否匹配,所以不會對編譯和執行速度帶來影響:

auto i = 1;         //OK
auto i;             //ERR,auto需要一個初始化值完成推導

int max(int, int)
auto fun = max;     //OK

auto str = "ABC";   //OK

int max(auto a, auto b);    //ERR,因為過載的原因,不能這麼使用

for (auto iter = vec.cbegin(); iter != vec.cend(); iter++){};  //OK
template <typename T1, typename T2, typename T3> auto add(T2 a, T3 b) { //僅在C++14中合法,c++11不支援 return a+b; }

型別判斷

型別判斷的引入主要是為了獲取變數的型別,使用decltype()可以在編譯期間獲取變數的型別:

auto a = 1;
auto b = 2;
decltype(a+b) c;            //ok

序列迭代

C++11引入了一種簡單的for語法用於快速迭代序列:

std::vector<int
>
a = {1, 2, 3, 4}; for (auto item : a) { std::cout << item << std::endl; }

初始化列表擴充套件

我們知道,c++可以使用{}實現對陣列、普通的結構體(沒有建構函式和解構函式)的初始化,但是初始化列表並不能對物件和函式使用,因此c++11使用std::initializer_list對這一特性進行了拓展:

#include <initializer_list>
class Test {
public:
    Test(std::initializer_list<int>){};
};

Test a = {1, 2, 3};     //初始化類

int fun(std::initializer_list<int> list) {};

fun({1, 2, 3});         //作為函式形參

其次,c++提供了統一的形式,完成對任意型別物件的初始化:

class Person {
public:
    Person(std::string _name, int _age, std::string _id): name(_name), age(_age), id(_id){};
private:
    std::string name;
    int age;
    std::string id;
};

struct Person_{
Person_(std::string _name, int _age, std::string _id): name(_name), age(_age), id(_id){};
private:
    std::string name;
    int age;
    std::string id;
};
//統一的初始化語法
Person c_person {"xiaoming", 18, "1234567"};    
Person_ s_person {"xiaohong", 17, "7654321"};

類特性修改

類中預設函式行為

我們知道在沒有指定的情況下,c++會對類設定預設的建構函式、拷貝建構函式、賦值函式以及解構函式,但是有時候我們並不需要這些預設函式,因此在C++11中引入了對這些特性進行精確控制的特性:default指定生成預設函式,delete指定禁用預設函式。如果禁用了預設的建構函式和解構函式,必須指定一個自定義的函式。

class Test {
public:
    Test() = default;       //指定為Test類生成預設建構函式,如果設定為delete,就是禁用預設建構函式,如果禁用了
    ~Test() = default;      //預設解構函式
    Test(const Test&) = delete;    //禁用拷貝建構函式
    Test& operator=(const Test&) = delete;  //禁用類賦值函式
};

Test a;
Test b(a);      //error,因為已經被禁用
Test c = a;     //error,因為已經被禁用

建構函式特性

C++11提供了兩種新的建構函式特性,用於提升類構造的效率,分別是委託構造和繼承構造,前者主要用於多建構函式的情況,而後者用在類繼承方面:
- 委託構造
委託構造的本質為了簡化函式程式碼,做到複用其他建構函式程式碼的目的。

class Test {
public:
    Test() {
        a = 1;
    }
    Test(int _b) : Test() {
        b = _b;
    }

    int a,b;
};

Test t(2);    //會呼叫Test()將a賦值為1
  • 繼承構造
    c++在繼承的時候,需要將建構函式的引數逐個傳遞到積父類的建構函式中完成父類的構造,這種效率是很低下的,因此c++11引入了繼承構造的特性,使用using關鍵字:
class Test {
public:
    Test(int _a, int _b) : a(_a), b(_b) {};
    int a,b;
};

class Test2 : public Test {
    using Test::Test;
}

Test2 t(2, 3);      //會呼叫父類的建構函式

顯式控制虛擬函式過載

由於虛擬函式的特性,可能會被意外進行重寫,為了做到精確對虛擬函式過載的控制,c++11使用了overridefinal關鍵字完成對這一特性的實現,下面看例子:

class Test {
public:
    Test() {};
    virtual int fun(int);
};

class Test2 : public Test {
    using Test::Test;
    int fun(int) override;      //顯式宣告對虛擬函式進行過載
    int fun(float) override;    //錯誤,父類沒有這個虛擬函式
}

final關鍵字是為了顯式終結類的繼承和虛擬函式的過載使用:

class Test {
public:
    virtual int fun(int) final;
};

class Test2 final: public Test {
    int fun(int) override;     //非法,因為該虛擬函式已經設定為finale,禁止過載 
};

class Test3 : public Test2 {};  //非法,Test2已經設定為final,禁止作為父類

nullptrconstexpr

之所以引入nullptr是為了解決NULL的詬病,在之前的c++中,NULL可以被定義為int0或者一個0值的指標,這就帶來一個問題:

void fun(int);
void fun(void *);

fun(NULL);  //無法確定使用的是哪一個過載函式,需要視NULL的定義而定 

nullptr現在定義為一個空指標,避免了NULL帶來的問題。
constexpr定義了一個使用者顯式的宣告函式或物件建構函式在編譯期間會成為常數,從 C++14 開始,constexptr函式可以在內部使用區域性變數、迴圈和分支等簡單語句:

//這個函式會在編譯期間進行計算
constexpr int fun(const int n) {
    if (1 == n) return 1;
    else if (2 == n) return 1;
    else    return fun(n - 1) + fun(n - 2);
}

強列舉型別

c++11引入了enum class來保證列舉不會被隱式轉換:

enum class test : int {
    v1 = 0,
    v2 = 1
};

if (test::v1 == 0)      //錯誤,不能把test型別與int做隱式轉換

if (test::v1 == test(0))    //正確,顯示轉換後進行比較

模板增強

類型別名

使用using關鍵字更加直觀的定義別名:

typedef int (*fun)(int *);   //以前c++的做法,宣告一個引數為`int *`,返回值為int的函式指標,名字叫fun
using fun = int (*)(int *);  //c++11,這樣更加直觀

template <typename T>
using newType = std::pair<T, T>;

變長模板和預設模板引數

c++11可以在定義模板時,給與模板一個預設的型別,這樣可以在不設定型別的時候使用預設型別:

template <typename T = int, typename U = std::string>

同時c++11可以設定模板引數為任意個數:

template <typename T1, typename... TS>      //可以接受至少一個模板型別
template <typename... TS>                   //至少0個

Lamda表示式

lamda表示式的語法如下:

[捕獲列表] (函式引數) 異常屬性 -> 返回型別 {
    //函式體
}

捕獲列表是lamda表示式內部使用的外部引數列表。

標準庫擴充

c++11對多個標準庫進行了擴充,包括:
1. 新增加容器
- 陣列容器:std::array
- 單項鍊表容器:std::forward_list
- 無序容器:std::unordered_set
- 無序對映:std::unordered_map
- 元組:std::tuple
2. 正則表示式
3. 語言級的執行緒支援
4. 智慧指標和引用計數
5. 函式繫結和包裝