C++11 特性
目錄
C++11簡介?
{} 初始化
std::initializer_list
auto
decltype
nullptr?
?范圍for循環
C++98的循環方式:
C++11的范圍遍歷:
智能指針
1. 為什么需要智能指針?
2.什么是內存泄漏?
2.1內存泄漏的分類
2.2如何避免內存泄漏
3.智能指針的使用及原理
3.1RAII
3.2智能指針的原理
還有常見的三種智能指針類型:
STL中的一些變化
右值引用和移動語義?
什么是左值?什么是左值引用呢?
?什么是右值?什么是右值引用?
左值引用和右值引用的比較?
左值引用總結:
?右值引用的總結:
??右值引用的場景和意義:
右值引用左值:
完美轉發
新的類功能?
強制生成的默認關鍵詞default:
?禁止生成默認成員函數的關鍵詞delete:
可變參數模板?
用遞歸的方式展開參數包:
逗號表達式展開參數包:
?編輯
STL中的emplace接口:?
lambda表達式
lambda表達式語法:
?函數對象與lambda表達式:
總結:
C++11簡介?
在2003年C++標準委員會曾經提交了一份技術勘誤表(簡稱TC1),使得C++03這個名字已經取代了
C++98稱為C++11之前的最新C++標準名稱。不過由于C++03(TC1)主要是對C++98標準中的漏洞
進行修復,語言的核心部分則沒有改動,因此人們習慣性的把兩個標準合并稱為C++98/03標準。
從C++0x到C++11,C++標準10年磨一劍,第二個真正意義上的標準珊珊來遲。相比于
C++98/03,C++11則帶來了數量可觀的變化,其中包含了約140個新特性,以及對C++03標準中
約600個缺陷的修正,這使得C++11更像是從C++98/03中孕育出的一種新語言。相比較而言,
C++11能更好地用于系統開發和庫開發、語法更加泛華和簡單化、更加穩定和安全,不僅功能更
強大,而且能提升程序員的開發效率,公司實際項目開發中也用得比較多,所以我們要作為一個
重點去學習。
由于C++11的新增語法特性篇幅特別多,所以文章也講解實際中比較實用的語法
{} 初始化
在C++98中,標準允許使用{}對數組或者結構體元素進行統一的列表初始值設定,比如:
struct Point {int _x;int _y; }; int main() {int array1[] = { 1, 2, 3, 4, 5 };int array2[5] = { 0 };Point p = { 1, 2 };return 0; }?C++11中擴大了{}的使用范圍,使其可用于所有的內置類型和用戶自定義的類型,使用初始化列表的時候 = 可加可不加
struct Point {int _x;int _y; }; int main() {int x1 = 1;int x2{ 2 };int array1[]{ 1, 2, 3, 4, 5 };int array2[5]{ 0 };Point p{ 1, 2 };// C++11中列表初始化也可以適用于new表達式中int* pa = new int[4]{ 0 };//標準容器vector<int> v{1,2,3,4,5};//代替了push_back一個一個插入提高代碼效率map<int, int> m{{1,1}, {2,2,},{3,3},{4,4}};return 0; }創建對象的時候也可以使用列表初始化方式調用構造函數初始化
class Date { public:Date(int year, int month, int day):_year(year),_month(month),_day(day){cout << "Date(int year, int month, int day)" << endl;} private:int _year;int _month;int _day; }; int main() {Date d1(2022, 1, 1); // old style// C++11支持的列表初始化,這里會調用構造函數初始化Date d2{ 2022, 1, 2 };Date d3 = { 2022, 1, 3 };return 0; }std::initializer_list
?initializer? 的使用:
int main() {vector<int> v = { 1,2,3,4 };list<int> lt = { 1,2 };// 這里{"sort", "排序"}會先初始化構造一個pair對象map<string, string> dict = { {"sort", "排序"}, {"insert", "插入"} };// 使用大括號對容器賦值v = {10, 20, 30};return 0; }讓模擬實現的vector也可以實現用{}賦值
template<class T> class vector { public:typedef T* iterator;vector(initializer_list<T> l){_start = new T[l.size()];_finish = _start + l.size();_endofstorage = _start + l.size();iterator vit = _start;typename initializer_list<T>::iterator lit = l.begin();while (lit != l.end()){*vit++ = *lit++;}}vector<T>& operator=(initializer_list<T> l) {vector<T> tmp(l);std::swap(_start, tmp._start);std::swap(_finish, tmp._finish);std::swap(_endofstorage, tmp._endofstorage);return *this;} private:iterator _start;iterator _finish;iterator _endofstorage; };auto
在C++98中auto是一個存儲類型的說明符,表明變量是局部自動存儲類型,但是局部域中定義局
部的變量默認就是自動存儲類型,所以auto就沒什么價值了。C++11中廢棄auto原來的用法,將
其用于實現自動類型推斷。這樣要求必須進行顯示初始化,讓編譯器將定義對象的類型設置為初
始化值的類型。
?
decltype
作用:將變量的聲明類型作為表達式指定的類型?
使用場景:
template<class T1, class T2> void F(T1 t1, T2 t2) {decltype(t1 * t2) ret;cout << typeid(ret).name() << endl; } int main() {const int x = 1;double y = 2.2;decltype(x * y) ret; // ret的類型是doubledecltype(&x) p; // p的類型是int*cout << typeid(ret).name() << endl;cout << typeid(p).name() << endl;F(1, 'a');return 0; }nullptr?
由于C++中NULL被定義成字面量0,這樣就可能回帶來一些問題,因為0既能指針常量,又能表示
整形常量。所以出于清晰和安全的角度考慮,C++11中新增了nullptr,用于表示空指針
#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endif
?
?范圍for循環
C++98的循環方式:
int arr[] = { 3,4,2,1,5,6};// 下標遍歷for(int i = 0; i < sizeof(arr) / sizeof(arr[0]); ++i){std::cout << arr[i] << " ";}// 指針遍歷for(int* p = arr; p < arr + sizeof(arr) / sizeof(arr[0]); ++p){std::cout << *p << " ";}C++11的范圍遍歷:
C++98的for循環遍歷的特點就是要寫明一個明確的范圍,如果我們需要完整的遍歷一個數組或者集合或者容器(map)的時候,可以讓編譯器來自動推算范圍。這就引出C++11的范圍for。
int arr[] = { 3,4,2,1,5,6};// 下標遍歷for(int &e : arr){cout << e <<" ";}//使用auto的話會更簡單for(auto &e : arr){cout << e <<" ";}//不明確的區間不能范圍forint *p = arr;for(auto &e : p)//范圍不確定,運行報錯{cout << e <<" ";}但是范圍遍歷并不適用于所有情況,范圍遍歷的前提條件就是迭代的區間范圍都是確定的。比如一般的容器vector,map,set,string,list.用戶自己實現的類要想實現的話需要自行提供自增運算符重載和賦值運算符重載?
智能指針
1. 為什么需要智能指針?
int div() {int a, b;cin >> a >> b;if (b == 0)throw invalid_argument("除0錯誤");return a / b; } void Func() {int* p1 = new int; //當p1和p2被正常的new出來int* p2 = new int;cout << div() << endl;//調用div時候如果輸入的b為0,那么根據異常的特性會在這里直接返回//這樣直接跳過下面的delete了,導致了內存泄漏 delete p1;delete p2; } int main() {try{Func();//這里捕捉異常}catch (exception& e){cout << e.what() << endl;}return 0; }分析上述代碼。
2.什么是內存泄漏?
內存泄漏指的是,人為的疏忽導致程序未能釋放已經不再使用的內存的情況。內存泄漏指的并不是內存在物理上的消失,而是程序員分配某段內存后,失去了對該段內存的控制,造成內存浪費
內存泄漏的危害:長期運行的程序出現內存泄漏,影響很大,如操作系統、后臺服務等等,出現
內存泄漏會導致響應越來越慢,最終卡死。
2.1內存泄漏的分類
- 堆內存泄露*(Heap?leak)
????????堆內存指的是程序執行中依據須要分配通過malloc / calloc / realloc / new等從堆中分配的一
????????塊內存,用完后必須通過調用相應的 free或者delete 刪掉。假設程序的設計錯誤導致這部分
????????內存沒有被釋放,那么以后這部分空間將無法再被使用,就會產生Heap Leak。
- 系統資源泄露
????????指程序使用系統分配的資源,比方套接字、文件描述符、管道等沒有使用對應的函數釋放
????????掉,導致系統資源的浪費,嚴重可導致系統效能減少,系統執行不穩定
2.2如何避免內存泄漏
1. 工程前期良好的設計規范,養成良好的編碼規范,申請的內存空間記著匹配的去釋放。ps:
這個理想狀態。但是如果碰上異常時,就算注意釋放了,還是可能會出問題。需要下一條智
能指針來管理才有保證。
2. 采用RAII思想或者智能指針來管理資源
3. 有些公司內部規范使用內部實現的私有內存管理庫。這套庫自帶內存泄漏檢測的功能選項。
4. 出問題了使用內存泄漏工具檢測。ps:不過很多工具都不夠靠譜,或者收費昂貴。
內存泄漏非常常見,解決方案分為兩種:1、事前預防型。如智能指針等。2、事后查錯型。如泄漏檢測工具
?
3.智能指針的使用及原理
3.1RAII
RAII(Resource Acquisition Is Initialization)是一種利用對象生命周期來控制程序資源(如內
存、文件句柄、網絡連接、互斥量等等)的簡單技術
在對象構造時獲取資源,接著控制對資源的訪問使之在對象的生命周期內始終保持有效,最后在
對象析構的時候釋放資源。借此,我們實際上把管理一份資源的責任托管給了一個對象
優點:1.不需要顯示的釋放資源 2.這種方式同時可以保證對象所需的資源在其生命周期內始終有效
// 使用RAII思想設計的SmartPtr類 template<class T> class SmartPtr { public:SmartPtr(T* ptr = nullptr): _ptr(ptr){}~SmartPtr(){if(_ptr)delete _ptr;} private:T* _ptr; };3.2智能指針的原理
上述的SmartPtr還不能稱作智能指針,因為他還不具備指針的行為。指針可以解引用,可以通過->訪問他的成員。因此AutoPtr模板中還需要重載 * ,->
template<class T> class SmartPtr { public:SmartPtr(T* ptr = nullptr): _ptr(ptr){}~SmartPtr(){if(_ptr)delete _ptr;}T& operator*() {return *_ptr;}T* operator->() {return _ptr;} private:T* _ptr; }; struct Date {int _year;int _month;int _day; }; int main() {SmartPtr<int> sp1(new int);*sp1 = 10cout<<*sp1<<endl;SmartPtr<int> sparray(new Date);// 需要注意的是這里應該是sparray.operator->()->_year = 2018;// 本來應該是sparray->->_year這里語法上為了可讀性,省略了一個->sparray->_year = 2018;sparray->_month = 1;sparray->_day = 1; }總結一下:智能指針的原理 1.RAII 2.將類重載*,->使其具備指針的行為?
還有常見的三種智能指針類型:
STL中的一些變化
?實際中最有用的就是:unordered_map和unordered_set在上片博客講了unordered_map和map的區別。https://blog.csdn.net/Obto_/article/details/128388643?spm=1001.2014.3001.5502
實際上更新了C++11后,容器中增加的新方法->插入接口函數的右值版本,都說這樣可以增加效率,具體的看下文對右值的詳細講述。
右值引用和移動語義?
傳統的C++語法中就有引用的語法,而C++11中新增了的右值引用語法特性,所以從現在開始我們
之前學習的引用就叫做左值引用。無論左值引用還是右值引用,都是給對象取別名。
什么是左值?什么是左值引用呢?
左值是一個數據的表達式,我們可以取他的地址,可以對他賦值,左值可以出現在賦值符號的左邊,而右值不能出現在賦值符號的左邊。定義const的左值就不能給他賦值,但可以對他取地址。
左值引用就是給左值取別名。?
int main() {// 以下的p、b、c、*p都是左值int* p = new int(0);int b = 1;const int c = 2;// 以下幾個是對上面左值的左值引用int*& rp = p;int& rb = b;const int& rc = c;int& rp = *p;return 0; }?什么是右值?什么是右值引用?
右值也是一個表達數據的表達式,比如:字面常量,表達式返回值,函數返回值(這個不能是左值引用的返回值)等等,右值可以出現在賦值符號的右邊,但是不能出現出現在賦值符號的左邊,右值不能取地址。右值引用就是對右值的引用,給右值取別名。
int main() {double x = 1.1, y = 2.2;// 以下幾個都是常見的右值10;x + y;fmin(x, y);// 以下幾個都是對右值的右值引用int&& rr1 = 10;double&& rr2 = x + y;double&& rr3 = fmin(x, y);// 這里編譯會報錯:error C2106: “=”: 左操作數必須為左值10 = 1;x + y = 1;fmin(x, y) = 1;return 0; }需要注意的是:右值是不能取地址,但可以取別名,當給右值取別名后會導致右值被存儲到特定的位置。可以對右值的別名進行左值的操作。
?
左值引用和右值引用的比較?
左值引用總結:
1.左值引用只能引用左值,不能引用右值?
2.但是const左值既可以引用左值,也可以引用右值
int main() {// 左值引用只能引用左值,不能引用右值。int a = 10;int& ra1 = a; // ra為a的別名//int& ra2 = 10; // 編譯失敗,因為10是右值// const左值引用既可引用左值,也可引用右值。const int& ra3 = 10;const int& ra4 = a;return 0;}?右值引用的總結:
1.右值引用只能引用右值,不能引用左值
2.但是右值引用可以用move后的左值
int main() {// 右值引用只能右值,不能引用左值。int&& r1 = 10;// error C2440: “初始化”: 無法從“int”轉換為“int &&”// message : 無法將左值綁定到右值引用int a = 10;int&& r2 = a;// 右值引用可以引用move以后的左值int&& r3 = std::move(a);return 0; }??右值引用的場景和意義:
#include<assert.h> namespace gch {class string{public:typedef char* iterator;iterator begin(){return _str;}iterator end(){return _str + _size;}string(const char* str = ""):_size(strlen(str)), _capacity(_size){//cout << "string(char* str)" << endl;_str = new char[_capacity + 1];strcpy(_str, str);}// s1.swap(s2)void swap(string& s){::swap(_str, s._str);::swap(_size, s._size);::swap(_capacity, s._capacity);}// 拷貝構造string(const string& s):_str(nullptr){cout << "string(const string& s) -- 深拷貝" << endl;string tmp(s._str);swap(tmp);}// 賦值重載string& operator=(const string& s){cout << "string& operator=(string s) -- 深拷貝" << endl;string tmp(s);swap(tmp);return *this;}// 移動構造string(string&& s):_str(nullptr), _size(0), _capacity(0){cout << "string(string&& s) -- 移動語義" << endl;swap(s);}// 移動賦值string& operator=(string&& s){cout << "string& operator=(string&& s) -- 移動語義" << endl;swap(s);return *this;}~string(){delete[] _str;_str = nullptr;}char& operator[](size_t pos){assert(pos < _size);return _str[pos];}void reserve(size_t n){if (n > _capacity){char* tmp = new char[n + 1];strcpy(tmp, _str);delete[] _str;_str = tmp;_capacity = n;}}void push_back(char ch){if (_size >= _capacity){size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;reserve(newcapacity);}_str[_size] = ch;++_size;_str[_size] = '\0';}//string operator+=(char ch)string& operator+=(char ch){push_back(ch);return *this;}const char* c_str() const{return _str;}private:char* _str;size_t _size;size_t _capacity; // 不包含最后做標識的\0}; }int main() {gch::string s1("ai");return 0; }左值引用的使用場景:
void func1(bit::string s) {} void func2(const bit::string& s) {} int main() {bit::string s1("hello world");// func1和func2的調用我們可以看到左值引用做參數減少了拷貝,提高效率的使用場景和價值func1(s1);func2(s1);//但左值引用能做的不夠全面,比如傳值返回的時候就會存在深拷貝// string operator+=(char ch) 傳值返回存在深拷貝// string& operator+=(char ch) 傳左值引用沒有拷貝提高了效率s1 += '!';return 0; }左值引用的短板:? 當函數返回對象是一個局部變量,出了函數作用域就不存在了,就不能使用左值引用進行返回,只能傳值返回。例如:gch::string to_string(int value)函數中可以看到,這里只能傳值返回,傳值返回會導致至少一次的深拷貝(舊的編譯器如果沒有優化的話可能會有兩次深拷貝)
?
右值引用和移動語義解決上述的問題:
在gch::string中新增了移動構造,移動構造本質使用其他人的資源來構造自己,這樣就不用深拷貝了。
string(string&& s):_str(nullptr),_size(0),_capacity(0) {cout << "string(string&& s) -- 移動語義" << endl;swap(s); } int main() {bit::string ret2 = bit::to_string(-1234);return 0; }在移動構造中沒有開空間,沒拷貝數據,所以效率提高了
下列是移動賦值:
//移動賦值 string& operator=(string&& s) {cout << "string& operator=(string&& s) -- 移動語義" << endl;swap(s);return *this; } int main() {bit::string ret1;ret1 = bit::to_string(1234);return 0; }::STL容器都增加了移動拷貝和移動賦值
右值引用左值:
按照語法,右值引用只能引用右值,但右值引用一定不能引用左值的嗎?答案顯然是否定的,在很多場景下需要右值引用去引用左值實現移動語義,當需要右值引用一個左值的時候,可以使用move函數,move(左值)這樣可以將左值強轉成右值,就可以進行對左值的移動了
int main() {gch::string s1("hello world");// 這里s1是左值,調用的是拷貝構造gch::string s2(s1);// 這里我們把s1 move處理以后, 會被當成右值,調用移動構造// 但是這里要注意,一般是不要這樣用的,因為我們會發現s1的// 資源被轉移給了s3,s1被置空了。gch::string s3(std::move(s2));return 0; }完美轉發
模板中的&&萬能引用?
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; }// 模板中的&&不代表右值引用,而是萬能引用,其既能接收左值又能接收右值。 // 模板的萬能引用只是提供了能夠接收同時接收左值引用和右值引用的能力, // 但是引用類型的唯一作用就是限制了接收的類型,后續使用中都退化成了左值, // 我們希望能夠在傳遞過程中保持它的左值或者右值的屬性, 就需要用我們下面學習的完美轉發template<typename T> void PerfectForward(T&& t) {Fun(t); } 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; }運行結果:
? ? ? ? ? ?完美轉發會在傳參的過程中對象的原生類型屬性?
新的類功能?
?原來C++的類中,有6個默認成員函數。C++11后新增兩個移動構造函數和移動賦值運算符重載。
針對移動構造函數和移動賦值運算符重載有一些需要注意的點如下
?
-
如果你沒有自己實現移動構造函數,且沒有實現析構函數 、拷貝構造、拷貝賦值重載中的任
意一個。那么編譯器會自動生成一個默認移動構造。默認生成的移動構造函數,對于內置類
型成員會執行逐成員按字節拷貝,自定義類型成員,則需要看這個成員是否實現移動構造,
如果實現了就調用移動構造,沒有實現就調用拷貝構造。
? - 如果你沒有自己實現移動賦值重載函數,且沒有實現析構函數 、拷貝構造、拷貝賦值重載中
的任意一個,那么編譯器會自動生成一個默認移動賦值。默認生成的移動構造函數,對于內
置類型成員會執行逐成員按字節拷貝,自定義類型成員,則需要看這個成員是否實現移動賦
值,如果實現了就調用移動賦值,沒有實現就調用拷貝賦值。(默認移動賦值跟上面移動構造
完全類似)
? - 如果你提供了移動構造或者移動賦值,編譯器不會自動提供拷貝構造和拷貝賦值。這里要注意:一定要配套的全部實現不要單獨實現移動構造或者移動賦值
強制生成的默認關鍵詞default:
C++11為了讓你更好的控制函數,假設有一個你需要用到的默認成員函數,因為某一些原因沒有生成,那就可以用default來強制生成默認成員函數。比如:我們提供了拷貝構造就不會再生成移動構造了,可以加個default來強制它生成
class Person { public:Person(const char* name = "", int age = 0):_name(name), _age(age){}Person(const Person& p):_name(p._name),_age(p._age){}Person(Person&& p) = default; private:bit::string _name;int _age; }; int main() {Person s1;Person s2 = s1;Person s3 = std::move(s1);return 0; }?禁止生成默認成員函數的關鍵詞delete:
如果能想要限制某些默認函數的生成,在C++98中,是該函數設置成private,并且只聲明補丁
已,這樣只要其他人想要調用就會報錯。在C++11中更簡單,只需在該函數聲明加上=delete即
可,該語法指示編譯器不生成對應函數的默認版本,稱=delete修飾的函數為刪除函數。
可變參數模板?
template <class ...Args> void ShowList(Args... args) {}?Args是一個模板參數包,args是一個函數形參參數包,聲明一個參數包Args...args,這個參數包可以包含0到任意個模板參數。
上面的參數args前面有省略號,所以它就是一個可變模版參數,我們把帶省略號的參數稱為“參數包”,它里面包含了0到N(N>=0)個模版參數。我們無法直接獲取參數包args中的每個參數的,只能通過展開參數包的方式來獲取參數包中的每個參數,這是使用可變模版參數的一個主要特點,也是最大的難點,即如何展開可變模版參數。由于語法不支持使用args[i]這樣方式獲取可變參數,所以我們的用一些奇招來一一獲取參數包的值。
用遞歸的方式展開參數包:
// 遞歸終止函數 template <class T> void ShowList(const T& t) {cout << t << endl; } // 展開函數 template <class T, class ...Args> void ShowList(T value, Args... args) {cout << value <<" ";ShowList(args...); } int main() {ShowList(1);ShowList(1, 'A');ShowList(1, 'A', std::string("sort"));return 0; }逗號表達式展開參數包:
這種展開參數包的方式,不需要通過遞歸終止函數,是直接在expand函數體中展開的, printarg
不是一個遞歸終止函數,只是一個處理參數包中每一個參數的函數。
這種就地展開參數包的方式實現的關鍵是逗號表達式。我們知道逗號表達式會按順序執行逗號前面的表達式。
expand函數中的逗號表達式:(printarg(args), 0),也是按照這個執行順序,先執行printarg(args),再得到逗號表達式的結果0。同時還用到了C++11的另外一個特性——初始化列表,通過初始化列表來初始化一個變長數組,
{(printarg(args), 0)...}將會展開成((printarg(arg1),0),(printarg(arg2),0), (printarg(arg3),0), etc... ),最終會創建一個元素值都為0的數組int arr[sizeof...(Args)]。由于是逗號表達式,在創建數組的過程中會先執行逗號表達式前面的部分printarg(args)打印出參數,也就是說在構造int數組的過程中就將參數包展開了,這個數組的目的純粹是為了在數組構造的過程展開參數包
template <class T> void PrintArg(T t) {cout << t << " "; } //展開函數 template <class ...Args> void ShowList(Args... args) {int arr[] = { (PrintArg(args), 0)... };cout << endl; } int main() {ShowList(1);ShowList(1, 'A');ShowList(1, 'A', std::string("sort"));return 0; }
?
STL中的emplace接口:
emplace接口支持模板的可變參數,并且萬能引用。相對于insert他的優勢在哪里呢??
int main() {std::list< std::pair<int, char> > mylist;// emplace_back支持可變參數,拿到構建pair對象的參數后自己去創建對象// 那么在這里我們可以看到除了用法上,和push_back沒什么太大的區別mylist.emplace_back(10, 'a');mylist.emplace_back(20, 'b');mylist.emplace_back(make_pair(30, 'c'));mylist.push_back(make_pair(40, 'd'));mylist.push_back({ 50, 'e' });for (auto e : mylist)cout << e.first << ":" << e.second << endl;return 0; }emplace_back支持可變參數,拿到pair對象的參數后自己去創建對象。和push_back沒有太大區別
區別就是,emplace_back是直接構造對象出來在list中,push_back是先構造一個對象然后再移動構造,效率對于今天來說影響并不是很大。
lambda表達式
在C++98中想對一個數據進行排序,一般使用std::sort??
int main() {int array[] = {3,1,2,4,6,8,5,9};// 默認是升序std::sort(array, array+sizeof(array)/sizeof(array[0]));默認是less<int>()// 如果需要降序,需要改變元素的比較規則std::sort(array, array + sizeof(array) / sizeof(array[0]), greater<int>());return 0; }而lambda表達式的?出現給程序員提供了很大的便攜;
struct Goods {string _name; // 名字double _price; // 價格int _evaluate; // 評價 Goods(const char* str, double price, int evaluate):_name(str), _price(price), _evaluate(evaluate){} };int main() {vector<Goods> v = { { "蘋果", 2.1, 5 }, { "香蕉", 3, 4 }, { "橙子", 2.2,3 }, { "菠蘿", 1.5, 4 } };sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2){return g1._price < g2._price; });sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2){return g1._price > g2._price; });sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2){return g1._evaluate < g2._evaluate; });sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2){return g1._evaluate > g2._evaluate; }); }看的出來其實lamda表達式就是一個匿名函數。
lambda表達式語法:
?
lambda表達式書寫格式:[capture-list] (parameters) mutable -> return-type { statement }
[ capture_list ] :?捕捉列表,出現在lambda表達式開頭,編譯器根據[]來判斷接下來的代碼是否為lambda函數,捕捉列表能夠捕捉上下文中變量供lambda函數使用。?
(parameters) :?參數列表。與普通函數的參數列表一致,如果不需要傳遞參數可以連()一起省了
mutable :?默認情況下,lambda函數總是一個const函數,mutable可以取消他的常性。使用該修飾符時,參數列表不能省略(即使參數為空)?
return -type :?返回值類型。用追蹤返回值類型形式聲明函數的返回值類型,沒有返回值可省略,有返回值也可以省略讓編譯器自動推導。
{statement} :?函數體。在該函數體內,可以使用其參數外,還可以使用所有捕捉到的變量。
注意:lambda函數定義中,參數列表和返回值類型都是可選部分,而捕捉列表可以為空。所以C++11中最簡單的lambda表達式為 []{}
?函數對象與lambda表達式:
lass Rate { public:Rate(double rate): _rate(rate){}double operator()(double money, int year){ return money * _rate * year;} private:double _rate; }; int main() {// 函數對象double rate = 0.49;Rate r1(rate);r1(10000, 2);// lamberauto r2 = [=](double monty, int year)->double{return monty*rate*year;};r2(10000, 2);return 0; }從使用方式上看,函數對象與lambda表達式完全一樣
實際在底層編譯器對于lambda表達式的處理方式,完全就是按照函數對象的方式處理的,如果定義了一個lambda表達式,編譯器會自動生成一個類,在該類中重載了operator()
總結:
?C++11的更新的特性非常多,本篇博客講述了C++11中使用較多的新語法。
1.{}更方便了類的初始化,使自定義類型的類更方便的初始化
2.auto廢棄了原先的用法,取而代之的是自動類型推斷,并要求顯示的初始化,當定義類型的名字很長的時候就可以使用auto自動推斷,亦或者在使用范圍for的時候更為方便
3.內存泄露的問題會導致程序的崩潰,新增的智能指針就可以在進程不正常結束的時候,能夠保證指針所指向的區域正常的釋放(free);智能指針本質就是定義了一個類,讓他具有指針的功能(因為類會自動調用析構函數)
4.在SLT中新增了移動構造和移動拷貝,這就引入了左值和右值的概念,左值可以取他的地址,可以對他賦值,左值可以出現在賦值符號的左邊,右值不能出現在賦值符號的左邊,比如一般的字面常量就是右值{1,'a',"str"}都算是右值。一般情況下左值的值都是有用的,而右值是可以被奪取的,在類的構造的時候會識別參數是左值還是右值,如果是左值就進行正常的構造,如果是右值就進行移動構造,移動構造就是用參數的資源來構建自己,有點成全他人犧牲自己,所以移動構造只是資源的轉移而不會新增資源。
5.lambda表達式很大程度上方便了代碼的控制,就像sort函數時一般情況下是默認升序,而想讓sort后降序就需要傳遞一個仿函數過去,這樣就導致我們要特地去寫一個仿函數僅僅為了讓sort排降序,所以lambda表達式就是一個匿名函數可以在一定程度上代替掉仿函數。
如果文章對你有幫助還請不要浪費你的點贊哦,謝謝支持
?
總結
- 上一篇: 目前主流微型计算机显卡总线接口是,江苏省
- 下一篇: C++11 新功能