一、引言
? ? ? ? 上期我們介紹了C++11的大部分特性。C++11的初始化列表、auto關鍵字、右值引用、萬能引用、STL容器的的emplace函數。
????????要補充的是右值引用是不能取地址的,我們程序員一定要遵守相關的語法。操作是未定義的很危險。
二、????????仿函數和函數指針
? ? ? ? 我們先從仿函數的形成和函數指針的形成開始介紹起來,有了這個大家會更好的清楚lamda表達式、裝配器function和綁定器bind。
? ? ? ? 仿函數顧名思義就是類似于函數,肯定不是函數,但是肯定具有和函數一樣的功能。那問題來了為什么不用函數指針呢?
? ? ? ? 函數指針使用起來還是有點問題的。首先需要typedef一個固定的函數類型。沒錯這個函數是固定的不可變的,參數類型是固定的。那為什么我們不用上一章講過的C語言不定參數。C語言的不定參數也需要傳入類型,這個點是比較麻煩的。而且明確的就是類型不是字符串,因為不定參數是宏定義而不是真正的函數。
? ? ? ? 為了方便比較我們也來使用一下函數指針吧。
#include <stdio.h>// 函數指針的模擬,隨便寫的類型名。
// 打印數組。// 定義函數指針
typedef void (*ptest)(int* a, int n);void test(int* a, int n)
{for (int i = 0;i < n;++i){printf("%d ", a[i]);}printf("\n");
}void function(ptest func,int* a,int n)
{func(a,n);
}int main()
{int a[] = { 1,2.3,4,5,6,7,8 };int n = sizeof(a) / sizeof(a[0]);function(test,a,n);return 0;
}
? ? ? ? 那我們是如何實現仿函數的呢?C++用的最多的就是類和對象,利用模板的特性我們可以傳入各種不同的類,那仿函數的原型毫無疑問就是類,類中包含的函數傳入到另一個函數或是其他類中也可以調用內部中的函數。這樣我們實現一個基本的多態。多種形態。和特殊化處理。那我們該如何命名類中的函數。既然叫函數就應該和函數差不多,不然可讀性不是很好用的也麻煩,而且本來就是當函數來使用沒必要取什么特殊的名字。這樣我們就實現出了一個類函數。
class 類名
{public:// 運算符重載 : operator + 符號 // 就可以通過符號與類結合調用。// 這完全就是C++基于類和變量之間不同的考量。返回類型 operator () (參數){; // …………所要調用的方法和所要實現的內容。}
}; //這個分號千萬不要忘記了,就把它當做是C語言定義變量。
? ? ? ? 那下面我們來做一個簡單的練習。首先我們要知道C++中包含排序方法。雖然排序的方法多種多樣,但是排序標準都各有不同。并不一定是基于簡單的比較大小,有可能是運用了特殊的比較方法。還可能會有順序的差異例如升序、降序。所以我們使用C++庫<algorithm>中std::sort()函數進行排序。另外排序標準由我們來進行確定。
#include <iostream>
#include <algorithm>// 仿函數。
class Func
{
public:// 這里依據函數實現的不同而返回類型不同。// 還可以加入模板。template<typename T>bool operator () (T i, T j){// 升序的話是后面的數據大于前面的數據。// 我們為了演示反其道而行之。return i > j;}
};int main()
{int a[] = { 1 , 2 , 4 , 5 , 6 , 7 , 8 };int n = sizeof(a) / sizeof(a[0]);// 起始位置和末尾位置的下一個(有效位置的下一個,也就是無效位置。更確切的講是規定范圍。)// 最后一個參數是用來傳遞排序標準的,什么都不傳的話默認是升序(缺省參數)。// 如果是STL容器的話直接用里面的begin函數和end函數。std::sort(a, a + n);// 范圍forfor (int x : a){std::cout << x << " ";}std::cout << std::endl;std::sort(a, a + n, Func());for (int x : a){std::cout << x << " ";}std::cout << std::endl;return 0;
}
三、????????Lamda表達式
? ? ? ? 上述的仿函數雖然與函數指針簡便不少,例如不用考慮參數的類型,不用定義函數指針。但C++標準委員會還是覺得不好用,能不能出現一個匿名的函數呢?直接不用寫名字。這個其實最開始是python最先搞出來的。C++看到后覺得好用直接也實現一個出來了。底層就是我們上面實現的仿函數,只是用完就丟這個特性和仿函數不一樣。
[捕捉列表:值捕捉&+變量、對象=引用。值引用:變量、對象=const引用]
// 如果只有一個&,說明是引用函數中的所有變量、對象。
->返回類型(沒有就省略這個->)(參數類型)
{; //…………實現的方法。
}
? ? ? ? 接下來我們還是使用std::sort()函數進行練習。
????????只需要更改其中的一行代碼就行了。lamda表達式其實就是匿名函數(沒有名字,說明接下來無法通過名字來調用,同時也意味著無法遞歸。(函數自己本身調用一個新的原本函數自己本身,可以理解創建新的自己分身。)
std::sort(a, a + n,[](int i,int j)
{return i > j;
});
四、????????包裝器function、綁定器bind介紹
? ? ? ?這兩個函數都是庫<functional>文件中的類。
????????std::function和函數指針有著異曲同工之妙,在項目中對于函數的發封裝起到非常大的作用。std::function可以與lamda表達式、仿函數和bind裝配器有著不錯的兼容。函數指針并不能做到這點。此外函數指針為了重復使用一般都是泛型編程(返回值或是參數都是用void*,這樣就可以不用管參數的類型,參數的數量,返回值的數量和類型,但是這樣出錯了非常不好檢查出來。)
? ? ? ? std::bind包裝器,常常用于為了一個類能夠調用其他類中的類函數,或者是固定好函數的參數,實現編程的隱蔽性和泛型編程。
? ? ? ? 下面我們以打印數組來做實例對象。
#include <iostream>
#include <algorithm>
#include <functional>class Func
{
public:void print(int* a,int n){for (int i = 0; i < n; ++i){std::cout << a[i] << " ";}std::cout << std::endl;}
};int main()
{int a[] = { 1 , 2 , 4 , 5 , 6 , 7 , 8 };int n = sizeof(a) / sizeof(a[0]);// 我們嘗試使用std::function包裝仿函數。// 還可以和模板進行搭配。// 調用未加static的類函數需要用到類中的指針或是引用。std::function<void(Func, int*, int)> func1 = &Func::print;func1(Func(), a, n);// 綁定器可以固定參數,調用更加方便,也防止別人亂改,也通過std::placeholders庫中的_n來確定該輸入幾個參數。// 一般可以將std::bind看作function的初始化類型。// 使用std::bind賦值給std::function的時候,記得已經在bind固定了的參數的類型從function中去掉。// 因為參數已經固定無需再傳參了。std::function<void(int*,int)> func2 = std::bind(&Func::print,Func(), std::placeholders::_1,std::placeholders::_2);func2(a, n);std::function<void()> func3 = std::bind(&Func::print, Func(), a, n);// 當然可以直接將所有參數都固定,直接變為無參函數。func3();return 0;
}
五、????????智能指針
????????我們在系統申請空間,總是要還的,所以我們常常需要手動釋放它。但是這非常不方便。所以C++搞出了智能指針,自動釋放申請空間或是自動關閉某個開關。但是最初的智能指針被罵的非常慘。因為C++根本沒有考慮多引用、多指向的情況。unique_ptr是明確了只有一個指針指向這塊空間。
? ? ? ? shared_ptr是允許多個指針指向申請的空間,運用了引用計數的概念,初始化時申請了一塊空間進行統計使用同一空間的指針數量。當引用為零時自動釋放申請空間。都使用shared_ptr了,肯定賦值的對象、接受的參數肯定也是shared_ptr。
? ? ? ? weak_ptr虛指針,防止shared_ptr指向過多,不能釋放,造成內存泄漏,從而導致服務器崩潰。在shared_ptr互相指向時使用,shared_ptr不增加引用計數。相互指向造成你不釋放,我也不釋放的難題。不能單獨使用,只能和shared_ptr一起使用。
template<class T>class unique_ptr{public:explicit unique_ptr(T* ptr):_ptr(ptr){}~unique_ptr(){if (_ptr){cout << "delete:" << _ptr << endl;delete _ptr;}}// 像指針?樣使? T& operator*(){return *_ptr;}T* operator->(){return _ptr;}unique_ptr(const unique_ptr<T>& sp) = delete;unique_ptr<T>& operator=(const unique_ptr<T>& sp) = delete;unique_ptr(unique_ptr<T>&& sp):_ptr(sp._ptr){sp._ptr = nullptr;}unique_ptr<T>& operator=(unique_ptr<T>&& sp){delete _ptr;_ptr = sp._ptr;sp._ptr = nullptr;}private:T* _ptr;};template<class T>class shared_ptr{public:explicit shared_ptr(T* ptr = nullptr): _ptr(ptr), _pcount(new int(1)){}template<class D>
{return _ptr;}int use_count() const{return *_pcount;}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}private:T* _ptr;int* _pcount;//atomic<int>* _pcount; function<void(T*)> _del = [](T* ptr) {delete ptr; };};// 需要注意的是我們這?實現的shared_ptr和weak_ptr都是以最簡潔的?式實現的, // 只能滿?基本的功能,這?的weak_ptr lock等功能是?法實現的,想要實現就要 // 把shared_ptr和weak_ptr?起改了,把引?計數拿出來放到?個單獨類型,shared_ptr // 和weak_ptr都要存儲指向這個類的對象才能實現,有興趣可以去翻翻源代碼 template<class T>class weak_ptr{public:weak_ptr(){}weak_ptr(const shared_ptr<T>& sp):_ptr(sp.get()){}weak_ptr<T>& operator=(const shared_ptr<T>& sp){_ptr = sp.get();return *this;}
private:T* _ptr = nullptr;};
}