C++11新特性

1.引入 nullptr

如果 NULL 被定義爲 ((void*)0),C++ 不允許直接將 void * 隱式轉換到其他類型,那麼當編譯char *ch = NULL時,無法編譯通過,因此NULL一般被定義爲 0。

而這依然會產生問題,將導致了 C++ 中重載特性會發生混亂,爲了解決這個問題,引入nullptr。考慮以下:

void foo(char *);
void foo(int);

foo(NULL);     // 此處調用哪個函數呢?可能會違反代碼本意?

2. 新增類型推導關鍵字auto、​​decltype

  • 類型推導auto
vector<string> data;
auto iter = data.begin();
  • 模版函數中使用auto:
//傳統寫法
template <typename Product, typename Creator>  
void processProduct(const Creator& creator) {  
    Product* val = creator.makeObject();  
}         

//如果使用auto,則可以這樣寫: 可以減少 template 模板的使用 
template <typename Creator>  
void processProduct(const Creator& creator) {  
    auto val = creator.makeObject();  
 }
  •  decltype有點像auto的反函數,auto可以讓你聲明一個變量,而decltype則可以從一個變量或表達式中得到類型。
auto x = 1;
auto y = 2;
decltype(x+y) z;
  • 拖尾返回類型(auto 與 decltype 配合)
template<typename T, typename U>
auto add(T x, U y) -> decltype(x+y) {
    return x+y;
}

// 從 C++14 開始是可以直接讓普通函數具備返回值推導,因此下面的寫法變得合法:
template<typename T, typename U>
auto add(T x, U y) {
    return x+y;
}

3.區間遍歷

類似python遍歷方法,引入以下for循環語法:

map<string, int> m{{"a", 1}, {"b", 2}, {"c", 3}};  
for (auto p : m){  
    cout<<p.first<<" : "<<p.second<<endl;  

4. 初始化列表

//原來版本中,只能這樣初始化數組
int arr[3] = {1, 2, 3}  
vector<int> v(arr, arr + 3); 

//在C++11中,我們可以使用以下語法來進行替換:

int arr[3]{1, 2, 3};

map<int, string>{{1, "a"}, {2, "b"}};

string str{"Hello World"};

vector<int> iv{1, 2, 3}; 
vector<int> data_int = {1,2,3};
vector<string> data_string = {"1", "2", "3"};

5. 模板增強

  • 使用using關鍵字更加直觀的定義別名(typedef 可以爲類型定義一個新的名稱,但是卻沒有辦法爲模板定義一個新的名稱,因爲模板不是類型):
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個

6. 顯式缺省(=default)、顯式刪除(=delete)

我們知道在沒有指定的情況下,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,因爲已經被禁用

7.構造函數特性

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);      //會調用父類的構造函數

8.顯式控制虛函數重載

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

//override顯式聲明對虛函數進行重載

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,禁止作爲父類

9. Lambda 表達式

lambda表達式類似Javascript中的閉包,它可以用於創建並定義匿名的函數對象,以簡化編程工作。Lambda的語法如下:

  • []內的參數指的是Lambda表達式可以取得的全局變量。(1)函數中的b就是指函數可以得到在Lambda表達式外的全局變量,如果在[]中傳入=的話,即是可以取得所有的外部變量,如(2)和(3)Lambda表達式。
  • ()內的參數是每次調用函數時傳入的參數。
  • ->後加上的是Lambda表達式返回值的類型,如(3)中返回了一個int類型的變量。
vector<int> iv{5, 4, 3, 2, 1};  
int a = 2, b = 1;  

for_each(iv.begin(), iv.end(), [b](int &x){cout<<(x + b)<<endl;}); // (1)  
for_each(iv.begin(), iv.end(), [=](int &x){x *= (a + b);});     // (2)  
for_each(iv.begin(), iv.end(), [=](int &x)->int{return x * (a + b);});// (3) 


//lambda 函數  adder是函數
//int (* p)(int, int) = [](int p1, int p2) {return p1 + p2; };  以前的寫法,使用auto擴展性更強
auto adder = [](auto op1, auto op2) {return op1 + op2; };
cout << "int result:" << std::accumulate(data_int.begin(), data_int.end(), 0, adder) << endl;
cout << "string result:" << std::accumulate(data_string.begin(), data_string.end(), string(""), adder) << endl;
cout << "sum is:" << adder(1, 3) << endl;

10.強枚舉類型

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

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

if (test::v1 == 0)          //錯誤,不能把test類型與int做隱式轉換
if (test::v1 == test(0))    //正確,顯示轉換後進行比較

11. 新增容器

  • std::array

std::array 保存在棧內存中,相比堆內存中的 std::vector,我們能夠靈活的訪問這裏面的元素,從而獲得更高的性能。

std::array 會在編譯時創建一個固定大小的數組,std::array 不能夠被隱式的轉換成指針,使用 std::array只需指定其類型和大小即可:

std::array<int, 4> arr= {1,2,3,4};

int len = 4;
std::array<int, len> arr = {1,2,3,4}; // 非法, 數組大小參數必須是常量表達式

當我們開始用上了 std::array 時,難免會遇到要將其兼容 C 風格的接口,這裏有三種做法:

void foo(int *p, int len) {
    return;
}

std::array<int 4> arr = {1,2,3,4};

// C 風格接口傳參
// foo(arr, arr.size());           // 非法, 無法隱式轉換
foo(&arr[0], arr.size());
foo(arr.data(), arr.size());

// 使用 `std::sort`
std::sort(arr.begin(), arr.end());

  • std::forward_list

1、std::forward_list 是一個列表容器,使用方法和 std::list 基本類似。 
2、和 std::list 的雙向鏈表的實現不同,std::forward_list 使用單向鏈表進行實現,提供了 O(1) 複雜度的元素插入,不支持快速隨機訪問(這也是鏈表的特點),也是標準庫容器中唯一一個不提供 size() 方法的容器。當不需要雙向迭代時,具有比 std::list 更高的空間利用率。

  • 無序容器

C++11 引入了兩組無序容器: 
std::unordered_map/std::unordered_multimap 和 std::unordered_set/std::unordered_multiset。

無序容器中的元素是不進行排序的,內部通過 Hash 表實現,插入和搜索元素的平均複雜度爲 O(constant)。

  • 元組 std::tuple

元組的使用有三個核心的函數:

std::make_tuple: 構造元組 
std::get: 獲得元組某個位置的值 
std::tie: 元組拆包

#include <tuple>
#include <iostream>

auto get_student(int id)
{
    // 返回類型被推斷爲 std::tuple<double, char, std::string>
    if (id == 0)
        return std::make_tuple(3.8, 'A', "張三");
    if (id == 1)
        return std::make_tuple(2.9, 'C', "李四");
    if (id == 2)
        return std::make_tuple(1.7, 'D', "王五");
    return std::make_tuple(0.0, 'D', "null");   
    // 如果只寫 0 會出現推斷錯誤, 編譯失敗
}

int main()
{
    auto student = get_student(0);
    std::cout << "ID: 0, "
    << "GPA: " << std::get<0>(student) << ", "
    << "成績: " << std::get<1>(student) << ", "
    << "姓名: " << std::get<2>(student) << '\n';

    double gpa;
    char grade;
    std::string name;

    // 元組進行拆包
    std::tie(gpa, grade, name) = get_student(1);
    std::cout << "ID: 1, "
    << "GPA: " << gpa << ", "
    << "成績: " << grade << ", "
    << "姓名: " << name << '\n';

    //合併兩個元組,可以通過 std::tuple_cat 來實現。
    auto new_tuple = std::tuple_cat(get_student(1), std::move(t));
}

12. 正則表達式

考慮下面的正則表達式:

[a-z]+.txt: 在這個正則表達式中, [a-z] 表示匹配一個小寫字母, + 可以使前面的表達式匹配多次,因此 [a-z]+ 能夠匹配一個及以上小寫字母組成的字符串。在正則表達式中一個 . 表示匹配任意字符,而 . 轉義後則表示匹配字符 . ,最後的 txt 表示嚴格匹配 txt 這三個字母。因此這個正則表達式的所要匹配的內容就是“文件名爲純小寫字母的文本文件” 。

  • 使用形式1:
#include <iostream>
#include <string>
#include <regex>

int main() {
    std::string fnames[] = {"foo.txt", "bar.txt", "test", "a0.txt", "AAA.txt"};
    // 在 C++ 中 `\` 會被作爲字符串內的轉義符,爲使 `\.` 作爲正則表達式傳遞進去生效,需要對 `\` 進行二次轉義,從而有 `\\.`
    std::regex txt_regex("[a-z]+\\.txt");
    for (const auto &fname: fnames)
        std::cout << fname << ": " << std::regex_match(fname, txt_regex) << std::endl;
}
  • 使用形式2:
std::regex base_regex("([a-z]+)\\.txt");
std::smatch base_match;
for(const auto &fname: fnames) {
    if (std::regex_match(fname, base_match, base_regex)) {
        // sub_match 的第一個元素匹配整個字符串
        // sub_match 的第二個元素匹配了第一個括號表達式
        if (base_match.size() == 2) {
            std::string base = base_match[1].str();
            std::cout << "sub-match[0]: " << base_match[0].str() << std::endl;
            std::cout << fname << " sub-match[1]: " << base << std::endl;
        }
    }
}

13. 語言級線程支持

std::thread 
std::mutex/std::unique_lock 
std::future/std::packaged_task 
std::condition_variable

代碼編譯需要使用 -pthread 選項

14. 右值引用和move語義

  • 在 C++03 中的引用類型是隻綁定左值的,C++11 引入一個新的引用類型叫右值引用類型,它是綁定到右值的,如臨時對象或字面量,增加右值引用的主要原因是爲了實現 move 語義。與傳統的拷貝不同,move 的意思是目標對象“竊取”原對象的資源,並將源置於“空”狀態。當拷貝一個對象時,其實代價昂貴且無必要,move 操作就可以替代它。如在 string 交換的時候,使用 move 意義就有巨大的性能提升,如下所示:
//原方案很慢,因爲需要申請內存,然後拷貝字符;
void naiveswap(string &a, string & b)  
{  
    string temp = a;  
    a=b;  
    b=temp;  
}  
 
//使用move就只需要交換兩個數據成員,無須申請、釋放內存和拷貝字符數組;
void moveswapstr(string& empty, string & filled)  
{  
    //pseudo code, but you get the idea  
    size_t sz=empty.size();  
    const char *p= empty.data();  
    //move filled's resources to empty  
    empty.setsize(filled.size());  
    empty.setdata(filled.data());  
    //filled becomes empty  
    filled.setsize(sz);  
    filled.setdata(p);  
} 

要實現支持 move 的類,需要聲明move 構造函數和 move 賦值操作符,如下:

class Movable  
{  
    Movable (Movable&&); //move constructor  
    Movable&& operator=(Movable&&); //move assignment operator  
}; 
  • 轉移左值 

有時候,我們可能想轉移左值,也就是說,有時候我們想讓編譯器把左值當作右值對待,以便能使用轉移構造函數,即便這有點不安全。出於這個目的,C++ 11在標準庫的頭文件< utility >中提供了一個模板函數std::move。實際上,std::move僅僅是簡單地將左值轉換爲右值,它本身並沒有轉移任何東西。它僅僅是讓對象可以轉移。

unique_ptr<Shape> a(new Triangle);
unique_ptr<Shape> b(a);              // still an error
unique_ptr<Shape> c(std::move(a));   // okay

請注意,第三行之後,a不再擁有Triangle對象 

15.新的智能指針

C++98 定義的唯一的智能指針類 auto_ptr 已經被棄用,C++11 引入了新的智能針對類 shared_ptr 和 unique_ptr。它們都是標準庫的其它組件兼容,可以安全地把智能指針存入標準容器,也可以安全地用標準算法“倒騰”它們。

一旦你用 unique_ptr 關鍵字定義了一個對象,那麼下列事件只要發生一個,對象就會被銷燬並釋放內存:
    • unique_ptr 管理的對象被銷燬。
    • unique_ptr 管理的對象通過賦值操作符指向另一個指針,或調用了reset()方法。
對於不想了解太多細節的用戶來說,這就意味着如果你使用了 unique 指針的語義,那麼在跳出作用域之前,你就不用手動回收對象的內存了。
以前,我們需要這麼寫代碼:
    YourObject * obj = new YourObject();
然後在程序的最後你一定要記得釋放內存:
    delete(obj);
否則你可就造成內存泄露了。而現在,
    std::unique_ptr<YourObject> obj(new YourObject());
當 obj 跳出作用域範圍之外的時候,內存將會被自動回收。

16.新的算法

主要是 all_of()、any_of() 和 none_of(),下面是例子:

//all_of()、any_of() 和 none_of()函數
#include <algorithm>  
//C++11 code  
//are all of the elements positive?  
all_of(first, first+n, ispositive()); //false  
//is there at least one positive element?  
any_of(first, first+n, ispositive());//true  
// are none of the elements positive?  
none_of(first, first+n, ispositive()); //false  
 
 
//新的 copy_n:
#include <algorithm>  
int source[5]={0,12,34,50,80};  
int target[5];  
//從 source 拷貝 5 個元素到 target  
copy_n(source,5,target);  
 
 
//iota() 算法可以用來創建遞增序列,它先把初值賦值給 *first,然後用前置 ++ 操作符增長初值並賦值到給下一個迭代器指向的元素,如下
#include <numeric>  
int a[5]={0};  
char c[3]={0};  
iota(a, a+5, 10); //changes a to {10,11,12,13,14}  
iota(c, c+3, 'a'); //{'a','b','c'}  

參考文章:

https://blog.csdn.net/jiange_zh/article/details/79356417

https://blog.csdn.net/caogenwangbaoqiang/article/details/79438279

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