【初阶与进阶C++详解】第二十二篇:C++11新特性(列表初始化+变量类型推到+右值引用+新增默认成员函数+可变模板参数+lambda表达式+包装器function_bind)
🏆個(gè)人主頁(yè):企鵝不叫的博客
? 🌈專(zhuān)欄
- C語(yǔ)言初階和進(jìn)階
- C項(xiàng)目
- Leetcode刷題
- 初階數(shù)據(jù)結(jié)構(gòu)與算法
- C++初階和進(jìn)階
- 《深入理解計(jì)算機(jī)操作系統(tǒng)》
- 《高質(zhì)量C/C++編程》
- Linux
?? 博主碼云gitee鏈接:代碼倉(cāng)庫(kù)地址
?若有幫助可以【關(guān)注+點(diǎn)贊+收藏】,大家一起進(jìn)步!
💙系列文章💙
【初階與進(jìn)階C++詳解】第一篇:C++入門(mén)知識(shí)必備
【初階與進(jìn)階C++詳解】第二篇:C&&C++互相調(diào)用(創(chuàng)建靜態(tài)庫(kù))并保護(hù)加密源文件
【初階與進(jìn)階C++詳解】第三篇:類(lèi)和對(duì)象上(類(lèi)和this指針)
【初階與進(jìn)階C++詳解】第四篇:類(lèi)和對(duì)象中(類(lèi)的六個(gè)默認(rèn)成員函數(shù))
【初階與進(jìn)階C++詳解】第五篇:類(lèi)和對(duì)象下(構(gòu)造+static+友元+內(nèi)部類(lèi)
【初階與進(jìn)階C++詳解】第六篇:C&C++內(nèi)存管理(動(dòng)態(tài)內(nèi)存分布+內(nèi)存管理+new&delete)
【初階與進(jìn)階C++詳解】第七篇:模板初階(泛型編程+函數(shù)模板+類(lèi)模板+模板特化+模板分離編譯)
【初階與進(jìn)階C++詳解】第八篇:string類(lèi)(標(biāo)準(zhǔn)庫(kù)string類(lèi)+string類(lèi)模擬實(shí)現(xiàn))
【初階與進(jìn)階C++詳解】第九篇:vector(vector接口介紹+vector模擬實(shí)現(xiàn)+vector迭代器區(qū)間構(gòu)造/拷貝構(gòu)造/賦值)
【初階與進(jìn)階C++詳解】第十篇:list(list接口介紹和使用+list模擬實(shí)現(xiàn)+反向迭代器和迭代器適配)
【初階與進(jìn)階C++詳解】第十一篇:stack+queue+priority_queue+deque(仿函數(shù))
【初階與進(jìn)階C++詳解】第十二篇:模板進(jìn)階(函數(shù)模板特化+類(lèi)模板特化+模板分離編譯)
【初階與進(jìn)階C++詳解】第十三篇:繼承(菱形繼承+菱形虛擬繼承+組合)
【初階與進(jìn)階C++詳解】第十四篇:多態(tài)(虛函數(shù)+重寫(xiě)(覆蓋)+抽象類(lèi)+單繼承和多繼承)
【初階與進(jìn)階C++詳解】第十五篇:二叉樹(shù)搜索樹(shù)(操作+實(shí)現(xiàn)+應(yīng)用KVL+性能+習(xí)題)
【初階與進(jìn)階C++詳解】第十六篇:AVL樹(shù)-平衡搜索二叉樹(shù)(定義+插入+旋轉(zhuǎn)+驗(yàn)證)
【初階與進(jìn)階C++詳解】第十七篇:紅黑樹(shù)(插入+驗(yàn)證+查找)
【初階與進(jìn)階C++詳解】第十八篇:map_set(map_set使用+multiset_multimap使用+模擬map_set)
【初階與進(jìn)階C++詳解】第十九篇:哈希(哈希函數(shù)+哈希沖突+哈希表+哈希桶)
【初階與進(jìn)階C++詳解】第二十篇:unordered_map和unordered_set(接口使用+模擬實(shí)現(xiàn))
【初階與進(jìn)階C++詳解】第二十一篇:哈希應(yīng)用(位圖實(shí)現(xiàn)應(yīng)用+布隆過(guò)濾器增刪查優(yōu)缺點(diǎn)+海量數(shù)據(jù)面試題)
文章目錄
- 💙系列文章💙
- 💎前言
- 💎一、列表初始化
- 🏆1.用法
- 🏆2.initializer_list
- 💎二、變量類(lèi)型推導(dǎo)
- 🏆1.auto
- 🏆2.decltype
- 🏆3.typeid
- 💎三、左/右值引用
- 🏆1.左值/右值引用區(qū)別
- move
- 純右值和將亡值
- 🏆2.左值引用
- 🏆3.右值引用
- 🏆4.左/右值引用的使用場(chǎng)景
- 移動(dòng)構(gòu)造/移動(dòng)賦值
- 移動(dòng)構(gòu)造優(yōu)化
- 🏆5.完美轉(zhuǎn)發(fā)
- 萬(wàn)能引用
- 💎四、新增默認(rèn)成員函數(shù)
- 🏆1.移動(dòng)構(gòu)造函數(shù)/拷貝賦值函數(shù)
- 🏆2.關(guān)鍵字default(強(qiáng)制生成默認(rèn))/delete(強(qiáng)制刪除默認(rèn))
- 💎五、可變參數(shù)模板
- 🏆1.**遞歸函數(shù)展開(kāi)參數(shù)包**
- 🏆2.**逗號(hào)表達(dá)式展開(kāi)參數(shù)包**
- 🏆3.**emplace_back**
- 💎六、lambda表達(dá)式
- 🏆1.lambda介紹
- 🏆2.lambda使用
- 基本使用
- 捕捉列表
- mutable
- 引用捕獲
- 傳值捕獲
- 混著捕獲
- 🏆3.優(yōu)化
- 🏆4.lambda函數(shù)底層-仿函數(shù)
- 💎七、包裝器
- 🏆1.function包裝器
- 基本使用
- 🏆2.bind
💎前言
C++11是2011年更新的C++新語(yǔ)法,C++11能更好地用于系統(tǒng)開(kāi)發(fā)和庫(kù)開(kāi)發(fā)、語(yǔ)法更加泛華和簡(jiǎn)單化、更加穩(wěn)定和安全,不僅功能更強(qiáng)大,而且能提升程序員的開(kāi)發(fā)效率。
💎一、列表初始化
🏆1.用法
使用初始化列表初始化時(shí),可以初始化數(shù)組和自定義類(lèi)型,同時(shí)可以省略=====,這些用法不常用
struct Point{int _a;int _b; }; int x1 = 1; //新寫(xiě)法 int x2{ 2 }; //原來(lái)寫(xiě)法 int arr1[] = { 1,2,3,4 }; //C++11支持寫(xiě)法 int arr2[]{ 1,2,3,4,5,6 }; //C++11 自定義類(lèi)型列表初始化 Point p{ 1, 2 };使用更多是在new(傳送門(mén))對(duì)象的時(shí)候,初始化對(duì)象,同時(shí)還有部分自定義類(lèi)型和內(nèi)置類(lèi)型也有新寫(xiě)法
//舊寫(xiě)法 int *p1 = new int(3);//開(kāi)辟一個(gè)int的空間,并初始化為3賦值給p1 int *p2 = new int[3];//開(kāi)辟3個(gè)int的空間,不進(jìn)行初始化 //C++11寫(xiě)法,用多個(gè)花括號(hào)的方式進(jìn)行批量初始化 Point* p4 = new Point(1, 3); Point* p4 = new Point[2]{ {1, 2}, {3, 4} }; //下面第一二種寫(xiě)法相同,利用{}直接調(diào)用構(gòu)造函數(shù) //第一種是舊寫(xiě)法,第二三種是新寫(xiě)法 Point p1(1, 3); Point p2{1, 3}; Point p3 = { 1, 3 };//對(duì)類(lèi)來(lái)說(shuō),會(huì)進(jìn)行隱式類(lèi)型轉(zhuǎn)換+調(diào)用構(gòu)造函數(shù) //內(nèi)置類(lèi)型也可以這樣寫(xiě) vector<int> v1 = { 1,2,3,4 }; vector<int> v2{ 1,2,3,4 };//不建議這么寫(xiě)🏆2.initializer_list
initializer_list一般是作為構(gòu)造函數(shù)的參數(shù)(本質(zhì)就是{}),C++11對(duì)STL中的不少容器就增加initializer_list作為參數(shù)的構(gòu)造函數(shù),支持多個(gè)對(duì)象初始化
auto il = { 10, 20, 30 }; cout << typeid(il).name() << endl;//initializer_list模擬實(shí)現(xiàn):初始化,同時(shí)開(kāi)對(duì)應(yīng)size()大小空間
//initializer_list構(gòu)造 vector(const initializer_list<T>& il):_start(nullptr),_finish(nullptr),_endofstorage(nullptr) {reserve(il.size());//擴(kuò)容for (auto e : il) {push_back(e);} }💎二、變量類(lèi)型推導(dǎo)
🏆1.auto
auto聲明的類(lèi)型必須要進(jìn)行初始化,否則編譯器無(wú)法推導(dǎo)出auto的實(shí)際類(lèi)型。auto不能作為函數(shù)的參數(shù)和返回值,且不能用來(lái)直接聲明數(shù)組類(lèi)型(傳送門(mén))
int a = 10; auto pa = &a;🏆2.decltype
decltype是根據(jù)表達(dá)式的實(shí)際類(lèi)型推演出定義變量時(shí)所用的類(lèi)型。且還可以使用推導(dǎo)出來(lái)的類(lèi)型進(jìn)行變量聲明。
decltype不可以作為函數(shù)的參數(shù),編譯時(shí)推導(dǎo)類(lèi)型
int Add(int x, int y) {return x + y; }int a = 10, b = 20;int sum = a * b;decltype(a + b) num;// 用decltype自動(dòng)推演sum的實(shí)際類(lèi)型,輸出intcout << "type(sum) " << typeid(sum).name() << endl;// 用decltype自動(dòng)推演a+b的實(shí)際類(lèi)型,輸出intcout << "type(a+b) " << typeid(num).name() << endl;// 不帶參數(shù),推導(dǎo)函數(shù)類(lèi)型,輸出intcout << "type(&sum) " << typeid(decltype(Add)).name() << endl;🏆3.typeid
typeid只能查看類(lèi)型,不能用其結(jié)果類(lèi)定義類(lèi)型,typeid只能推導(dǎo)類(lèi)型,但是不能使用類(lèi)型聲明和定義變量
int a = 9; cout << "type(a) " << typeid(a).name() << endl;💎三、左/右值引用
🏆1.左值/右值引用區(qū)別
左值:
- 一個(gè)表示數(shù)據(jù)的表達(dá)式,可以取地址和賦值,左值引用不能引用右值,左值可以出現(xiàn)在賦值符號(hào)的左邊,也可以出現(xiàn)在賦值符號(hào)的右邊
- const修飾后的左值不可以賦值,但是可以取地址,可以引用左值也可以引用右值
- 左值引用用符號(hào)&
右值:
-
一個(gè)表示數(shù)據(jù)的表達(dá)式,右值不能取地址,右值引用不能引用左值(可以move后引用左值),右值可以出現(xiàn)在賦值符號(hào)的右邊,但是不能出現(xiàn)出現(xiàn)在賦值符號(hào)的左邊比如:表達(dá)式返回值A(chǔ)+B、字面常量10
-
如果表達(dá)式運(yùn)行結(jié)果或單個(gè)變量是一個(gè)引用,則認(rèn)為是右值
-
右值引用的符號(hào)是&&
總結(jié):左值可以取地址,右值不能取地址
move
move:當(dāng)需要用右值引用引用一個(gè)左值時(shí),可以通過(guò)move來(lái)獲得綁定到左值上的右值引,可以把左值換成右值,但不能把右值轉(zhuǎn)左值,則可能資源被掠奪導(dǎo)致該對(duì)象失效
-
被轉(zhuǎn)化的左值,其生命周期并沒(méi)有隨著左值的轉(zhuǎn)化而改變,即std::move轉(zhuǎn)化的左值變量value不會(huì)被銷(xiāo)毀。move告訴編譯器:我們有?個(gè)左值,但我們希望像?個(gè)右值?樣處理它。
-
STL中也有另一個(gè)move函數(shù),就是將一個(gè)范圍中的元素搬移到另一個(gè)位置。
int main() {String s1("123");//輸出移動(dòng)拷貝,s1變成一個(gè)右值,所以會(huì)調(diào)用移動(dòng)構(gòu)造去構(gòu)造s2,這樣s1的資源就被轉(zhuǎn)移給了s2,s1本身也沒(méi)有資源了String s2(move(s1));return 0; }
純右值和將亡值
- 純右值:基本類(lèi)型的常量或臨時(shí)對(duì)象,如:a+b,字面常量
- 將亡值:自定義類(lèi)型的臨時(shí)對(duì)象用完自動(dòng)完成析構(gòu),如:函數(shù)以值的方式返回一個(gè)對(duì)象
🏆2.左值引用
左值引用就是對(duì)左值的引用,針對(duì)出了作用域不會(huì)銷(xiāo)毀的變量進(jìn)行引用返回,引用傳參,減少形參拷貝代價(jià)
int b = 1;const int c = 2;// 以下是對(duì)上面左值的 左值引用int& rb = b;const int& rc = c;🏆3.右值引用
下面是常見(jiàn)的右值和右值引用,如果表達(dá)式運(yùn)行結(jié)果或單個(gè)變量是一個(gè)引用,則認(rèn)為是右值
int Add(int x, int y) {return x + y; }double x = 1.1, y = 2.2; // 常見(jiàn)的右值 10; x + y; Add(x, y); // 以下都是對(duì)右值的右值引用 int&& rr1 = 10; double&& rr2 = x + y; int&& ret = Add(3, 4);// 函數(shù)的返回值是一個(gè)臨時(shí)變量,是一個(gè)右值不能直接對(duì)右值進(jìn)行取地址/賦值操作,但是在右值引用過(guò)后,便可以對(duì)引用值進(jìn)行取地址/賦值操作,給右值取別名后,會(huì)導(dǎo)致右值被存儲(chǔ)到特定位置,且可以取到該位置的地址
int&& rr1 = 1; cout << rr1 << endl; rr1 = 2; cout << rr1 << endl; int* p = &rr1; *p = 3; cout << rr1 << endl; //上面依次輸出1 2 3🏆4.左/右值引用的使用場(chǎng)景
左值引用:做參數(shù)和做返回值都可以提高效率
右值引用:提高移動(dòng)構(gòu)造/移動(dòng)賦值等深拷貝場(chǎng)景的效率
移動(dòng)構(gòu)造/移動(dòng)賦值
右值引用的構(gòu)造函數(shù)/賦值重載稱(chēng)作移動(dòng)構(gòu)造/移動(dòng)賦值,直接拿取對(duì)象資源,避免多次拷貝造成時(shí)間和空間的損失
調(diào)用下面這個(gè)string類(lèi),下面會(huì)輸出三次深拷貝,造成浪費(fèi),如果使用左值引用,出了函數(shù)作用域之后,臨時(shí)對(duì)象會(huì)被銷(xiāo)毀,進(jìn)行賦值的時(shí)候會(huì)出現(xiàn)利用str別名拷貝,但str是一個(gè)已經(jīng)銷(xiāo)毀的對(duì)象
class String { public:string(const char* str = ""):_size(strlen(str)), _capacity(_size){//cout << "string(char* str)" << endl;_str = new char[_capacity + 1];strcpy(_str, str);} void swap(string& s){::swap(_str, s._str);::swap(_size, s._size);::swap(_capacity, s._capacity);} // 移動(dòng)構(gòu)造string(string&& s):_str(nullptr), _size(0), _capacity(0){cout << "string(string&& s) -- 資源轉(zhuǎn)移" << endl;swap(s);}// 拷貝構(gòu)造string(const string& s):_str(nullptr), _size(0), _capacity(0){cout << "string(const string& s) -- 深拷貝" << endl;string tmp(s._str);swap(tmp);}// 移動(dòng)賦值string& operator=(string&& s){cout << "string& operator=(string&& s) -- 資源轉(zhuǎn)移" << endl;swap(s);return *this;}// 賦值重載string& operator=(const string& s){cout << "string& operator=(string s) -- 深拷貝" << endl;string tmp(s);swap(tmp);return *this;}~string(){delete[] _str;_str = nullptr;}char& operator[](size_t pos){assert(pos < _size);return _str[pos];} private:char* _str;size_t _size;size_t _capacity; // 不包含最后做標(biāo)識(shí)的\0 }; String func(String& str) {String tmp(str);return tmp; } int main() {String s1("123");String s2(s1);String s3(func(s1));return 0; }因?yàn)榉祷氐呐R時(shí)對(duì)象是一個(gè)右值,所以會(huì)調(diào)用上面的移動(dòng)構(gòu)造的代碼對(duì)返回的臨時(shí)對(duì)象進(jìn)行構(gòu)造,本質(zhì)是資源進(jìn)行轉(zhuǎn)移,此時(shí)tmp指向的是一塊空的資源。最后返回的臨時(shí)對(duì)象拷貝構(gòu)造給s3時(shí)還是調(diào)用了移動(dòng)構(gòu)造,兩次移動(dòng)構(gòu)造被編譯器優(yōu)化為一個(gè)。可以看出的是這里解決的是減少接受函數(shù)返回對(duì)象時(shí)帶來(lái)的拷貝,極大地提高了效率。
// 移動(dòng)構(gòu)造 string(string&& s) :_str(nullptr), _size(0), _capacity(0) {// 對(duì)于將亡值,內(nèi)部做移動(dòng)拷貝cout << "string(string&& s) -- 資源轉(zhuǎn)移" << endl;swap(s); }可以看出的是func返回的臨時(shí)對(duì)象是通過(guò)移動(dòng)賦值給s2的,也是一次移動(dòng)賦值,復(fù)用了之前已經(jīng)寫(xiě)好的一個(gè)swap函數(shù),實(shí)現(xiàn)了一個(gè)“現(xiàn)代寫(xiě)法”的構(gòu)造,直接交換了二者的資源。避免深拷貝帶來(lái)的副作用
// 移動(dòng)賦值 string& operator=(string&& s) { cout << "string& operator=(string&& s) -- 資源轉(zhuǎn)移" << endl; swap(s);return *this; }移動(dòng)構(gòu)造優(yōu)化
🏆5.完美轉(zhuǎn)發(fā)
萬(wàn)能引用
模板中的&&不代表右值引用,而是萬(wàn)能引用,其既能接收左值又能接收右值,傳入的值,不管是左值還是右值,傳入后,都被當(dāng)作左值。
void fun(int &x){ cout << "左值引用" << endl; } void fun(const int &x){ cout << "const 左值引用" << endl; }void fun(int &&x){ cout << "右值引用" << endl; } void fun(const int &&x){ cout << "const 右值引用" << endl; }// 模板中的&&不代表右值引用,而是萬(wàn)能引用,其既能接收左值又能接收右值。 // 模板的萬(wàn)能引用只是提供了能夠接收同時(shí)接收左值引用和右值引用的能力, // 但是引用類(lèi)型的唯一作用就是限制了接收的類(lèi)型,后續(xù)使用中都退化成了左值,template<typename T> void perfectforward(T&& t) {fun(t);//fun(std::move(t));//利用move將左值轉(zhuǎn)換成右值//Fun(std::forward<T>(t));//完美轉(zhuǎn)發(fā) }int main() {perfectforward(10); // 右值int a;perfectforward(a); // 左值perfectforward(std::move(a)); // 右值const int b = 8;perfectforward(b); // const 左值perfectforward(std::move(b)); // const 右值return 0; }問(wèn)題:我們此時(shí)想調(diào)用右值怎么辦,但是我們不能用move來(lái)解決問(wèn)題,使用move變成右值后,數(shù)據(jù)容易被拿走
解決:使用完美轉(zhuǎn)發(fā)能夠在傳遞過(guò)程中保持它的左值或者右值的屬性,使用forward函數(shù)進(jìn)行完美轉(zhuǎn)發(fā)
template<typename T> void PerfectForward(T&& t){Fun(std::forward<T>(t)); }💎四、新增默認(rèn)成員函數(shù)
🏆1.移動(dòng)構(gòu)造函數(shù)/拷貝賦值函數(shù)
-
構(gòu)造函數(shù)
-
析構(gòu)函數(shù)
-
拷貝構(gòu)造函數(shù)
-
拷貝賦值重載
-
取地址重載
-
const 取地址重載
-
移動(dòng)構(gòu)造函數(shù)
-
拷貝賦值函數(shù)
最后兩個(gè)是新增的,具有以下特點(diǎn)
-
如果沒(méi)有實(shí)現(xiàn)移動(dòng)構(gòu)造函數(shù),且沒(méi)有實(shí)現(xiàn)析構(gòu)函數(shù) 、拷貝構(gòu)造、拷貝賦值重載中的任意一個(gè)。那么編譯器會(huì)自動(dòng)生成一個(gè)默認(rèn)移動(dòng)構(gòu)造。
-
默認(rèn)生成的移動(dòng)構(gòu)造函數(shù),對(duì)于內(nèi)置類(lèi)型成員會(huì)按照字節(jié)序進(jìn)行淺拷貝,自定義類(lèi)型成員,則需要看這個(gè)成員是否實(shí)現(xiàn)移動(dòng)構(gòu)造,如果實(shí)現(xiàn)了就調(diào)用移動(dòng)構(gòu)造,沒(méi)有實(shí)現(xiàn)就調(diào)用拷貝構(gòu)造。
-
如果沒(méi)有實(shí)現(xiàn)移動(dòng)賦值重載函數(shù),且沒(méi)有實(shí)現(xiàn)析構(gòu)函數(shù) 、拷貝構(gòu)造、拷貝賦值重載中的任意一個(gè),那么編譯器會(huì)自動(dòng)生成一個(gè)默認(rèn)移動(dòng)賦值。
-
默認(rèn)生成的移動(dòng)構(gòu)造函數(shù),對(duì)于內(nèi)置類(lèi)型成員會(huì)按照字節(jié)序進(jìn)行淺拷貝,自定義類(lèi)型成員,則需要看這個(gè)成員是否實(shí)現(xiàn)移動(dòng)賦值,如果實(shí)現(xiàn)了就調(diào)用移動(dòng)賦值,沒(méi)有實(shí)現(xiàn)就調(diào)用拷貝賦值。
class Test{ private:string _s;int _a; public:Test(const string& s="", int a=0):_s(s),_a(a){} }; void DefaultMoveCopy(){Test t1;//構(gòu)造 拷貝構(gòu)造 構(gòu)造cout << endl;TestB t2 = t1;//拷貝構(gòu)造 構(gòu)造cout << endl;TestB t3 = std::move(t1); //移動(dòng)拷貝cout << endl;TestB t4;t4 = std::move(t2);//構(gòu)造 拷貝構(gòu)造 構(gòu)造 移動(dòng)賦值重載cout << endl; }
-
🏆2.關(guān)鍵字default(強(qiáng)制生成默認(rèn))/delete(強(qiáng)制刪除默認(rèn))
調(diào)用對(duì)應(yīng)強(qiáng)制生成或者刪除默認(rèn)成員函數(shù)
class Person { public:Person(const char* name = "", int age = 0):_name(name),_age(age){}Person(Person&& p) = default;// 強(qiáng)制生成默認(rèn)的Person& operator=(Person&& p) = default;Person(Person& p) = delete;// 強(qiáng)制刪除默認(rèn)的~Person(){} private:Simulation::string _name;int _age; };💎五、可變參數(shù)模板
這就是一個(gè)可變的模板參數(shù),允許一個(gè)函數(shù)有多個(gè)參數(shù),且不要求是相同類(lèi)型,使用sizeof即可查看參數(shù)的個(gè)數(shù)
template <class... Args> void emplace_back (Args&&... args){// 參數(shù)個(gè)數(shù)cout << sizeof...(args) << endl; }- Args和args前面有省略號(hào),所以它們是可變參數(shù),帶省略號(hào)的參數(shù)稱(chēng)為“參數(shù)包”,它里面包含了0到N(N>=0)個(gè)模版參數(shù)
- Args是一個(gè)模板參數(shù)包,args是一個(gè)函數(shù)形參參數(shù)包
🏆1.遞歸函數(shù)展開(kāi)參數(shù)包
//一個(gè)無(wú)參的同名函數(shù),用作參數(shù)包遞歸的結(jié)尾,當(dāng)參數(shù)包中的參數(shù)個(gè)數(shù)為0時(shí),調(diào)用無(wú)參的ShowList void ShowList() {} //當(dāng)參數(shù)包中只有一個(gè)參數(shù)的時(shí)候,調(diào)用對(duì)應(yīng)的單參函數(shù) //args中第一個(gè)參數(shù)作為val傳參,參數(shù)包中剩下的參數(shù)作為新的參數(shù)包傳參 template <class T> void ShowList(const T & val) { cout << val << "->" << typeid(val).name() << " end" << endl; }template <class T, class ...Args> void ShowList(const T & val, Args... args) { cout << "參數(shù)個(gè)數(shù)" << sizeof...(args) << endl; cout << val << "->" << typeid(val).name() << endl; // 遞歸調(diào)用ShowList,當(dāng)參數(shù)包中的參數(shù)個(gè)數(shù)為0時(shí),調(diào)用上面無(wú)參的ShowList ShowList(args...); } void test() { ShowList(1, 'x', string("123")); }🏆2.逗號(hào)表達(dá)式展開(kāi)參數(shù)包
如果一個(gè)參數(shù)包中都是一個(gè)的類(lèi)型,那么可以使用該參數(shù)包對(duì)數(shù)組進(jìn)行列表初始化,參數(shù)包會(huì)展開(kāi),然后對(duì)數(shù)組進(jìn)行初始化,可以利用列表初始化數(shù)組時(shí),展開(kāi)參數(shù)包的特性,再與一個(gè)逗號(hào)表達(dá)式結(jié)合使用,可以展開(kāi)都會(huì)表達(dá)式中的參數(shù).
template <class T> int PrintArg(const T& t) {cout << t << " ";return 0; }template <class ...Args> void ShowList(Args... args) {//錯(cuò)誤寫(xiě)法//只適用于所有參數(shù)都是相同類(lèi)型的情況,如果是不同類(lèi)型則會(huì)報(bào)錯(cuò)//int arr[] = { args... };int arr[] = { PrintArg(args)... };cout << endl; }//都會(huì)表達(dá)式會(huì)按順序執(zhí)行,先執(zhí)行第一個(gè)函數(shù),然后把最后的0值賦給數(shù)組,這樣就把參數(shù)包展開(kāi)了 //展開(kāi) //(PrintArg(arg1), 0),(PrintArg(arg2), 0),(PrintArg(arg3), 0),(PrintArg(arg4), 0)🏆3.emplace_back
emplace_back可以支持可變參數(shù)包,然后調(diào)用定位new,使用參數(shù)包對(duì)空間進(jìn)行初始化
template <class... Args> //萬(wàn)能引用,實(shí)參是左值,參數(shù)包的這個(gè)形參就是左值引用,實(shí)參是右值,參數(shù)包的這個(gè)形參就是右值引用 void emplace_back (Args&&... args); //靈活使用 list<pair<int, string>> lt; lt.emplace_back(1, "hehe");push_back接受的是一個(gè)參數(shù)包,參數(shù)不能夠匹配,所以無(wú)法使用
list<pair<int, string>> lt; lt.push_back({ 2, "haha" });- 對(duì)于右值對(duì)象,emplace_back是先構(gòu)造一個(gè)對(duì)象,移動(dòng)構(gòu)造,push_back也是如此
- 對(duì)于左值對(duì)象,都是先構(gòu)造一個(gè)左值對(duì)象,再拷貝構(gòu)造
- 對(duì)于參數(shù)包,emplace_back直接使用參數(shù)包用定位new構(gòu)造
💎六、lambda表達(dá)式
對(duì)于自定義類(lèi)型排序,我們可以用sort,并且可以通過(guò)仿函數(shù)使用自定義類(lèi)型比較
#include <algorithm>//sort #include <functional>//greater/lessint main() {int arr[]={1,3,2,5,4}; int sz=sizeof(arr)/sizeof(arr[0]); //默認(rèn)升序std::sort(arr,arr+sz); //降序傳入仿函數(shù)greater std::sort(arr,arr+sz,greater<int>()); }對(duì)于自定義類(lèi)型,我們需要自己實(shí)現(xiàn)一個(gè)對(duì)應(yīng)的仿函數(shù)
struct Goods {string _name; // 名字double _price; // 價(jià)格int _evaluate; // 評(píng)價(jià)Goods(const char* str, double price, int evaluate):_name(str), _price(price), _evaluate(evaluate){} };//價(jià)格降序 struct CompPriceGreater{bool operator()(const Goods& g1, const Goods& g2){return g1._price > g2._price;} }; //價(jià)格升序 struct CompPriceLess {bool operator()(const Goods& g1, const Goods& g2){return g1._price < g2._price;} }; int main () {sort(v.begin(), v.end(), ComparePriceLess());sort(v.begin(), v.end(), ComparePriceGreater());}處理的對(duì)象有很多不同的成員變量的時(shí)候,就需要實(shí)現(xiàn)非常非常多的仿函數(shù)
🏆1.lambda介紹
[capture-list](parameters)mutable -> return-type{statement}- [capture-list]捕捉列表,用于編譯器判斷是否是lambda表達(dá)式,同時(shí)捕捉該表達(dá)式所在域的變量以供函數(shù)使用
- (parameters)參數(shù),和函數(shù)的參數(shù)一致。如果不需要傳參則可連帶()一起省略
- mutable默認(rèn)情況下捕捉列表捕捉的參數(shù)是const修飾的,該關(guān)鍵字的作用是取消const使其可修改
- -> return-type函數(shù)返回值類(lèi)型,返回類(lèi)型明確的情況下可以省略,由編譯器自動(dòng)推導(dǎo)
- {statement}函數(shù)體。除了可以使用傳入的參數(shù),還可以使用捕捉列表獲取的參數(shù)
🏆2.lambda使用
基本使用
下面可以看到,可以省略函數(shù)返回值類(lèi)型,編譯器會(huì)自動(dòng)推導(dǎo),或者我們可以指定返回值類(lèi)型
int x = 1, y = 2; auto Add = [](int x, int y) {return x + y; }; //auto Add = [](int x, int y) ->int{return x + y; }; cout<< Add(x, y)<<endl;//輸出3捕捉列表
捕捉了函數(shù)作用域里面的局部變量x/y,直接在lambda表達(dá)式內(nèi)部使用,因?yàn)椴恍枰獋魅雲(yún)?shù),所以直接把參數(shù)()和返回值類(lèi)型一并省略掉
int x = 1, y = 2; auto Add = [x, y]{return x+y }; cout<< Add(x, y)<<endl;//輸出3-
[var]:表示值傳遞方式捕捉變量var
-
[=]:表示值傳遞方式捕獲所有父作用域中的變量(包括this)
-
[&var]:表示引用傳遞捕捉變量var
-
[&]:表示引用傳遞捕捉所有父作用域中的變量(包括this)
注意:lambda函數(shù)僅能捕捉父作用域中局部變量,捕捉任何非此作用域或者非局部變量都會(huì)報(bào)錯(cuò),lambda函數(shù)生成的是一個(gè)對(duì)象,lambda表達(dá)式之間不能相互賦值
//lambda表達(dá)式之間不能相互賦值,編譯器會(huì)將他們是別成其他類(lèi)型auto f1 = []{cout << "hello world" << endl; };auto f2 = []{cout << "hello world" << endl; };//f1 = f2;
mutable
下面這個(gè)函數(shù)不加mutable是無(wú)法使用的,因?yàn)閰?shù)默認(rèn)是const類(lèi)型,如果不用()mutable我們不能對(duì)其修改
//mutable 只是讓傳值捕捉變量const屬性去掉了,這個(gè)關(guān)鍵字使用的時(shí)候必須帶上函數(shù)參數(shù)的() auto Swap2 = [a, b]()mutable{int tmp = a;a = b;b = tmp; };引用捕獲
下面是引用方式捕獲部分和捕獲全部對(duì)象,當(dāng)然可以修改參數(shù)
// 用引用的方式捕捉 auto Swap2 = [&x, &y]{ int tmp = x; x = y; y = tmp; }; int c =2, d=3, e=4, f=5, g=6, ret; // 傳引用捕捉全部對(duì)象 auto Func2 = [&]{ ret = c + d*e / f + g; };傳值捕獲
下面是傳值捕獲全部對(duì)象
int c =2, d=3, e=4, f=5, g=6, ret; // 傳值捕捉全部對(duì)象auto Func1 = [=]{ return c + d*e / f + g; };混著捕獲
下面可以用單獨(dú)引用的方式修改ret,語(yǔ)法上捕捉列表由多個(gè)捕獲對(duì)象組成,用逗號(hào)分割
int c =2, d=3, e=4, f=5, g=6, ret; // 混著捕捉 auto Func3 = [c, d, &ret]{ ret = c + d; }; // ret傳引用捕捉 其他全部傳值捕捉 auto Func4 = [=, &ret]{ ret = c + d*e / f + g; };🏆3.優(yōu)化
對(duì)自定義價(jià)格修改如下
vector<Goods> v = { Goods( "蘋(píng)果", 2.1, 5 ), { "香蕉", 3, 4 }, { "橙子", 2.2, 3 }, { "菠蘿", 1.5, 4 } };//價(jià)格升序 sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2){ return g1._price < g2._price; });cout << endl; //價(jià)格降序 sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2){ return g1._price > g2._price; });cout << endl; //名字排序 sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2){ return g1._evaluate < g2._evaluate; });🏆4.lambda函數(shù)底層-仿函數(shù)
lambda的底層就是把自己轉(zhuǎn)成了一個(gè)仿函數(shù)供我們調(diào)用,就是在類(lèi)中重載了operator()運(yùn)算符的類(lèi)對(duì)象
💎七、包裝器
🏆1.function包裝器
function包裝器,也叫作適配器。C++中的function本質(zhì)是一個(gè)類(lèi)模板,也是一個(gè)包裝器。頭文件< functional >
基本使用
class AddClass { public:static int Addi(int a, int b) {return a + b;}double Addd(double a, double b) {return a + b;} };int func(int a, int b) {return a + b; } //仿函數(shù) struct Functor {int operator()(int a, int b) {return a + b;} };void TestFunction1() {// 普通函數(shù)function<int(int, int)> func1 = func;cout << func1(10, 20) << endl;// 仿函數(shù)function<int(int, int)> func2 = Functor();cout << func2(10, 20) << endl;// 類(lèi)中static成員函數(shù),,可加&可不加function<int(int, int)> func3 = AddClass::Addi;cout << func3(100, 200) << endl;// 類(lèi)中非靜態(tài)成員函數(shù),要加上&,要AddClass()的匿名對(duì)象來(lái)適配包裝器function<double(AddClass, double, double)> func4 = &AddClass::Addd;cout << func4(AddClass(), 100.11, 200.11) << endl;// lambda表達(dá)式function<int(int, int)> func5 = [](int a, int b) {return a + b; };cout << func5(100, 200) << endl; }🏆2.bind
在頭文件< functional >中,函數(shù)適配器,接受一個(gè)可調(diào)用對(duì)象,生成一個(gè)新的可調(diào)用對(duì)象配對(duì)原參數(shù)列表,現(xiàn)參數(shù)順序調(diào)整
auto newCallable = bind(callable,arg_list);-
newCallable本身是一個(gè)可調(diào)用對(duì)象
-
arg_list是一個(gè)逗號(hào)分隔的參數(shù)列表 ,對(duì)應(yīng)給定的callable的參數(shù)
-
當(dāng)我們調(diào)用newCallable時(shí),newCallable會(huì)調(diào)用callable,并傳給它arg_list中的參數(shù)
arg_list中的參數(shù)可能包含形如_n的名字,其中n是一個(gè)整數(shù),這些參數(shù)是“占位符”,表示newCallable的參數(shù),它們占據(jù)了傳遞給newCallable的參數(shù)的“位置”。數(shù)值n表示生成的可調(diào)用對(duì)象中參數(shù)的位置:_1為newCallable的第一個(gè)參數(shù),_2為第二個(gè)參數(shù)
placeholders是用來(lái)占位的,代表這里的參數(shù)需要用戶手動(dòng)傳入,而_1代表傳入的第一個(gè)參數(shù),_2就是傳入的第二個(gè)參數(shù),同時(shí)可以利用 placeholders 調(diào)整參數(shù)個(gè)數(shù)
// 調(diào)整可調(diào)用對(duì)象的參數(shù)個(gè)數(shù)和順序 // _1 _2 ... 表示你要自己傳的那些參數(shù),_1表示第一個(gè)參數(shù)傳給_1 // 2個(gè)參數(shù) function<int(int, int)> func1 = bind(&AddClass::Addii,AddClass(), placeholders::_1, placeholders::_2); cout << func1(100, 200) << endl;//輸出300 // 1個(gè)參數(shù) function<int(int)> func2 = bind(&AddClass::Addii, AddClass(), 10, placeholders::_1); cout << func1(100, 200) << endl;//輸出210總結(jié)
以上是生活随笔為你收集整理的【初阶与进阶C++详解】第二十二篇:C++11新特性(列表初始化+变量类型推到+右值引用+新增默认成员函数+可变模板参数+lambda表达式+包装器function_bind)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Android Studio中如何隐藏顶
- 下一篇: 调皮捣蛋的孩子--十大负面测试用例