C++11
在2003年C++標(biāo)準(zhǔn)委員會(huì)曾經(jīng)提交了一份技術(shù)勘誤表(簡(jiǎn)稱TC1),使得C++03這個(gè)名字已經(jīng)取代了C++98稱為
C++11之前的最新C++標(biāo)準(zhǔn)名稱。不過由于TC1主要是對(duì)C++98標(biāo)準(zhǔn)中的漏洞進(jìn)行修復(fù),語言的核心部分則沒
有改動(dòng),因此人們習(xí)慣性的把兩個(gè)標(biāo)準(zhǔn)合并稱為C++98/03標(biāo)準(zhǔn)。從C++0x到C++11,C++標(biāo)準(zhǔn)10年磨一劍,
第二個(gè)真正意義上的標(biāo)準(zhǔn)珊珊來遲。相比于C++98/03,C++11則帶來了數(shù)量可觀的變化,其中包含了約140
個(gè)新特性,以及對(duì)C++03標(biāo)準(zhǔn)中約600個(gè)缺陷的修正,這使得C++11更像是從C++98/03中孕育出的一種新語
言。相比較而言,C++11能更好地用于系統(tǒng)開發(fā)和庫(kù)開發(fā)、語法更加泛華和簡(jiǎn)單化、更加穩(wěn)定和安全,不僅功能更強(qiáng)大,而且能提升程序員的開發(fā)效率。
一 列表初始化
在C++98中,標(biāo)準(zhǔn)允許使用花括號(hào){}
對(duì)數(shù)組元素進(jìn)行統(tǒng)一的列表初始值設(shè)定。比如:
int array1[] = {1,2,3,4,5};int array2[5] = {0};
對(duì)于一些自定義的類型,卻無法使用這樣的初始化。比如:
vector<int> v{1,2,3,4,5};
就無法通過編譯,導(dǎo)致每次定義vector時(shí),都需要先把vector定義出來,然后使用循環(huán)對(duì)其賦初始值,非常不方便。C++11擴(kuò)大了用大括號(hào)括起的列表(初始化列表)的使用范圍,使其可用于所有的內(nèi)置類型和用戶自定義的類型,使用初始化列表時(shí),可添加等號(hào)(=)也可不添加。列表初始化可以在{}之前使用等號(hào),其效果與不使用=沒有什么區(qū)別。
單個(gè)對(duì)象的多參數(shù)列表初始化
#include<iostream>
#include<vector>
#include<map>
#include<string>
using namespace std;
struct B
{
int _x;
int _y;
};
class Point
{
public:
Point(int x = 0, int y = 0)
:_x(x)
, _y(y)
{}
private:
int _x;
int _y;
};
class A
{
public:
//內(nèi)含單參數(shù)的構(gòu)造函數(shù)
A(int a)
:_a(a)
{}
/*explicit A(int a)
:_a(a)
{}*/
private:
int _a;
};
int main()
{
//自定義類型
// 當(dāng)添加explicit之后就不讓轉(zhuǎn)換了
//單參數(shù)構(gòu)造函數(shù),支持隱士類型轉(zhuǎn)換
A aa(1);
A aa2 = 2;//隱士類型轉(zhuǎn)換int->A
//同理
string s1("hello");
string s2="world";//const char* ->string
vector<string>v;
v.push_back("world");
//C++11-多參數(shù),支持隱士類型轉(zhuǎn)換
Point p{ 1,2 };
B b{1,2};//兼容C語言
int* ptr1 = new int[5] {1, 2, 3, 4, 5};
Point* ptr2 = new Point[2]{ {1,2},{3,4} };
//內(nèi)置類型
int x1 = { 10 };
int x2{ 10 };
int x3{ 1 + 2 };
int x4 = { 1 + 2 };
int arr1[5]{ 1,2,3,4,5 };
int arr2[]{ 1,2,3,4,5 };
//動(dòng)態(tài)數(shù)組C++98中不支持
int* arr3 = new int[5] {1, 2, 3, 4, 5};
vector<int>v{ 1,2,3,4,5 };
map<int, int> m{ {1,1},{2,2},{3,3} };
return 0;
}
多個(gè)對(duì)象的列表初始化
多個(gè)對(duì)象想要支持列表初始化,需給該類(模板類)添加一個(gè)帶有initializer_list類型參數(shù)的構(gòu)造函數(shù)即可,常見的類比如vector,list,map,set在C++11中都支持initializer_list類型參數(shù)的構(gòu)造函數(shù)。注意:initializer_list是系統(tǒng)自定義的類模板,該類模板中主要有三個(gè)方法:begin()、end()迭代器以及獲取區(qū)間中元素個(gè)數(shù)的方法size()。
編譯器自己識(shí)別{}為initializer_list類型進(jìn)行轉(zhuǎn)化。
比如vector:
#include <initializer_list>
template<class T>
class Vector {
public:
// ...
Vector(initializer_list<T> l)
: _capacity(l.size())
, _size(0)
{
_array = new T[_capacity];
for (auto e : l)
_array[_size++] = e;
}
Vector<T>& operator=(initializer_list<T> l) {
delete[] _array;
size_t i = 0;
for (auto e : l)
_array[i++] = e;
return *this;
}
// ...
private:
T* _array;
size_t _capacity;
size_t _size;
};
int main()
{
//自己實(shí)現(xiàn)的
Vector<int> vv = { 1,2,3,4,5 };
Vector<int> vv2 = vv;
//多個(gè)對(duì)象支持列表初始化
vector<int>v1 = {1,2,3,4,5};
list<int> l1 = {1,2,3,4,5};
pair<string, string>kv("left","左邊");
map<string, string>dict = { {"insert","插入"},kv};
initializer_list<int> ilt = {1,2,32,4,5};
}
自己編寫list使用initializer_list支持多對(duì)象的列表初始化
注意:如果使用迭代器時(shí)報(bào)錯(cuò),前面加上typename initializer_list<T>:;iterator
,這是在類模板中再去找他的內(nèi)嵌類型,未實(shí)例化之前可能取不到。告訴編譯器類模板實(shí)例化了之后再去調(diào)用類里面的迭代器。
二 stl中的一些變化
- 新增一些容器
- 已有容器增加一些好用或者提高效率的接口。比如列表初始化initializer_list,右值引用相關(guān)接口提高效率。移動(dòng)構(gòu)造,移動(dòng)賦值。
cbegin(),cend(),emplace_back(),emplace().
array
定長(zhǎng)數(shù)組,相比于變長(zhǎng)數(shù)組vector。
優(yōu)點(diǎn):支持迭代器,更好的兼容STL容器。對(duì)于越界的檢查。
std::array
template < class T, size_t N > class array;
int main()
{
array<int, 10>a;
int a1[10];//數(shù)組
a1[14] = 0;//抽查行為,*(a+14)=0,越界可能不檢查
a[14] = 0;//a.operator[](14)=0,對(duì)于函數(shù)的調(diào)用,肯定檢查
return 0;
}
forword_list
forward_list,單鏈表,支持頭插頭刪(push_front() pop_front()),支持在節(jié)點(diǎn)后面插入刪除,不支持尾插尾刪和在節(jié)點(diǎn)之前的操作
unordered_map unordered_set
三 右值引用和移動(dòng)語義
C++98中提出了引用的概念,引用即別名,引用變量與其引用實(shí)體公共同一塊內(nèi)存空間,而引用的底層是通過指針來實(shí)現(xiàn)的,因此使用引用,可以提高程序的可讀性。
左值與右值是C語言中的概念,但C標(biāo)準(zhǔn)并沒有給出嚴(yán)格的區(qū)分方式,一般認(rèn)為:
-
可以放在=左邊的,變量或者解引用的指針,我們可以獲取他的地址+可以對(duì)他賦值,稱為左值。const修飾的左值可以取地址,但是不可以賦值。
-
只能放在=右邊的,或者不能取地址的稱為右值。表示數(shù)據(jù)的表達(dá)式如:字面常量,表達(dá)式返回值,傳值返回函數(shù)的返回值,臨時(shí)對(duì)象也是右值。
左值引用& vs 右值引用&&
const int b=10;//常量,函數(shù)返回值等不能取地址的都是右值
int main()
{
//可以取地址對(duì)象就是左值
const int b = 10;//b是左值,可以取地址不能賦值
const int& r3=b; //左值引用
//右值
double x = 1.1, y = 2.2;
x + y;
double &&r4=fmin(x,y);
//右值引用就是右值的別名
int&& rr1 = 10;
//左值引用不能直接引用右值,得加上const
const int& r1 = x + y;
const int& r2 = 10;
const int& r3 = fmin(x,y);
const int& p1 = (10+20);
//void push_back(const T& x)這樣傳參,對(duì)面左值引用和右值引用均可傳過去
//右值引用不能直接引用左值,但是可以右值引用move以后的左值
int d = 10;
int* p = &d;
int*&& rr1 = move(p);
int n = 10;
int&& p2 = move(n);
const int p = 20;
const int&& rr2 = move(p2);
//給右值取別名之后會(huì)改變存儲(chǔ)位置,另開辟個(gè)空間存儲(chǔ)右值,別名就是個(gè)左值了
cout<<&p2<<endl;
return 0;
}
右值引用的應(yīng)用
- 為了彌補(bǔ)左值引用的不足。
左值引用:傳值傳參會(huì)調(diào)用拷貝構(gòu)造函數(shù),作為參數(shù)基本完美解決問題;作為返回值以下問題就不完美只能解決部分問題,所以可用右值引用優(yōu)化。
string& operator+=(char ch)
{
push_back(ch);
return *this;//處理作用域還在,就很完美
}
string operator+(char ch)
{
string tmp(*this);
push_back(ch);
return tmp;//出了作用域就會(huì)被銷毀,傳值返回會(huì)多一次拷貝構(gòu)造,然后再析構(gòu),不完美。
//所以只能用傳值返回是右值
}
- 右值引用如何解決operator+的拷貝構(gòu)造問題呢?
提供一個(gè)移動(dòng)構(gòu)造,是右值只會(huì)去移動(dòng)構(gòu)造中,走最匹配的那個(gè)函數(shù)。
注意:C++11中將右值分為:自定義類型叫將亡值,或者純右值。
移動(dòng)構(gòu)造
//拷貝構(gòu)造
string(const string &s)//左值
:_str(nullptr)
,_size(0)
,_capacity(0)
{
string tmp(s._str);
swap(tmp);
}
//移動(dòng)構(gòu)造,一種資源轉(zhuǎn)移,避免了資源的些許浪費(fèi),少一層拷貝
string(string &&s)//右值(臨時(shí)對(duì)象也是一種右值)
:str(nullptr)
{
this->swap(s);
return *this;
}
int main()
{
string s("hello world");
string s1 = s;
string s2 = move(s);//將s左值的屬性修改為右值屬性,賦予了別人將自己的資源拿走的權(quán)利
//單純的move并不會(huì)對(duì)于s造成影響,當(dāng)傳給別人時(shí)就會(huì)有影響。
return 0;
}
//右值引用理解場(chǎng)景2
string to_string(int value)
{
string str;
while(value)
{
int val=value%10;
str+=('0'+val);
value/=10;
}
reverse(str.begin(),str.end());
return str;
}
int main()
{
string ret=to_string(1234);
}
? 首先,如果編譯器不優(yōu)化,str拷貝構(gòu)造臨時(shí)對(duì)象,臨時(shí)對(duì)象(在main函數(shù)的棧幀中)作為to_string的返回值構(gòu)造ret。優(yōu)化之后,在to_string結(jié)束之前,用str構(gòu)造ret,從兩次拷貝構(gòu)造優(yōu)化為只有一次拷貝構(gòu)造。
-
什么情況可優(yōu)化?
當(dāng)用臨時(shí)對(duì)象去構(gòu)造ret時(shí),也就是有ret接收時(shí)會(huì)進(jìn)行優(yōu)化。當(dāng)沒有ret時(shí),因?yàn)閟tr在to_string函數(shù)結(jié)束之后要被銷毀,必須得有一個(gè)臨時(shí)對(duì)象作為返回值返回,還沒人接收時(shí)就無法優(yōu)化。
? 如果有了移動(dòng)構(gòu)造之后,
如果不考慮優(yōu)化的存在,原來的兩次拷貝構(gòu)造變成一次拷貝構(gòu)造,一次移動(dòng)構(gòu)造,因?yàn)閟tr是左值調(diào)用一次拷貝構(gòu)造產(chǎn)生臨時(shí)變量,此時(shí)產(chǎn)生的拷貝的臨時(shí)對(duì)象被認(rèn)為是右值,調(diào)用移動(dòng)構(gòu)造。
優(yōu)化之后,就變成了一次移動(dòng)構(gòu)造,魯莽地直接將str認(rèn)為是右值,
如果有ret接收,直接移動(dòng)構(gòu)造交給ret,完成一次資源轉(zhuǎn)移。
如果沒有ret接收,之前直接將ret視為右值的存在在to_string()函數(shù)銷毀時(shí)作為函數(shù)返回值返回即可。
資源轉(zhuǎn)移:有ret接收,就資源轉(zhuǎn)移給ret,如果沒ret接收,就資源轉(zhuǎn)移給一個(gè)函數(shù)必有的函數(shù)返回值。
- 移動(dòng)構(gòu)造提升效率一個(gè)例子:
使得C++11 效率更高,當(dāng)然,如果vv是靜態(tài)或者是全局的,出了作用域還在,直接用左值返回就OK了。
移動(dòng)賦值
//移動(dòng)賦值
string& operator=(string&& s)
{
cout<<"轉(zhuǎn)移資源"<<endl;
swap(s);
return *this;
}
//正常深拷貝賦值
string & operator=(const string &s)
{
cout<<"深拷貝"<<endl;
string tmp(s);
swap(tmp);
return *this;
}
int main()
{
yuanwei::string s1("hello");
yuanwei::string s2("world");
s2=move(s1);//此時(shí)是右值,走匹配的右值引用,走移動(dòng)賦值函數(shù)實(shí)現(xiàn)資源轉(zhuǎn)移
//避免移動(dòng)構(gòu)造,先有一個(gè)對(duì)象
string ret;
ret=to_string(12345);
}
如果有移動(dòng)賦值時(shí),將str作為右值直接進(jìn)行移動(dòng)構(gòu)造,資源轉(zhuǎn)移給臨時(shí)對(duì)象,再用臨時(shí)對(duì)象移動(dòng)賦值給ret,兩次資源轉(zhuǎn)移。將臨時(shí)對(duì)象這個(gè)已經(jīng)存在的對(duì)象交給ret這個(gè)已經(jīng)存在的對(duì)象。
如果沒有移動(dòng)賦值函數(shù),
to_string()
的str被識(shí)別為右值移動(dòng)構(gòu)造資源轉(zhuǎn)移給臨時(shí)對(duì)象,臨時(shí)對(duì)象賦值走一遍深拷貝交給已經(jīng)存在的ret。
- 容器的插入接口也提供了一個(gè)右值引用的版本
//List C++11
void push_back (const value_type& val);
void push_back (value_type&& val);//新增
int main()
{
//string也有右值引用
list<string> lt;
string s("11111111");
lt.push_back(s);//傳參是左值,是深拷貝構(gòu)造
cout<<endl;
lt.push_back("22222222");//右值引用,移動(dòng)構(gòu)造,資源轉(zhuǎn)移
cout<<endl;
lt.push_back(to_string(333333));//to_string 一次str移動(dòng)構(gòu)造給臨時(shí)對(duì)象返回
//push_back走右值引用版本移動(dòng)賦值,定位new節(jié)點(diǎn)上,到lt中。
cout<<endl;
}
完美轉(zhuǎn)發(fā)
模板中的&&不代表右值引用,而是萬能引用,其既能接收左值又能接收右值。
模板的萬能引用只是提供了能夠接收同時(shí)接收左值引用和右值引用的能力,
但是引用類型的唯一作用就是限制了接收的類型,后續(xù)使用中都退化成了左值,
我們希望能夠在傳遞過程中保持它的左值或者右值的屬性, 就需要用我們下面學(xué)習(xí)的完美轉(zhuǎn)發(fā)
- 退化的例子,如圖所示:
使用完美轉(zhuǎn)發(fā)之后:
- 只要右值引用,再傳遞其他函數(shù)調(diào)用,要保持右值屬性,必須用完美轉(zhuǎn)發(fā),然后走的yuanwei::string的移動(dòng)賦值,資源轉(zhuǎn)移。
template<class T>
struct ListNode
{
ListNode* _next = nullptr;
ListNode* _prev = nullptr;
T _data;
};
template<class T>
class List
{
typedef ListNode<T> Node;
public:
List()
{
_head = new Node;
_head->_next = _head;
_head->_prev = _head;
}
void PushBack(T&& x)
{
// 只要右值引用,再傳遞其他函數(shù)調(diào)用,要保持右值屬性,必須用完美轉(zhuǎn)發(fā),然后走的yuanwei::string的移動(dòng)賦值,資源轉(zhuǎn)移
//Insert(_head, x);//這個(gè)x退化為左值,就會(huì)去調(diào)用yuanwei::string中的深拷貝
Insert(_head, std::forward<T>(x));
}
void PushFront(T&& x)
{
//Insert(_head->_next, x);
Insert(_head->_next, std::forward<T>(x));
}
void Insert(Node* pos, T&& x)
{
Node* prev = pos->_prev;
//Node* newnode = new Node;
//newnode->_data = std::forward<T>(x); // 關(guān)鍵位置
Node* newnode = (Node*)malloc(sizeof(Node));
//new(&newnode->_data)T(x);
//定位new調(diào)用從拷貝構(gòu)造->移動(dòng)構(gòu)造
new(&newnode->_data)T(std::forward<T>(x));
//new是開空間+調(diào)用構(gòu)造函數(shù)初始化
//stl中的容器的空間是從內(nèi)存池來的,和malloc效果一樣,只開空間不初始化也就不會(huì)調(diào)用構(gòu)造函數(shù),對(duì)已經(jīng)存在的空間初始化,需要用定位new初始化空間,就像這個(gè)一樣。
// prev newnode pos
prev->_next = newnode;
newnode->_prev = prev;
newnode->_next = pos;
pos->_prev = newnode;
}
void Insert(Node* pos, const T& x)
{
Node* prev = pos->_prev;
Node* newnode = new Node;
newnode->_data = x; // 關(guān)鍵位置
// prev newnode pos
prev->_next = newnode;
newnode->_prev = prev;
newnode->_next = pos;
pos->_prev = newnode;
}
private:
Node* _head;
};
int main()
{
List<yuanwei::string> lt;
lt.PushBack("1111");
//lt.PushFront("2222");
return 0;
}
emplace_back()
push_back VS emplace_back
//二者相比,右值版本不會(huì)更加高效,差不多
//左值版本 emplace 會(huì)更高效,因?yàn)樗淮嬖谏羁截惖膯栴}
int main()
{
std::list<pair<int, char>>mylist;
//兩次資源轉(zhuǎn)移,先構(gòu)造右值,再移動(dòng)構(gòu)造
mylist.push_back(make_pair(1,'A'));//一個(gè)類型參數(shù)
mylist.push_back({1,'a'});//pair支持{}初始化
//兩次直接構(gòu)造
mylist.emplace_back(make_pair(1,'a'));//整體作為單參數(shù)對(duì)象
mylist.emplace_back(1,'a');//多參數(shù)傳值也支持
return 0;
}
emplace_back()是直接構(gòu)造,pushback()是先構(gòu)造對(duì)象,然后再進(jìn)行資源轉(zhuǎn)移.
類的新功能
-
原來的默認(rèn)構(gòu)造函數(shù)有6個(gè),重要的有4個(gè):析構(gòu)函數(shù),構(gòu)造函數(shù),拷貝構(gòu)造函數(shù),拷貝賦值函數(shù).
- 默認(rèn)生成的構(gòu)造函數(shù)不寫的話,會(huì)自動(dòng)生成并在初始化列表階段調(diào)用自定義成員的構(gòu)造函數(shù).
-
新的是移動(dòng)構(gòu)造函數(shù),移動(dòng)賦值函數(shù)。這倆的使用規(guī)則是相同的,下面只介紹一個(gè):
如果在類中,你沒有實(shí)現(xiàn)移動(dòng)構(gòu)造函數(shù),并且你沒有實(shí)現(xiàn)你的析構(gòu)函數(shù),拷貝構(gòu)造函數(shù),拷貝賦值函數(shù)其中一個(gè),那么會(huì)給你生成默認(rèn)移動(dòng)構(gòu)造函數(shù);他對(duì)于內(nèi)置類型是采用值拷貝的方式,對(duì)于自定義類型,當(dāng)自定義類型本身是提供移動(dòng)構(gòu)造函數(shù)的話,就調(diào)用。如果沒有,就去調(diào)用拷貝構(gòu)造函數(shù)。
同理移動(dòng)賦值函數(shù)。
類成員初始化
類內(nèi)聲明內(nèi)置類型的時(shí)候給個(gè)缺省值
四 可調(diào)用對(duì)象類型
- C-函數(shù)指針void(*p)();
- C++98-仿函數(shù)/函數(shù)對(duì)象
- C++11-lambda表達(dá)式/匿名函數(shù)
//仿函數(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)
{}
};
struct ComparePriceLess
{
bool operator()(const Goods& gl, const Goods& gr)
{
return gl._price < gr._price;
}
};
struct ComparePriceGreater
{
bool operator()(const Goods& gl, const Goods& gr)
{
return gl._price > gr._price;
}
};
int main()
{
//仿函數(shù)
vector<Goods> v = { { "蘋果", 2.1, 5 }, { "香蕉", 3, 4 }, { "橙子", 2.2, 3 }, { "菠蘿", 1.5, 4 } };
sort(v.begin(), v.end(), ComparePriceLess());
sort(v.begin(), v.end(), ComparePriceGreater());
//C++11 lambda表達(dá)式
auto priceLess = [](const Goods& g1, const Goods& g2){return g1._price < g2._price; };
sort(v.begin(), v.end(), priceLess);
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; });
return 0;
}
lambda表達(dá)式
lambda表達(dá)式書寫格式:[capture-list] (parameters) mutable -> return-type { statement }
lambda表達(dá)式各部分說明
[capture-list] : 捕捉列表,該列表總是出現(xiàn)在lambda函數(shù)的開始位置,編譯器根據(jù)[]來判斷接下來的代碼是否為lambda函數(shù)**,**捕捉列表能夠捕捉上下文中的變量供lambda函數(shù)使用。所以不能省略。
(parameters):參數(shù)列表。與普通函數(shù)的參數(shù)列表一致,如果不需要參數(shù)傳遞,則可以連同()一起省略
mutable:默認(rèn)情況下,lambda函數(shù)總是一個(gè)const函數(shù),mutable可以取消其常量性。使用該修飾符時(shí),參數(shù)列表不可省略(即使參數(shù)為空)。
->returntype:返回值類型。用追蹤返回類型形式聲明函數(shù)的返回值類型,沒有返回值時(shí)此部分可省略。返回值類型明確情況下,也可省略,由編譯器對(duì)返回類型進(jìn)行推導(dǎo)。
{statement}:函數(shù)體。在該函數(shù)體內(nèi),除了可以使用其參數(shù)外,還可以使用所有捕獲到的變量。
注意: 在lambda函數(shù)定義中,參數(shù)列表和返回值類型都是可選部分,而捕捉列表和函數(shù)體可以為空。因此C++11中最簡(jiǎn)單的lambda函數(shù)為:[]{}; 該lambda函數(shù)不能做任何事情。
int main()
{
//lambda實(shí)現(xiàn)兩個(gè)數(shù)相加的功能
auto add1=[](int a,int b) ->int {return a + b; };
//調(diào)用方式
cout<<add1(1,2)<<endl;
auto add3=add1;
decltype(add1) add3=add1;
//返回值可以省略
auto add2 = [](int a,int b){return a + b; };
//沒有參數(shù)可以省略參數(shù)列表沒有返回值
auto func1 = [] { cout << "hello" << endl; };
//捕捉列表不能省略[]
auto swap1 = [](int& a,int& b){
int c = a;
a = b;
b = c; };
swap1(3,4);
int a = 3, b = 4;
auto fun1 = [&](int c) {b = a + c; };
//auto fun1 = [&a,&b](int c) {b = a + c; };//傳引用捕捉才能改變值
fun1(10);
cout << a << " " << b << endl;
int a = 0;
int b = 1;
//傳值捕捉,調(diào)用之后并不能改變ab的值,只是一份拷貝交換了.
//加上mutable lambda函數(shù)總是一個(gè)const函數(shù),mutable可以取消其常量性。使用該修飾符,參數(shù)列表不能省略。
//但是還是不能交換成功
auto swap2 = [a, b]()mutable {
int c = a;
a = b;
b = c;
};
swap2();//捕捉就不需要傳參數(shù)了
//傳引用捕捉
auto swap3 = [&a, &b]() {
int c = a;
a = b;
b = c;
};
swap3();
}
捕捉聲明:
捕捉列表描述了上下文中那些數(shù)據(jù)可以被lambda使用,以及使用的方式傳值還是傳引用。
[var]:表示值傳遞方式捕捉變量var
[=]:表示值傳遞方式捕獲所有父作用域中的變量(包括this),如果指定某一個(gè)變量進(jìn)行值傳遞,[a,b]指明出來
[&var]:表示引用傳遞指定的捕捉變量var
[&]:表示引用傳遞捕捉所有父作用域中的變量(包括this)
[this]:表示值傳遞方式捕捉當(dāng)前的this指針
注意事項(xiàng):
a. 父作用域指包含lambda函數(shù)的語句塊
b. 語法上捕捉列表可由多個(gè)捕捉項(xiàng)組成,并以逗號(hào)分割。比如:[=, &a, &b]:以引用傳遞的方式捕捉變量a和b,值傳遞方式捕捉其他所有變量 [&,a, this]:值傳遞方式捕捉變量a和this,引用方式捕捉其他變量
c. 捕捉列表不允許變量重復(fù)傳遞,否則就會(huì)導(dǎo)致編譯錯(cuò)誤。 比如:[=, a]:=已經(jīng)以值傳遞方式捕捉了所有變量,捕捉a重復(fù)。
d. 在塊作用域以外的lambda函數(shù)捕捉列表必須為空。在全局中無法捕捉變量。
e. 在塊作用域中的lambda函數(shù)僅能捕捉父作用域中局部變量,捕捉任何非此作用域或者非局部變量都會(huì)導(dǎo)致編譯報(bào)錯(cuò)。
f. lambda表達(dá)式之間不能相互賦值,即使看起來類型相同
void (*PF)();
int main()
{
auto f1 = [] {cout << "hello world" << endl; };
auto f2 = [] {cout << "hello world" << endl; };
//f1 = f2; // 編譯失敗--->提示找不到operator=()
// 允許使用一個(gè)lambda表達(dá)式拷貝構(gòu)造一個(gè)新的副本
auto f3(f2);
f3();
// 可以將lambda表達(dá)式賦值給相同類型的函數(shù)指針
PF = f2;
PF();
return 0;
}
函數(shù)對(duì)象(仿函數(shù))與lambda表達(dá)式
函數(shù)對(duì)象,又稱為仿函數(shù),即可以像函數(shù)一樣使用的對(duì)象,就是在類中重載了operator()運(yùn)算符的類對(duì)象。
lambda表達(dá)式,底層原理其實(shí)是被處理成一個(gè)lambda_uuid的一個(gè)仿函數(shù)類。
class Rate
{
public:
Rate(double rate) : _rate(rate)
{}
double operator()(double money, int year)
{
return money * _rate * year;
}
private:
double _rate;
};
int main()
{
// 函數(shù)對(duì)象
double rate = 0.49;
Rate r1(rate);
r1(10000, 2);
// lamber
auto r2 = [=](double monty, int year)->double {return monty * rate * year; };
r2(10000, 2);
//cout << typeid(r2).name() <<endl;
return 0;
}
從使用方式上來看,函數(shù)對(duì)象與lambda表達(dá)式完全一樣。函數(shù)對(duì)象將rate作為其成員變量,在定義對(duì)象時(shí)給出初始值即可,lambda表達(dá)式通過捕獲列表可以直接將該變量捕獲到。
實(shí)際在底層編譯器對(duì)于lambda表達(dá)式的處理方式,完全就是按照函數(shù)對(duì)象的方式處理的,即:如果定義了一個(gè)lambda表達(dá)式,編譯器會(huì)**自動(dòng)生成一個(gè)類,在該類中重載了operator()。**仿函數(shù)對(duì)象去調(diào)用operator()。
五 關(guān)鍵字
auto
在定義變量時(shí),必須先給出變量的實(shí)際類型,編譯器才允許定義,但有些情況下可能不知道需要實(shí)際類型怎
么給,或者類型寫起來特別復(fù)雜。C++11中,可以使用auto來根據(jù)變量初始化表達(dá)式類型推導(dǎo)變量的實(shí)際類型,可以給程序的書寫提供許多方便。將程序中c與it的類型換成auto,程序可以通過編譯,而且更加簡(jiǎn)潔
decltype
auto使用的前提是:必須要對(duì)auto聲明的類型進(jìn)行初始化,否則編譯器無法推導(dǎo)出auto的實(shí)際類型。但有時(shí)候可能需要根據(jù)表達(dá)式運(yùn)行完成之后結(jié)果的類型進(jìn)行推導(dǎo),因?yàn)榫幾g期間,代碼不會(huì)運(yùn)行,此時(shí)auto也就無能為力。如果能用加完之后結(jié)果的實(shí)際類型作為函數(shù)的返回值類型就不會(huì)出錯(cuò),但這需要程序運(yùn)行完才能知道結(jié)果的實(shí)際類型,即RTTI(Run-Time Type Identifification 運(yùn)行時(shí)類型識(shí)別)。
C++98中確實(shí)已經(jīng)支持RTTI:typeid
只能查看類型不能用其結(jié)果類定義類型。dynamic_cast
只能應(yīng)用于含有虛函數(shù)的繼承體系中運(yùn)行時(shí)類型識(shí)別的缺陷是降低程序運(yùn)行的效率。
int func(int a)
{
return a;
}
int main()
{
int a = 10;
int b = 20;
// 用decltype推演a+b的實(shí)際類型,作為定義c的類型
decltype(a + b) c;
//C++98 const int ->int 會(huì)存在區(qū)別,decltype就不會(huì)存在
cout << typeid(c).name() << endl;
//聲明函數(shù)指針類型
int(*pfunc1)(int) = func;
auto pfunc2 = func;
decltype(pfunc2) pfunc3 = func;//和auto配合使用
decltype(&func) pfunc4 = func;
map<string, string> dict = { {"left","左邊"}};
auto it = dict.begin();
//decltype的使用場(chǎng)景:要頂一個(gè)auto推導(dǎo)對(duì)象的拷貝
decltype(it) copyIt1 = it;
auto copyIt2 = it;
//vector<auto>無法通過編譯
vector<decltype(it)> v;
v.push_back(it);
}
final-不讓繼承
override-檢查能否被重寫
default
? 在C++中對(duì)于空類編譯器會(huì)生成一些默認(rèn)的成員函數(shù),比如:構(gòu)造函數(shù)、拷貝構(gòu)造函數(shù)、運(yùn)算符重載、析構(gòu)函數(shù)和&和const&的重載、移動(dòng)構(gòu)造、移動(dòng)拷貝構(gòu)造等函數(shù)。如果在類中顯式定義了,編譯器將不會(huì)重新生成默認(rèn)版本。有時(shí)候這樣的規(guī)則可能被忘記,最常見的是聲明了帶參數(shù)的構(gòu)造函數(shù),必要時(shí)則需要定義不帶參數(shù)的版本以實(shí)例化無參的對(duì)象。而且有時(shí)編譯器會(huì)生成,有時(shí)又不生成,容易造成混亂,于是C++11讓程序員可以控制是否需要編譯器生成。顯示缺省函數(shù)。在C++11中,可以在默認(rèn)函數(shù)定義或者聲明時(shí)加上=default,從而顯式的指示編譯器生成該函數(shù)的默認(rèn)版本,用=default修飾的函數(shù)稱為顯式缺省函數(shù)。
class A
{
public:
A(int a): _a(a)
{}
// 顯式缺省構(gòu)造函數(shù),由編譯器生成
A() = default;
// 在類中聲明,在類外定義時(shí)讓編譯器生成默認(rèn)賦值運(yùn)算符重載
A& operator=(const A& a);
private:
int _a;
};
A& A::operator=(const A& a) = default;
int main()
{
A a1(10);
A a2;
a2 = a1;
return 0;
}
delete
刪除默認(rèn)函數(shù)。如果能想要限制某些默認(rèn)函數(shù)的生成,在C++98中,是該函數(shù)設(shè)置成private,并且不給定義,這樣只要其他人想要調(diào)用就會(huì)報(bào)錯(cuò)。在C++11中更簡(jiǎn)單,只需在該函數(shù)聲明加上=delete即可,該語法指示編譯器不生成對(duì)應(yīng)函數(shù)的默認(rèn)版本,稱=delete修飾的函數(shù)為刪除函數(shù).
比如:?jiǎn)卫J较拢?guī)定聲明的對(duì)象只能有一個(gè),這個(gè)對(duì)象是不允許被拷貝構(gòu)造其他的對(duì)象的,C++98 的方法一是,讓拷貝構(gòu)造函數(shù)私有化,在類的外面就調(diào)用不到了,但是呢,在類的里面中的某個(gè)函數(shù)還是可以調(diào)用的,同時(shí)造成默認(rèn)構(gòu)造函數(shù)就無法生成了。如果類的里面也不讓調(diào)用時(shí),也就是方法二:在私有中,只聲明但是不實(shí)現(xiàn)來防止拷貝。注意:如果不設(shè)置成私有的話,別人可以在類的外面實(shí)現(xiàn)拷貝構(gòu)造函數(shù),從而控制你提供的類做出修改。
C++11 就直接提供了關(guān)鍵字delete,在拷貝構(gòu)造函數(shù)的后面加上:delete 函數(shù)就變成了已刪除函數(shù),也無論私有與否了。
避免刪除函數(shù)和explicit一起使(explicit是防止函數(shù)進(jìn)行隱式類型轉(zhuǎn)換)
class A
{
public:
A(int a): _a(a)
{}
// 禁止編譯器生成默認(rèn)的拷貝構(gòu)造函數(shù)以及賦值運(yùn)算符重載
A(const A&) = delete;
A& operator(const A&) = delete;
private:
int _a;
};
int main()
{
A a1(10);
// 編譯失敗,因?yàn)樵擃悰]有拷貝構(gòu)造函數(shù)
//A a2(a1);
// 編譯失敗,因?yàn)樵擃悰]有賦值運(yùn)算符重載
A a3(20);
a3 = a2;
return 0;
}
六 模板的可變參數(shù)
- printf
int printf ( const char * format, ... );//可變參數(shù)
- 解析打印參數(shù)包中的類型和值
文章來源:http://www.zghlxwxcb.cn/news/detail-411304.html
//遞歸終止函數(shù)
template<class T>
void ShowList(const T& t)
{
cout << t <<endl;
}
template<class T,class ...Args>//Args模板參數(shù)包
void ShowList(T val, Args ... args)//args形參參數(shù)包
{
cout << typeid(val).name() << ":" << val << endl;
ShowList(args...);//遞歸依次到達(dá)下一個(gè)參數(shù)
}
int main()
{
ShowList(1);
ShowList(1, 'A');
ShowList(1,'A',string("sort"));
return 0;
}
逗號(hào)表達(dá)式展開參數(shù)包
文章來源地址http://www.zghlxwxcb.cn/news/detail-411304.html
//參數(shù)包,傳一個(gè)或者多個(gè)
template<class T>
void PrintArg(T val)
{
T copy(val);
cout << typeid(T).name() << ":" << val << endl;
}
template<class ...Args>
void ShowList(Args... args)
{
//{}列表初始化,開多大空間取決于可變參數(shù)個(gè)數(shù),依次取出參數(shù)包
//但是函數(shù)沒有返回值,創(chuàng)建數(shù)組需要元素有返回值,所以用逗號(hào)表達(dá)式,帶個(gè)0
int arr[] = {(PrintArg(args),0)...};
//逗號(hào)表達(dá)式,0是返回值。
cout << endl;
}
//帶返回值,不用逗號(hào)表達(dá)式
template<class T>
int PrintArg(T val)
{
T copy(val);
cout << typeid(T).name() << ":" << val << endl;
return 0;
}
template<class ...Args>
void ShowList(Args... args)
{
int arr[] = { PrintArg(args)... };
//0是返回值
cout << endl;
}
int main()
{
ShowList(1);
ShowList(1, 'A');
ShowList(1,'A',string("sort"));
return 0;
}
endl;
ShowList(args...);//遞歸依次到達(dá)下一個(gè)參數(shù)
}
int main()
{
ShowList(1);
ShowList(1, 'A');
ShowList(1,'A',string("sort"));
return 0;
}
到了這里,關(guān)于C++11 新功能的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!