C++11特性總結

一、語法

1、auto

自動推導變量數據類型,用於從初始化表達式中推斷出變量的數據類型。類型確定後,不能重複初始化。

// testC++11.cpp : 此文件包含 "main" 函數。程序執行將在此處開始並結束。
#include "pch.h"
#include <iostream>
#include <map>
#include <list>
#include <vector>
using namespace std;

struct testStruct
{
   int a;
   int b;
};

int main()
{
   // auto
   cout << "Hello World!\n";
   auto a = 5;                      	//int類型
   //a = "ding";                 	//錯誤,不能重複初始化,a已推到出int類型
   auto b = "testing";                	//不是string類型
   string c = "testing";
   //auto d = b.c_str();
   auto e = c;                      	//string類型
   cout << e.c_str() << endl;
   char* f = new char[5];
   auto g = f;                      	//char*類型
   f[5] = 0;
   g[5] = 0;
   f = NULL;
   g = NULL;
   auto* h = new testStruct;        	//自定義testStruct類型
   h->a = 5;

   vector<int> i;
   vector<vector<int>> j;
   map<int, string> k;
   k[1] = "aaa";
   map<int, string>::iterator it = k.begin();
   auto l = i;                      	//vector<int>類型
   auto m = j;                      	//vector<vector<int>>類型
   auto n = k;                      	//map<int, string>
   //auto ll = i, mm = j, nn = k;    	//錯誤寫法,編譯失敗
   auto ll = 5, mm = 6, nn = 7;      	//正確寫法,在聲明符列表中,auto必須始終推到爲同一類型
   auto iter = k.begin();          	//map<int, string>::iterator類型

   double a2 = 3.144;
   const auto a3 = a2;                	//const double
   auto a4 = a2;                 	//double
   volatile int c2 = 3;
   auto c3 = c2;                 	//int

   return 0;
}

for循環迭代差異:

// testC++11.cpp : 此文件包含 "main" 函數。程序執行將在此處開始並結束。
#include "pch.h"
#include <iostream>
#include <vector>
using namespace std;

int main()
{
    //C++98
    vector<int> vec(8, 1);
    cout << "C++98 range for:" << endl;
    for (vector<int>::iterator it = vec.begin(); it != vec.end(); it++)
    {
        cout << *it << endl;
    }

    //C++11
    cout << "C++11 range for:" << endl;
    for (auto d : vec)   		//d中存儲的是vec中的值
    {
        d = 2;				//不會改變vector中的值,d只是一個臨時變量;
    }

    for (auto d : vec)			//d中存儲的是vec中的值
    {
        cout << d << endl;
    }

    for (auto &d : vec)  		
    {
        d = 6;				//可以改變vector中的值
    }

    for (auto d : vec)   
    {
        cout << d << endl;
    }

    //數組for_each
    char arr[] = {'a','b','c','d'};
    for (auto &d : arr)
    {
        d -= 32;
    }
    for (auto d : arr)
    {
        cout << d << endl;
    }

    //遍歷二維數組,注意迭代變量row必須爲引用。如果你想用 range for 的方法,來遍歷更高維的數組 (dim > 2),那麼你只需要:除了最內層循環之外,其他所有外層循環都加入 '&' 即可。
    int array2[5][5] = {0};
    for (auto &row : array2)
        for (auto col : row)
            cout << col << endl;

    return 0;
}

2、decltype

通過一個變量或表達式得到變量的數據類型,並不會真正的執行表達式;

// testC++11.cpp : 此文件包含 "main" 函數。程序執行將在此處開始並結束。
#include "pch.h"
#include <iostream>
#include <map>
#include <list>
#include <vector>
using namespace std;

int func(int a)
{
    cout << "this is func, int type param" << endl;
    return a;
}

string func(string b)
{
    cout << "this is func, string type param" << endl;
    return "this is func, string type param";
}

void func()
{
 cout << "this is func, without return value" << endl;
}

int main()
{
    // decltype
    map<int, int> aa;
    aa[5] = 6;
    decltype(aa) bb = aa;		//map<int, int>類型
    bb[1] = 2;
    map<int, int>::iterator cc = aa.begin();	
    decltype(cc) dd = cc;		//map<int, int>::iterator類型
    decltype(func(5)) ee = 5;     	//int類型,並不會執行func(5)
    decltype(func("5")) ff = "5";	//string類型,並不會執行func("5")
    // decltype(func()) gg = 6;		//編譯錯誤,非法使用void類型
    
    return 0;
}

3、nullptr

nullptr是一個表示空指針的標識。NULL只是一個定義爲常整數0的宏,而nullptr是C++11的一個關鍵字,一個內建的標識符。

// testC++11.cpp : 此文件包含 "main" 函數。程序執行將在此處開始並結束。
#include "pch.h"
#include <iostream>
#include <vector>

using namespace std;

void fun(int a)
{
    cout << "call function: [int]\n" << endl;
}

void fun(int *a)
{
    cout << "call function: [int*]\n" << endl;
}

int main()
{
    fun(NULL);   		//調用void fun(int a)
    fun(nullptr);  		//調用void fun(int *a)

    int* p = NULL;
    fun(p);  			//調用void fun(int *a)

    return 0;
}

4、final

final使用方法有以下三種:

  1. 修飾變量
    final修飾基本數據類型的變量時,必須賦予初始值且不能被改變,修飾引用變量時,該引用變量不能再指向其他對象。
  2. 修飾方法
    代表這個方法不可以被子類的方法重寫。如果你認爲一個方法的功能已經足夠完整了,子類中不需要改變的話,你可以聲明此方法爲final。
  3. 修飾類
    final類通常功能是完整的,它們不能被繼承。

C++11中的final不能修飾變量,修飾方法和類與java中final功能相同。

class Base final             	//final修飾類
{
public:
    int a;

    //final int b;          	//final不能修飾變量

    virtual void func() final; 	//final修改函數,不能被子類重寫
};

class Last final : Base       	//Base不能被繼承
{

};

5、override

允許派生類顯示的註明它將使用那個成員函數改寫基類的虛函數,必須是virtual修飾的虛函數。
與java中的@override功能類似,標識重寫(覆蓋)。

struct B
{
    virtual void f1(int) const;
    virtual void f2();
    void f3();
};

struct D1 : public B
{
    void f1(int) const override;  		//ok
    void f2(int) override;   			//error,B中沒有形如f2(int)的函數
    void f3() override;  			//error,f3不是虛函數
    void f4() override;  			//error,B中無f4函數
};

6、=default和=delete

對於 C++ 的類,如果程序員沒有爲其定義特殊成員函數,那麼在需要用到某個特殊成員函數的時候,編譯器會隱式的自動生成一個默認的特殊成員函數,比如拷貝構造函數,或者拷貝賦值操作符。

C++11允許我們使用=default來要求編譯器生成一個默認構造函數,也允許我們使用=delete來告訴編譯器不要爲我們生成某個默認函數

class B
{
    B() = default; 			//顯示聲明使用默認構造函數
    B(const B&) = delete; 		//禁止使用類對象之間的拷貝
    ~B() = default;  			//顯示聲明使用默認析構函數
    B& operator=(const B&) = delete;  	//禁止使用類對象之間的賦值
    B(int a);  
};

7、lambda表達式

lambda的基礎語法定義如下:

[capture](parameters) mutable ->return-type{statement}
  1. [capture]:捕獲列表。它總是出現在lambda函數的開始位置。在編譯器看來[]是lambda的引出符號,編譯器正式通過它來判斷接下來的代碼是否是lambda函數。捕獲列表能夠捕捉當前上下文中的變量供給lambda函數使用。具體的capture列表中的語法,下面還會詳細講述。

  2. (parameters):參數列表。它跟一般函數的參數列表一樣,使用規則也相同。在lambda中,如果不需要傳入參數,可以省略。

  3. mutable:修飾符。默認情況下,lambda函數總是一個const函數,mutable可以取消它的常量屬性。顯示指定mutable修飾符的時候,參數列表不能省略。

  4. ->return-type:返回值類型。->這個同C++11新引入的追蹤返回值類型的聲明是一致的,語法也是一致的。不同的是,處於方便,lambda函數在沒有返回值的情況下,可以省略掉(在某些編譯器可以推導出返回值類型的情況亦可省略)。

  5. {statement}:函數體。與一般函數的函數體一致,額外可以使用捕獲列表中捕獲的變量。

直觀感受下lambda的使用:

// testC++11.cpp : 此文件包含 "main" 函數。程序執行將在此處開始並結束。
#include "pch.h"
#include <iostream>

using namespace std;

int main()
{
    auto f = []() {cout << "hello world!" << endl; };
    f();  				//hello world!

    int a = 123;
    auto f1 = [a] { cout << a << endl; };
    f1();  				//123

    auto f2 = [&a] {cout << a << endl; };
    a = 789;
    f2();  				//789

    //隱式捕獲:讓編譯器根據函數體中的代碼來推斷需要捕獲哪些變量
    auto f3 = [=] {cout << a << endl; };
    f3();  				//789

    auto f4 = [&] {cout << a << endl; };
    a = 990;
    f4();  				//990

    auto f5 = [](int a, int b)->int {return a + b; };
    printf("%d\n", f5(1, 2));  		//3

    return 0;
}

lambda函數相較普通函數調用最便捷之處就是其捕獲列表,它可以通過值傳遞捕獲或者引用傳遞方式捕獲,直接在函數體內訪問到上下文(一個代碼塊內)的變量。

如果是普通函數的話,這些都要以參數形式傳遞進去,使代碼十分冗長。那麼捕獲列表的具體語法可以歸納如下:

  1. [a]表示值傳遞捕獲變量a(多個參數可以用逗號分隔)

  2. [=]表示值傳遞捕獲上下文所有變量

  3. [&a]表示引用傳遞捕獲變量a

  4. [&]表示引用傳遞捕獲上下文所有變量

  5. [this]表示值傳遞捕獲當前的this指針

  6. [=, &a, &b]表示值傳遞捕獲上下文所有變量,但是a、b變量以引用傳遞方式捕獲。

  7. [&, a, this]表示引用傳遞捕獲上下文所有變量,但是a和this指針以值傳遞方式捕獲

    char c = 'a';
    float d = 1.11f;
    foo([=](int param)->void
    {
        std::cout << "lambda param is :"<< param <<std::endl;
        std::cout << "lambda cap is :" << c << std::endl;
        std::cout << "lambda cap2 is :" << d << std::endl;
    }, 123);

8、move

是將對象的狀態或者所有權從一個對象轉移到另一個對象,只是轉移,沒有內存的搬遷或者內存拷貝。

// testC++11.cpp : 此文件包含 "main" 函數。程序執行將在此處開始並結束。
#include "pch.h"
#include <iostream>
#include <utility>
#include <vector>
#include <string>
int main()
{
    std::string str = "Hello";
    std::vector<std::string> v;
    
    //調用常規的拷貝構造函數,新建字符數組,拷貝數據
    v.push_back(str);
    std::cout << "After copy, str is \"" << str << "\"\n"; //After move, str is "Hello"
    
    //調用移動構造函數,掏空str,掏空後,最好不要使用str
    v.push_back(std::move(str));
    std::cout << "After move, str is \"" << str << "\"\n";   //After move, str is ""
    std::cout << "The contents of the vector are \"" << v[0]
        << "\", \"" << v[1] << "\"\n";   //The contents of the vector are "Hello", "Hello"
}

二、STL新內容

1、array

  1. 使用 std::array保存在棧內存中,相比堆內存中的 std::vector,我們就能夠靈活的訪問這裏面的元素,從而獲得更高的性能;同時正式由於其堆內存存儲的特性,有些時候我們還需要自己負責釋放這些資源。

  2. 使用std::array能夠讓代碼變得更加現代,且封裝了一些操作函數,同時還能夠友好的使用標準庫中的容器算法等等,比如 std::sort。

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

// testC++11.cpp : 此文件包含 "main" 函數。程序執行將在此處開始並結束。
#include "pch.h"
#include <stdio.h>
#include <algorithm>
#include <array>

void foo(int* p)
{

}

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

    foo(&arr[0]);  				//OK
    foo(arr.data());  				//OK
    //foo(arr);  				//wrong
    std::sort(arr.begin(), arr.end());  	//排序

    return 0;
}

2、forward_list

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

// testC++11.cpp : 此文件包含 "main" 函數。程序執行將在此處開始並結束。
#include "pch.h"
#include <stdio.h>
#include <algorithm>
#include <iostream>
#include <string>
#include <forward_list>

int main()
{
    std::forward_list<int> list1 = { 1, 2, 3, 4 };

    //從前面向foo1容器中添加數據,注意不支持push_back
    list1.pop_front();  		//刪除鏈表第一個元素
    list1.remove(3);   			//刪除鏈表值爲3的節點
    list1.push_front(2);
    list1.push_front(1);
    list1.push_front(14);
    list1.push_front(17);

    list1.sort();

    for (auto &n : list1)
    {
        if (n == 17)
            n = 19;
    }

    for (const auto &n : list1)
    {
        std::cout << n << std::endl;  	//1 2 2 4 14 19
    }

    return 0;
}

3、unordered_map和unordered_set

無序容器中的元素是不進行排序的,內部通過 Hash 表實現,插入和搜索元素的平均複雜度爲 O(constant),在不關心容器內部元素順序時,能夠獲得顯著的性能提升。

C++11 引入了兩組無序容器:std::unordered_map/std::unordered_multimap 和 std::unordered_set/std::unordered_multiset。
下面給出unordered_map和unordered_set的使用方法。

// testC++11.cpp : 此文件包含 "main" 函數。程序執行將在此處開始並結束。
#include "pch.h"
#include <stdio.h>
#include <algorithm>
#include <iostream>
#include <string>
#include <unordered_map>
#include <unordered_set>

void foo(int* p)
{

}

int main()
{
    //unordered_map usage
    std::unordered_map<std::string, int> um = { {"2",2},{"1",1},{"3",3} };

    //遍歷
    for (const auto &n : um)
    {
        std::cout << "key:" << n.first << "  value:" << n.second << std::endl;
    }

    std::cout << "value:" << um["1"] << std::endl;


    //unordered_set usage
    std::unordered_set<int> us = { 2,3,4,1};

    //遍歷
    for (const auto &n : us)
    {
        std::cout << "value:" << n << std::endl;
    }

    std::cout << "value:" << us.count(9) << std::endl; 		//判斷一個數是否在集合內,1存在0不存在
    std::cout << "value:" << *us.find(1) << std::endl;  	//查找一個特定的數是否在集合內,找到就返回該數的迭代器位置

    return 0;
}

三、智能指針

1、shared_ptr

shared_ptr使用引用計數,每一個shared_ptr的拷貝都指向相同的內存。每使用他一次,內部的引用計數加1,每析構一次,內部的引用計數減1,減爲0時,刪除所指向的堆內存。賦值非原子操作,多線程使用需加鎖

// testC++11.cpp : 此文件包含 "main" 函數。程序執行將在此處開始並結束。
#include "pch.h"
#include <stdio.h>
#include <memory>
#include <iostream>

int main()
{
    //auto ptr = std::make_shared<int>(10);
    std::shared_ptr<int> ptr(new int(10));
    std::shared_ptr<int> ptrC(ptr);

    auto ptr2 = ptr;

    {
        auto ptr3 = ptr2;
        std::cout << "pointer1.use_count() = " << ptr.use_count() << std::endl;  		//4
        std::cout << "pointer2.use_count() = " << ptr2.use_count() << std::endl;  		//4
    }

    std::cout << "pointer1.use_count() = " << ptr.use_count() << std::endl;  		//3
    std::cout << "pointer2.use_count() = " << ptr2.use_count() << std::endl;  		//3

    int *p = ptr.get(); //獲取原始指針

    std::cout << "pointer1.use_count() = " << ptr.use_count() << std::endl;  		//3
    std::cout << "pointer2.use_count() = " << ptr2.use_count() << std::endl;  		//3

    return 0;
}

2、unique_ptr

一種獨佔的智能指針,它禁止其他智能指針與其共享同一個對象,從而保證代碼的安全。

// testC++11.cpp : 此文件包含 "main" 函數。程序執行將在此處開始並結束。
#include "pch.h"
#include <stdio.h>
#include <memory>
#include <iostream>

int main()
{
    std::unique_ptr<int> ptr(new int(10));
    //auto ptr2 = ptr; 		//非法

    //雖說unique_ptr是不可複製的,但我們可以使用std::move將其獨佔權轉移到其他的unique_ptr
    auto ptr2(std::move(ptr));
    std::cout << *ptr2 << std::endl;

    return 0;
}

3、weak_ptr

weak_ptr是爲了配合shared_ptr而引入的一種智能指針,因爲它不具有普通指針的行爲,沒有重載operator*和->,它的最大作用在於協助shared_ptr工作,像旁觀者那樣觀測資源的使用情況。

// testC++11.cpp : 此文件包含 "main" 函數。程序執行將在此處開始並結束。
#include "pch.h"
#include <iostream>
#include <map>
#include <list>
#include <vector>
#include <forward_list>
using namespace std;


#include <iostream>
#include <memory>
using namespace std;

class A;
class B;

class A {
public:
    A() {
        cout << "A !" << endl;
    }
    ~A() {
        cout << "~~~~~A !" << endl;
    }
    void setBB(shared_ptr<B> val) {
        bb = val;
    }
private:
    //shared_ptr<B> bb;
    weak_ptr<B> bb;             //用循環引用的指針之一爲weak_ptr,來解決循環引用
};


class B {
public:
    B() {
        cout << "B !" << endl;
    }
    ~B() {
        cout << "~~~~~~B !" << endl;
    }
    void setAA(shared_ptr<A> val) {
        aa = val;
    }
private:
    shared_ptr<A> aa;
};

int main()
{
    shared_ptr<A> aa(new A());
    shared_ptr<B> bb(new B());
    aa->setBB(bb);
    bb->setAA(aa);

    return 0;
}

參考鏈接:
https://www.cnblogs.com/skyfsm/p/9038814.html
https://www.cnblogs.com/lenmom/p/9198126.html
https://www.jianshu.com/p/5ea6e6feeaf3
https://www.jianshu.com/p/234b818f289a

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