国产亚洲精品福利在线无卡一,国产精久久一区二区三区,亚洲精品无码国模,精品久久久久久无码专区不卡

當(dāng)前位置: 首頁 > news >正文

淘寶里網(wǎng)站建設(shè)公司可以嗎地推十大推廣app平臺

淘寶里網(wǎng)站建設(shè)公司可以嗎,地推十大推廣app平臺,網(wǎng)站兼容視圖,做網(wǎng)站多少錢角西寧君博特惠目錄 C11簡介 統(tǒng)一的列表初始化 {}初始化 std::initializer_list 聲明 auto decltype nullptr 范圍for循環(huán) 智能指針 STL中的一些變化 右值引用和移動(dòng)語義 左值引用和右值引用 右值引用的意義 完美轉(zhuǎn)發(fā) lambda表達(dá)式 新的類功能 可變參數(shù)模版 包裝器 func…

目錄

C++11簡介

統(tǒng)一的列表初始化

{}初始化

std::initializer_list

聲明

auto

decltype

nullptr

范圍for循環(huán)

智能指針

STL中的一些變化

右值引用和移動(dòng)語義

左值引用和右值引用

右值引用的意義

完美轉(zhuǎn)發(fā)

lambda表達(dá)式

新的類功能

可變參數(shù)模版?

包裝器

function包裝器

bind

線程庫

線程函數(shù)參數(shù)

lock_guard和unique_lock

原子性操作庫

兩個(gè)線程交替打印


C++11簡介

相比于C++98/03,C++11發(fā)生了較大的變化,大約新增了約140個(gè)新特性,以及修正了約600個(gè)缺陷,這使得C++更加強(qiáng)大。

統(tǒng)一的列表初始化

{}初始化

在C++98中,允許使用花括號{}對數(shù)組或者結(jié)構(gòu)體元素進(jìn)行統(tǒng)一的列表初始值設(shè)定。比如:

struct Point
{int _x;int _y;
};int main()
{int array1[] = { 1,2,3,4,5 };Point p = { 1,2 };return 0;  
}

C++11擴(kuò)大了用大括號括起的列表的使用范圍,使其可用于所有的內(nèi)置類型和用戶自定義類型,列表初始化時(shí),可以添加等號(=),也可以不添加,但是不太建議去掉。

int arr1[]{ 1,2,3,4,5 };
int arr2[5]{ 0 };
Point p2{ 1,2 };

對于自定義類型創(chuàng)建對象時(shí)也可以用列表初始化方式調(diào)用構(gòu)造函數(shù)初始化:

class A
{
public:A(int x, int y):_x(x), _y(y){}A(int x):_x(x), _y(x){}
private:int _x;int _y;
};
int main()
{A a5(6);//單參數(shù)的隱式類型轉(zhuǎn)換A a3 = 1;A a4 {2};//多參數(shù)的隱式類型轉(zhuǎn)換//C++支持的列表初始化,這里{1,2}先調(diào)用構(gòu)造函數(shù)初始化,再調(diào)用拷貝構(gòu)造賦值給a1   A a1 = { 1,2 };A a2 { 3,4 };return 0;
}

std::initializer_list

//vector可以這樣初始化
vector<int> v1(10, 1);
//但是vector不能用{}初始化
vector<int> v2 = {1,2,3,4,5};

為什么vector用不了{(lán)}這樣構(gòu)造呢?{...}里面可能有1/2/3/4/....個(gè)元素,但是vector的構(gòu)造函數(shù)參數(shù)列表不知道到底該設(shè)置多少個(gè),要寫無數(shù)個(gè)構(gòu)造函數(shù)才行,就像下面這樣:

所以,為了支持這樣的初始化,引入了initializer_list,

因此,我們現(xiàn)在可以將{...}的常量數(shù)組轉(zhuǎn)化為initializer_list。

initializer_list的本質(zhì)是兩個(gè)指針,一個(gè)first指向第一個(gè)元素,一個(gè)last指向最后一個(gè)元素的下一個(gè),為了驗(yàn)證這樣一個(gè)事實(shí),我們算一下i1的大小,在32位平臺下,i1的大小是8(2個(gè)指針的大小):

initializer_list也支持簡單的迭代器begin和end,迭代器的類型是const T*,由于它指向常量數(shù)組,所以是const T*,不支持修改。?

所以,為了解決本節(jié)剛開始提出的問題,C++11使用了這樣的方式解決:

vector(initializer_list<T> i1);

這個(gè)構(gòu)造一勞永逸的解決了問題,不用像上面那樣麻煩的方式解決。

initializer_list可以接收常量數(shù)組,本質(zhì)是兩個(gè)指針,一個(gè)指向數(shù)組的開始,一個(gè)指向數(shù)組最后的下一個(gè)。這樣,就算{...}里有5個(gè)8個(gè),都可以傳給initializer_list。

當(dāng)? X自定義 = Y類型,這個(gè)時(shí)候就會隱式類型轉(zhuǎn)換,需要有 X(Y mm),即X支持Y為參數(shù)類型的構(gòu)造就可以。

在上面圖中,{1,2,3,4,5}會被識別成initializer_list,下面的那個(gè),等號右側(cè)生成一個(gè)臨時(shí)對象,再去拷貝構(gòu)造給v4。??

再舉一個(gè)例子,創(chuàng)建map對象時(shí),也可以用{}進(jìn)行創(chuàng)建:

聲明

auto

在C++11中,auto用于自動(dòng)推斷類型,這樣要求必須進(jìn)行顯示初始化。

int main()
{int i = 10;auto p = &i;cout << typeid(p).name() << endl;map<string, string> dict = { {"sort", "排序"}, {"insert", "插入"} };//map<string, string>::iterator it = dict.begin();auto it = dict.begin();return 0;
}

decltype

關(guān)鍵字decltype將變量的類型聲明為表達(dá)式指定的類型。它的功能和typeid有點(diǎn)像,typeid可以拿到對象的類型,但是得到的只是一個(gè)單純的字符串,不能用來定義對象,如果想定義一個(gè)和typeid一樣的值,這就做不到了。

但是decltype可以做到:

但是,有人可能會認(rèn)為,我用一個(gè)auto不也可以嗎?但是,我們換一個(gè)場景:

我想用ret3的返回值類型去初始化class B的模版參數(shù),這時(shí)候typeid就不行了,但是可以用decltype來確定ret3的類型,

B<decltype(ret3)> b;

雖然auto和decltype雖然可以方便我們寫代碼,但是有些地方增加代碼讀起來的難度,所以慎用auto和decltype!

nullptr

由于C++中NULL被定義成字面量0,這樣就可能回帶來一些問題,因?yàn)?既能指針常量,又能表示整形常量。所以出于清晰和安全的角度考慮,C++11中新增了nullptr,用于表示空指針。

范圍for循環(huán)

我們在之前的學(xué)習(xí)中,已經(jīng)多次使用了范圍for,這里不再贅述。

只有一點(diǎn)需要注意,我們最好把&加上,即for(auto& e:v)或者for(const auto& e:v)(在不改變值的情況下加const),這樣可以防止深拷貝,提高效率。

智能指針

我們之后單獨(dú)學(xué)習(xí)這塊。

STL中的一些變化

新容器?

array:對越界檢查更嚴(yán)格,但是可以用vector來替代,所以用處不大,是一個(gè)挺失敗的設(shè)計(jì)。

forward_list:單向鏈表,相比list節(jié)省一個(gè)指針,但是也不好用。

容器內(nèi)部

幾乎在每個(gè)容器內(nèi)部,都增加了initializer_list、移動(dòng)構(gòu)造(這個(gè)很有用),

還有emplace、empalce_back:

右值引用和移動(dòng)語義

左值引用和右值引用

傳統(tǒng)的C++語法中就有引用的語法,而C++11中新增了的右值引用語法特性,所以從現(xiàn)在開始我們之前學(xué)習(xí)的引用就叫做左值引用。無論左值引用還是右值引用,都是給對象取別名

什么是左值和右值

左值和右值的區(qū)分關(guān)鍵,是能否取地址??梢匀〉刂返氖亲笾?#xff0c;不能取地址的是右值。

因此,a、b、c均是左值。

左值不一定是值,也可能是表達(dá)式。如*p也是左值,因?yàn)樗梢匀〉刂贰?/p>

類似的,v[1]也是左值,它可以取地址。

右值也是一個(gè)表達(dá)數(shù)據(jù)的表達(dá)式,它不能取地址,如字面常量、匿名對象、臨時(shí)對象。

什么是左值引用和右值引用

引用就是取別名,左值引用就是給左值取別名,右值引用就是給右值取別名,

右值引用就是用&&表示:?

自定義的類型右值主要有兩種,一類是匿名對象,一類是傳值返回的臨時(shí)對象。

左值引用不能給右值取別名,但是const 左值引用可以,

const string& ref1 = string("1111");

右值引用也不能給左值取別名,但是可以給move以后得左值取別名。

左值引用總結(jié):

1. 左值引用只能引用左值,不能引用右值。

2. 但是const左值引用既可引用左值,也可引用右值。

右值引用總結(jié):

1. 右值引用只能右值,不能引用左值。

2. 但是右值引用可以move以后的左值。

右值引用的意義

引用的意義是減少拷貝,提高效率, 比如引用傳參

void func1(const string& s);

如果不用引用,那傳參時(shí)就要拷貝,像map這種大一點(diǎn)的容器拷貝的代價(jià)很大,

還有傳引用返回

string& func2();

左值引用的場景主要就是引用傳參和傳引用返回。但是,左值引用返回值的問題沒有徹底解決,因?yàn)?span style="color:#fe2c24">如果是func2中局部對象,不能用引用返回,它的生命周期就在func2里面,出了作用域就銷毀了,同時(shí),也不能用右值引用返回,右值引用也不能解決生命周期的問題,

例如,bit::string to_string(int value)函數(shù)中可以看到,這里只能使用傳值返回,傳值返回會導(dǎo)致至少1次拷貝構(gòu)造(如果是一些舊一點(diǎn)的編譯器可能是兩次拷貝構(gòu)造)。

to_string的返回值用左值引用和右值引用都不太行,因?yàn)榉祷刂祍tr出了作用域都會被銷毀,在C++11中,另辟蹊徑,增加了一個(gè)參數(shù)為右值引用的構(gòu)造函數(shù)--移動(dòng)構(gòu)造,(對比:參數(shù)為左值引用的構(gòu)造函數(shù)為拷貝構(gòu)造),移動(dòng)構(gòu)造拷貝構(gòu)造構(gòu)成函數(shù)重載。

在沒有移動(dòng)構(gòu)造時(shí),拷貝構(gòu)造string(const string& s)既可以接收左值,又可以接收右值;但是在有了移動(dòng)構(gòu)造時(shí),編譯器會找最匹配的。

在C++11中,將右值做了區(qū)分,分為純右值將亡值,內(nèi)置類型的那種是純右值,自定義類型的那種是將亡值,在移動(dòng)構(gòu)造中,可以這樣寫:

//移動(dòng)構(gòu)造
//右值(將亡值)
string(string&& s):_str(nullptr)
{cout << "string(string&& s) -- 移動(dòng)構(gòu)造" << endl;swap(s);
}

既然已經(jīng)是將亡值,那么不妨把我的和將亡值交換一下,移動(dòng)將亡值的資源,這樣就不用拷貝構(gòu)造了,效率就上來了? ??

有人說右值引用延長了str的生命周期,這種說法是不正確的,str出了作用域就會被銷毀,確切的說是延長了資源的生命周期。

如果是下面這種形式,如果沒有移動(dòng)拷貝和移動(dòng)賦值,那就是一次拷貝構(gòu)造和一次賦值拷貝,編譯器不敢將這兩步合二為一(因?yàn)檫@是兩個(gè)不同的操作),

如果有了移動(dòng)拷貝和移動(dòng)賦值,那就是一次移動(dòng)拷貝和一次移動(dòng)賦值,

移動(dòng)構(gòu)造、移動(dòng)賦值和我們之前拷貝構(gòu)造的現(xiàn)代寫法有點(diǎn)像,但是這兩種有本質(zhì)的區(qū)別,現(xiàn)代寫法是讓一個(gè)臨時(shí)對象去構(gòu)造并轉(zhuǎn)移它的資源,并沒有提高效率,而移動(dòng)構(gòu)造、移動(dòng)賦值給我的右值就是一個(gè)將亡值,直接轉(zhuǎn)移這個(gè)將亡值的資源,代價(jià)很小。

我們再來看一些關(guān)于右值引用的其他問題:

我們知道,std::string("111111111111")本身是右值,但是右值引用本身(s1)是左值,因?yàn)橹挥杏抑狄帽旧硖幚沓勺笾?#xff0c;才能實(shí)現(xiàn)移動(dòng)構(gòu)造和移動(dòng)賦值,轉(zhuǎn)移資源(右值不能轉(zhuǎn)移資源)。這樣的意思,是為了移動(dòng)構(gòu)造和移動(dòng)賦值的轉(zhuǎn)移資源的邏輯是自洽的。

我們來看一下C++11中其他地方用到右值引用的,

由于s1是左值,所以push_back不能調(diào)用移動(dòng)拷貝,只能做深拷貝,

但是,如果想上圖這樣,將匿名對象放到push_back的參數(shù)中,就會調(diào)用移動(dòng)構(gòu)造。在push_back這類函數(shù)時(shí),使用匿名對象就更好了,這樣就不會設(shè)計(jì)到深拷貝,只需要移動(dòng)構(gòu)造,提高效率。可見,右值引用不僅在返回值有意義,也在參數(shù)值有意義。

也可以這樣,這樣就會先發(fā)生隱式類型轉(zhuǎn)換,將const char*轉(zhuǎn)換為string,轉(zhuǎn)換時(shí)會產(chǎn)生臨時(shí)對象,是右值,臨時(shí)對象具有常性,會調(diào)用移動(dòng)構(gòu)造。

最后一個(gè)問題,如果list里的值類型是int(內(nèi)置類型)或者日期類(淺拷貝自定義類型),還會涉及到移動(dòng)拷貝和移動(dòng)構(gòu)造嗎?

不會涉及,上面效率提升,針對的是自定義類型的深拷貝的類,因?yàn)樯羁截惖念惒庞修D(zhuǎn)移資源的移動(dòng)系列函數(shù);對于內(nèi)置類型和淺拷貝自定義類型,沒有移動(dòng)系列函數(shù)。

完美轉(zhuǎn)發(fā)

模版中的&&萬能轉(zhuǎn)發(fā)

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; }//函數(shù)模版里面,這里叫做萬能引用
template<typename T>
void PerfectForward(T&& t)
{Func(t);
}void PerfectForward(int& 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;
}

函數(shù)模板中的&&不代表右值引用,而是萬能引用,其既能接收左值又能接收右值。模板的萬能引用只是提供了能夠接收同時(shí)接收左值引用和右值引用的能力。

但是,我們看程序運(yùn)行的結(jié)果,

因?yàn)樵诤瘮?shù)參數(shù)t接收后,后續(xù)都退化成了左值,所以都會調(diào)用Fun(int& x)和Fun(const int& x)這兩個(gè)函數(shù),但是這不是我們想要的結(jié)果,我們希望能夠在傳遞的過程中保持它的左值或者右值的屬性,那么就需要用到完美轉(zhuǎn)發(fā)

std::forward 完美轉(zhuǎn)發(fā)在傳參的過程中保留對象原生類型屬性。

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; }//函數(shù)模版里面,這里叫做萬能引用
//實(shí)參傳左值,就推成左值引用,實(shí)參傳右值,就推成右值引用
template<typename T>
void PerfectForward(T&& t)
{//std::forward<T>(t)在傳參的過程中保持了t的原生類型屬性Fun(std::forward<T>(t));
}void PerfectForward(int& 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;
}

什么時(shí)候用完美轉(zhuǎn)發(fā)呢?在函數(shù)模版里,想要達(dá)到傳什么就保持它的屬性就用完美轉(zhuǎn)發(fā)。這是一道常見的面試題。

lambda表達(dá)式

在C++98中,如果想要對一個(gè)自定義類型進(jìn)行排序,需要用到仿函數(shù),用戶自定義排序的比較規(guī)則,比如,有這樣一個(gè)自定義類型,

struct Goods
{string _name; // 名字double _price; // 價(jià)格int _evaluate; // 評價(jià)Goods(const char* str, double price, int evaluate):_name(str), _price(price), _evaluate(evaluate){}
};

我們有存儲Goods類型的vector,想要對這個(gè)vector按照價(jià)格排序,既可以排升序,又可以排降序,可以使用algorithm這個(gè)頭文件中的sort算法,但是如果需要對商品按照它的某一項(xiàng)屬性排序,如價(jià)格,就需要自己寫一個(gè)類來定義仿函數(shù),

struct Goods
{string _name; // 名字double _price; // 價(jià)格int _evaluate; // 評價(jià)Goods(const char* str, double price, int evaluate):_name(str), _price(price), _evaluate(evaluate){}
};struct ComparePriceLess
{bool operator()(const Goods& g1, const Goods& g2){return g1._price < g2._price;}
};struct ComparePriceGreater
{bool operator()(const Goods& g1, const Goods& g2){return g1._price > g2._price;}
};int main()
{vector<Goods> v = { {"蘋果",2.3,2},{"香蕉",3.2,4}, {"西瓜",0.9,3} };sort(v.begin(), v.end(), ComparePriceGreater());return 0;
}

但是,上面的方法有點(diǎn)復(fù)雜,每次都要自己寫一個(gè)類,所以C++為了解決這樣的問題,引入了lambda表達(dá)式

lambda表達(dá)式實(shí)際是一個(gè)匿名函數(shù),lambda表達(dá)式書寫格式:

lambda表達(dá)式書寫格式:[capture-list] (parameters) mutable -> return-type { statement }

各部分說明:

????????[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í)此部分可省略。返回值類型明確情況下,也可省略,由編譯器對返回類型進(jìn)行推導(dǎo)。

????????{statement}:函數(shù)體。在該函數(shù)體內(nèi),除了可以使用其參數(shù)外,還可以使用所有捕獲到的變量。

注意:在lambda函數(shù)定義中,參數(shù)列表和返回值類型都是可選部分,而捕捉列表和函數(shù)體可以為空。因此C++11中最簡單的lambda函數(shù)為:[]{}; 該lambda函數(shù)不能做任何事情。

對捕捉列表和mutable的進(jìn)一步說明:

為了解決傳值捕捉在函數(shù)內(nèi)部改變不會影響外面的問題,引入了引用捕捉,引用捕捉是在捕捉列表變量前加&(這其實(shí)和取地址&有些沖突,但是在lambda表達(dá)式捕捉列表中我們認(rèn)為&是引用捕捉)?:??

?捕捉列表描述了上下文中哪些數(shù)據(jù)可以被lambda使用,以及使用的方式傳值還是傳引用。

[var]:表示傳值捕捉變量var

[=]:表示傳值方式捕捉父作用域的所有變量

[&var]:表示引用方式捕捉變量var

[&]:表示引用方式捕捉父作用域的所有變量

[this]:表示傳值方式捕捉當(dāng)前的this指針

?除了上面?zhèn)髦岛蛡饕貌蹲椒绞揭酝?#xff0c;還有混合捕捉(一部分傳值捕捉,一部分傳引用捕捉):

因此,我們可以寫lambda表達(dá)式作為一個(gè)匿名函數(shù)傳給sort進(jìn)行排序:

int main()
{vector<Goods> v = { {"蘋果",2.3,2},{"香蕉",3.2,4}, {"西瓜",0.9,3} };auto priceLess = [](const Goods g1, const Goods g2)->bool {return g1._price < g2._price; };sort(v.begin(), v.end(), priceLess);//也可以直接傳lambda表達(dá)式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;});sort(v.begin(), v.end(), [](const Goods g1, const Goods g2){return g1._name < g2._name;});sort(v.begin(), v.end(), [](const Goods g1, const Goods g2){return g1._name > g2._name;});return 0;
}

其中,priceLess的類型,我不知你不知只有編譯器知,見下圖,是隨機(jī)生成的類型。

其實(shí),lambda表達(dá)式就是仿函數(shù),lambda編譯時(shí),會生成對應(yīng)的仿函數(shù),上面的隨機(jī)名字就是仿函數(shù)類的名稱。

實(shí)際上,lambda表達(dá)式和范圍for很類似,范圍for替代成了迭代器,lambda替代成了仿函數(shù)。

新的類功能

在前面我們學(xué)習(xí)了右值引用,其實(shí),右值引用的特點(diǎn)是和左值引用的特點(diǎn)進(jìn)行了區(qū)分,是左值就匹配左值引用,是右值就匹配右值引用。移動(dòng)語義是,當(dāng)右值匹配到右值引用的時(shí)候,會調(diào)用移動(dòng)構(gòu)造和移動(dòng)拷貝。它們針對的是深拷貝的自定義類型對象,如string、vector、list等,可以轉(zhuǎn)移資源,提高效率。

原來的C++類中,有6個(gè)默認(rèn)成員函數(shù),構(gòu)造函數(shù)、析構(gòu)函數(shù)、拷貝構(gòu)造函數(shù)、拷貝賦值重載、取地址重載、const取地址重載,前4個(gè)最重要,后兩個(gè)用處不大。

C++11 新增了兩個(gè):移動(dòng)構(gòu)造函數(shù)和移動(dòng)賦值運(yùn)算符重載。

對于移動(dòng)構(gòu)造函數(shù)和移動(dòng)賦值運(yùn)算符重載,有一些需要注意的:

如果你沒有自己實(shí)現(xiàn)移動(dòng)構(gòu)造函數(shù),且沒有實(shí)現(xiàn)析構(gòu)函數(shù) 、拷貝構(gòu)造、拷貝賦值重載中的任意一個(gè)。那么編譯器會自動(dòng)生成一個(gè)默認(rèn)移動(dòng)構(gòu)造。默認(rèn)生成的移動(dòng)構(gòu)造函數(shù),對于內(nèi)置類型成員會執(zhí)行逐成員按字節(jié)拷貝,自定義類型成員,則需要看這個(gè)成員是否實(shí)現(xiàn)移動(dòng)構(gòu)造,如果實(shí)現(xiàn)了就調(diào)用移動(dòng)構(gòu)造,沒有實(shí)現(xiàn)就調(diào)用拷貝構(gòu)造。

如果你沒有自己實(shí)現(xiàn)移動(dòng)賦值重載函數(shù),且沒有實(shí)現(xiàn)析構(gòu)函數(shù) 、拷貝構(gòu)造、拷貝賦值重載中的任意一個(gè),那么編譯器會自動(dòng)生成一個(gè)默認(rèn)移動(dòng)賦值。默認(rèn)生成的移動(dòng)構(gòu)造函數(shù),對于內(nèi)置類型成員會執(zhí)行逐成員按字節(jié)拷貝,自定義類型成員,則需要看這個(gè)成員是否實(shí)現(xiàn)移動(dòng)賦值,如果實(shí)現(xiàn)了就調(diào)用移動(dòng)賦值,沒有實(shí)現(xiàn)就調(diào)用拷貝賦值。(默認(rèn)移動(dòng)賦值跟上面移動(dòng)構(gòu)造完全類似)。

如果提供了移動(dòng)構(gòu)造或者移動(dòng)賦值,編譯器不會自動(dòng)提供拷貝構(gòu)造和拷貝賦值。

?我們調(diào)試以下代碼可以得到驗(yàn)證:

class Person
{
public:Person(const char* name = "張三", int age = 18):_name(name), _age(age){}/*~Person(){}*/
private:ghs::string _name;int _age;
};int main()
{Person s1;//默認(rèn)拷貝構(gòu)造Person s2 = s1;Person s3 = std::move(s1);Person s4;s4 = std::move(s2);return 0;
}

?強(qiáng)制生成默認(rèn)函數(shù)的關(guān)鍵字default

C++可以更好地控制要使用的默認(rèn)函數(shù)。假設(shè)要使用某個(gè)默認(rèn)的函數(shù),但是因?yàn)槟承┰蜻@個(gè)函數(shù)沒有默認(rèn)生成。比如:當(dāng)我們提供了拷貝構(gòu)造,就不會生成移動(dòng)構(gòu)造,那么就可以使用default關(guān)鍵字指定移動(dòng)構(gòu)造生成。

class Person
{
public:Person(const char* name = "張三", int age = 18):_name(name), _age(age){}//強(qiáng)制生成Person(Person& p) = default;Person& operator=(Person& p) = default;Person(Person&& p) = default;Person& operator=(Person&& p) = default;~Person(){}
private:ghs::string _name;int _age;
};int main()
{Person s1;//默認(rèn)拷貝構(gòu)造Person s2 = s1;Person s3 = std::move(s1);Person s4;s4 = std::move(s2);return 0;
}

禁止生成默認(rèn)函數(shù)的關(guān)鍵字delete:

?如果能想要限制某些默認(rèn)函數(shù)的生成,在C++98中,是該函數(shù)設(shè)置成private,并且只聲明補(bǔ)丁已,這樣只要其他人想要調(diào)用就會報(bào)錯(cuò)。在C++11中更簡單,只需在該函數(shù)聲明加上=delete即可,該語法指示編譯器不生成對應(yīng)函數(shù)的默認(rèn)版本,稱=delete修飾的函數(shù)為刪除函數(shù)。

繼承和多態(tài)中的final與override關(guān)鍵字
這個(gè)我們在繼承和多態(tài)章節(jié)已經(jīng)進(jìn)行了詳細(xì)學(xué)習(xí)。

可變參數(shù)模版?

在C++11之前,模版中的參數(shù)數(shù)量是固定的,但是在C++11中引入了可變參數(shù)模版,這無疑是一大進(jìn)步。

我們先來看一個(gè)基本可變參數(shù)的函數(shù)模版:

template <class ...Args>
void ShowList(Args... args)
{}

其中,Args是一個(gè)模版參數(shù)包,代表0~N個(gè)類型,args是根據(jù)模版參數(shù)包定義的一個(gè)形參參數(shù)包。上面的參數(shù)Args前面有省略號,所以它是一個(gè)可變模版參數(shù),我們把帶省略號的參數(shù)成為參數(shù)包,包含0~N個(gè)模版參數(shù)。但是我們無法直接獲取參數(shù)包args中的每個(gè)參數(shù),只能通過展開參數(shù)包的方式來獲取參數(shù)包中的每個(gè)參數(shù)。由于語法不支持args[i]的方式獲取可變參數(shù),所以我們用一些方法來獲取參數(shù)包的每個(gè)值。

遞歸函數(shù)展開參數(shù)包

void _CppPrintf()
{cout << endl;
}
template<class T,class ... Args>
void _CppPrintf(const T& val, Args... args)
{cout << val << endl;_CppPrintf(args...);
}
template <class ... Args>
void CppPrintf(Args... args)
{_CppPrintf(args...);
}
int main()
{CppPrintf(1, 'A', std::string("sort"));return 0;
}

其編譯時(shí)遞歸推導(dǎo)過程如下:?

還有另外一個(gè)奇葩的推導(dǎo)方式

STL中的emplace相關(guān)接口函數(shù)

可以看出,emplace_back對深拷貝類型有一定的優(yōu)化,但是不那么明顯,效率沒有提升很多

其實(shí),emplace_back對需要淺拷貝的效率更高,因?yàn)閜ush_back淺拷貝類型,只能調(diào)用拷貝構(gòu)造,即需要一次構(gòu)造+一次拷貝構(gòu)造,而emplace_back只需要調(diào)用一次構(gòu)造就行。

總之,emplace_back可以直接構(gòu)造,而無需調(diào)用拷貝構(gòu)造!

當(dāng)然,emplace_back除了上面的用多個(gè)參數(shù)進(jìn)行構(gòu)造,也可以用單參數(shù)構(gòu)造,

因此,當(dāng)使用emplace時(shí),實(shí)參建議的選擇順序是:參數(shù)包 > 右值 > 左值。

包裝器

function包裝器

function包裝器,也叫做適配器。C++中function本質(zhì)是一個(gè)類模版,也是一個(gè)包裝器。

ret = func(x);

上面的func可能是什么呢?可能是函數(shù)名、函數(shù)指針、仿函數(shù)或者lambda表達(dá)式,這些都是可調(diào)用對象

但是,它們都多少有些問題:

函數(shù)指針? ? ? ?->? ? 類型定義復(fù)雜

仿函數(shù)對象? ?->? ? 要定義一個(gè)類,用的時(shí)候有點(diǎn)麻煩,不適合類型統(tǒng)一

lambda? ? ? ? ?->? ? 沒有類型概念? ? ?

template<class F, class T>
T useF(F f, T x)
{static int count = 0;cout << "count:" << ++count << endl;cout << "count:" << &count << endl;return f(x);
}
double f(double i)
{return i / 2;
}
struct Functor
{double operator()(double d){return d / 3;}
};
int main()
{// 函數(shù)名cout << useF(f, 11.11) << endl;// 函數(shù)對象cout << useF(Functor(), 11.11) << endl;// lamber表達(dá)式cout << useF([](double d)->double { return d / 4; }, 11.11) << endl;return 0;
}

運(yùn)行以上程序,發(fā)現(xiàn)useF函數(shù)模版實(shí)例化了三份,這看起來有點(diǎn)麻煩,為了解決以上問題,引出了包裝器。

std::function在頭文件<functional>

//類模版原型
template <class Ret, class... Args>
class function<Ret(Args...)>;

模版參數(shù)說明:

Ret被調(diào)用函數(shù)返回值

Args被調(diào)用函數(shù)的形參

實(shí)際上,function的底層是仿函數(shù),在調(diào)用時(shí)會調(diào)用operator()。

int f(int a, int b)
{return a + b;
}
struct Functor
{
public:int operator() (int a, int b){return a + b;}
};//不是定義可調(diào)用對象,而是包裝可調(diào)用對象
int main()
{//空對象function<int(int, int)> fc1;//包裝函數(shù)指針function<int(int, int)> fc2 = f;//包裝仿函數(shù)對象function<int(int, int)> fc3 = Functor();//包裝lambdafunction<int(int, int)> fc4 = [](int x, int y) {return x + y; };cout << fc2(1, 2) << endl;//fc2本質(zhì)是調(diào)用了operator()cout << fc2.operator()(1, 2) << endl;cout << fc3(1, 2) << endl;cout << fc4(1, 2) << endl;return 0;
}

那我們不禁要問,為什么要給函數(shù)指針、仿函數(shù)、lambda外面套一個(gè)殼再使用呢,它們本身也是可以調(diào)用的啊。

包裝器的一些玩法:逆波蘭表達(dá)式求解

class Solution {
public:int evalRPN(vector<string>& tokens) {stack<int> st;//命令->動(dòng)作(函數(shù))map<string,function<int(int,int)>> m={{"+",[](int x,int y){return x+y;}},{"-",[](int x,int y){return x-y;}},{"*",[](int x,int y){return x*y;}},{"/",[](int x,int y){return x/y;}},};for(auto& e : tokens){if(m.count(e)) {function<int(int,int)> f = m[e];//操作符運(yùn)算int right = st.top();st.pop();int left = st.top();st.pop();st.push(f(left,right));}else{//操作數(shù)入棧st.push(stoi(e));}} return st.top();   }
};

另外,如果我們想要包裝成員函數(shù)指針,需要&類型::函數(shù)名這樣調(diào)用。成員函數(shù)指針又分靜態(tài)成員函數(shù)和非靜態(tài)成員函數(shù):

int main()
{//成員函數(shù)的函數(shù)指針 &類型::函數(shù)名//包裝靜態(tài)成員函數(shù)function<int(int, int)> f1 = &Plus::plusi;cout << f1(1, 2) << endl;//包裝非靜態(tài)成員函數(shù),不能直接Plus::plusd,需要在前面加取地址符&,靜態(tài)函數(shù)前可加可不加&/*function<double(double, double)> f2 = &Plus::plusd;cout << f2(1.1, 2.2) << endl;*///包裝器的參數(shù)要和成員函數(shù)的參數(shù)一致,成員函數(shù)第一個(gè)參數(shù)是一個(gè)隱含的this指針function<double(Plus*, double, double)> f3 = &Plus::plusd;Plus plus;cout << f3(&plus,1.1, 2.2) << endl;//但是,上面包裝非靜態(tài)的成員函數(shù)有點(diǎn)麻煩,還需要定義一個(gè)類對象//是通過指針&plus或者對象Plus()去調(diào)用plusd,所以這里傳指針和對象都可以function<double(Plus, double, double)> f4 = &Plus::plusd;cout << f4(Plus(), 1.1, 2.2) << endl;return 0;
}

bind

std::bind是一個(gè)函數(shù)模版,用于調(diào)整可調(diào)用對象的參數(shù)個(gè)數(shù)或者順序

bind函數(shù)可以看做一個(gè)通用的函數(shù)適配器,它接受一個(gè)可調(diào)用對象,生成一個(gè)新的可調(diào)用對象來適應(yīng)原對象的參數(shù)列表。

調(diào)用bind的一般形式:auto newCallable = bind(callable,arg_list);

?newCallable本身是一個(gè)可調(diào)用對象,arg_list是一個(gè)逗號分隔的參數(shù)列表,給callable提供實(shí)參,當(dāng)我們調(diào)用newCallable時(shí),newCallable會調(diào)用callable,并傳給它arg_list中的參數(shù)

fn是要綁定的函數(shù),args是要對應(yīng)的參數(shù),包含像_1、_2這樣的名字,這些名字是占位符,_1為newCallable中第一個(gè)參數(shù),_2為newCallable中第二個(gè)參數(shù),依次類推。Ret是返回值的類型,我們可以顯示實(shí)例化bind,以此來控制返回值的類型。

但是,調(diào)整順序的意義不大,了解即可。

除了調(diào)整順序以外,我們還可以調(diào)整參數(shù)個(gè)數(shù):

class Sub
{
public:int sub(int a, int b){return a - b;}
};int main()
{//綁定,調(diào)整參數(shù)個(gè)數(shù),把第一個(gè)參數(shù)用Sub()綁定死auto f4 = bind(&Sub::sub, Sub(), placeholders::_1, placeholders::_2);cout << f4(10, 5);cout << endl;auto f5 = bind(&Sub::sub, &sub, placeholders::_1, placeholders::_2);cout << f5(10, 5);return 0;
}

這樣,我們得到綁定后的對象為f4,調(diào)用f4時(shí),只需要傳兩個(gè)未綁定的參數(shù)a和b即可。

再來看一個(gè)例子:

void fx(const string& s, int x, int y)
{cout << s << " -> [血量:" << x << "  藍(lán):" << y <<"]" << endl;
}
int main()
{fx("趙云", 80, 46);fx("趙云", 78, 34);fx("趙云", 54, 13);return 0;
}

我們可以這樣調(diào)fx,這很正常,但是調(diào)fx時(shí)每次都要傳“趙云”這個(gè)參數(shù),其實(shí),我們可以綁定第一個(gè)參數(shù)一直是“趙云”,這樣只需要輸入?yún)?shù)x和y即可:

int main()
{auto f1 = bind(fx1, "趙云", placeholders::_1, placeholders::_2);f1(100, 89);f1(98, 76);return 0;
}

除了綁定第一個(gè)參數(shù),我們還可以綁定第二個(gè)參數(shù),比如在游戲中開掛,無論使用哪個(gè)角色,血量一直保持在100:

int main()
{auto f2 = bind(fx1, placeholders::_1, 100, placeholders::_2);f2("孫尚香", 46);f2("關(guān)羽", 93);return 0;
}

我們需要記住的是,_1代表第一個(gè)實(shí)參,_2代表第二個(gè)實(shí)參。

實(shí)際上,bind的返回值是一個(gè)類,里面重載了operator(),實(shí)際上會調(diào)用仿函數(shù)。

bind的返回除了傳給auto,也可以傳給function(因?yàn)閎ind的返回值是一個(gè)類,里面重載了仿函數(shù)),

線程庫

我們之前了解的多線程問題,都是和平臺相關(guān)的,比如windows和linux下都有自己的接口,這使得代碼的可移植性比較差。C++11中最重要的特性就是對線程進(jìn)行支持了,使得C++在多線程編程時(shí)不需要依賴第三方庫。要使用標(biāo)準(zhǔn)庫中的線程,必須包含<thread>頭文件。

注意:

1.當(dāng)創(chuàng)建線程對象后,如果使用無參構(gòu)造,沒有提供線程函數(shù),該對象沒有對應(yīng)任何線程。

get_id()的返回值類型是id類型,id類型為std::thread命名空間下瘋轉(zhuǎn)的一個(gè)類。

2.當(dāng)創(chuàng)建一個(gè)線程對象后,并且給線程關(guān)聯(lián)函數(shù),該線程就會被啟動(dòng),與主線程一起運(yùn)行。線程函數(shù)一般有3種提供方式:

  • 函數(shù)指針
  • lambda表達(dá)式
  • 函數(shù)對象

函數(shù)指針:

void print(int n, int k)
{for (int i = k; i < n; i++){std::cout << i << " ";}std::cout << std::endl;
}int main()
{std::thread t1(print, 100, 0);std::thread t2(print, 200, 100);std::cout << t1.get_id() << std::endl;std::cout << t2.get_id() << std::endl;t1.join();t2.join();std::cout << std::this_thread::get_id << std::endl;return 0;
}

lambda表達(dá)式:

int main()
{int x = 0;std::mutex mtx;std::thread t1([&] {mtx.lock();for (int i = 0; i < 10000; i++){++x;}mtx.unlock();});std::thread t2([&]{mtx.lock();for (int i = 0; i < 10000; i++){++x;}mtx.unlock();});t1.join();t2.join();std::cout << x << std::endl;return 0;
}

可以使用this_thread類中的get_id來獲取主線程id。

3.線程不支持拷貝,不允許拷貝構(gòu)造和賦值,但是支持移動(dòng)構(gòu)造和移動(dòng)賦值。

線程函數(shù)參數(shù)

線程函數(shù)參數(shù)是以值拷貝的方式拷貝到線程棧空間中的,因此,即使線程參數(shù)為引用類型,在線程中也不能修改外部實(shí)參,因?yàn)槠鋵?shí)際引用的是線程棧中的拷貝,而不是外部實(shí)參。

如果想要通過形參改變外部實(shí)參,必須借助std::ref()函數(shù)或者傳入指針。

lock_guard和unique_lock

在多線程中,為了保證線程安全,需要通過鎖的方式控制。?

?如果把鎖放到for循環(huán)里面,也是線程安全的,但是這樣會導(dǎo)致線程之間頻繁切換,效率低。

上述代碼中,其缺陷是,鎖控制不好時(shí),可能會造成死鎖,比如在鎖中間代碼返回,或者在鎖的范圍內(nèi)拋異常。因此,C++采用RAII的方式對鎖進(jìn)行了封裝,即lock_guard和unique_lock。

那如果在func中前半部分希望加鎖,而后半部分不希望加鎖,只需要用{}把前半部分括住,定義一個(gè)局部域,LockGuard的生命周期就在這個(gè){}局部域了。

而unique_lock和lock_guard類似,只不過功能更豐富一些,支持手動(dòng)加鎖解鎖。

原子性操作庫

傳統(tǒng)解決線程安全的方法是對共享資源進(jìn)行加鎖保護(hù),雖然加鎖可以解決問題,但是加鎖的缺陷是,只要要有一個(gè)線程在對sum++,其他線程就會被阻塞,影響效率,而且可能造成死鎖。

為此,C++11中引入了原子操作,需要包含<atomic>庫。

在C++11中,程序員不需要對原子類型變量進(jìn)行加鎖解鎖操作,線程能夠?qū)υ宇愋妥兞炕コ庠L問。程序員可以使用atomic類模版,定義出需要的任意原子類型。

atomic<T> t;  //聲明一個(gè)類型為T的原子類型變量t

兩個(gè)線程交替打印

要使兩個(gè)線程交替打印,需要使用到條件變量:?

條件變量condition_variable用于進(jìn)行進(jìn)程之間的互相通知。

為了使得線程交替打印,要保證線程1先執(zhí)行,線程2后執(zhí)行(哪怕把線程2放到前面)。

這里利用了wait,

由于條件變量wait不是線程安全的,因此要給wait傳互斥鎖,調(diào)用wait的線程被阻塞,直到被notified,wait的作用是使進(jìn)程間做到同步。在wait阻塞進(jìn)程時(shí),當(dāng)前進(jìn)程會先把鎖解掉,允許在這個(gè)鎖上阻塞的線程繼續(xù)走。當(dāng)這個(gè)進(jìn)程被喚醒后,這個(gè)進(jìn)程會解阻塞,并獲取到這個(gè)鎖。

notify_one會喚醒在這個(gè)條件變量上等待的一個(gè)線程,如果沒有現(xiàn)成在上面等待,什么都不做,如果有多個(gè)線程在等待,會選擇其中任意一個(gè)。

下面是兩個(gè)線程交替打印的代碼:

{std::mutex mtx;std::condition_variable c;int n = 100;bool flag = true;std::thread t1([&]() {int i = 0;while (i < n){std::unique_lock<std::mutex> lock(mtx);//flag=false t1就阻塞//flag=true t1就不會阻塞while (!flag){c.wait(lock);}std::cout << i << std::endl;flag = false;i += 2; // 偶數(shù)c.notify_one();}});std::thread t2([&]() {int j = 1;while (j < n){std::unique_lock<std::mutex> lock(mtx);//只要flag==true,t2就阻塞//只要flag==false,t2就不會阻塞while (flag)c.wait(lock);std::cout << j << std::endl;j += 2; // 奇數(shù)flag = true;c.notify_one();}});t1.join();t2.join();return 0;
}

http://aloenet.com.cn/news/46548.html

相關(guān)文章:

  • 做網(wǎng)站怎么切圖網(wǎng)站seo收錄工具
  • 威客做的好的網(wǎng)站有哪些建網(wǎng)站哪個(gè)平臺好
  • 網(wǎng)站怎樣做平面設(shè)計(jì)圖百度貼吧入口
  • 做網(wǎng)站接私活百度推廣助手手機(jī)版
  • css做簡單網(wǎng)站seo是什么崗位
  • wordpress forum南通seo
  • 專業(yè)網(wǎng)站建設(shè)培訓(xùn)機(jī)構(gòu)seo是搜索引擎嗎
  • 網(wǎng)站用什么做關(guān)鍵詞安卓優(yōu)化大師舊版本下載
  • 合肥市住房和城鄉(xiāng)建設(shè)廳網(wǎng)站鄭州網(wǎng)站seo優(yōu)化公司
  • dz做網(wǎng)站網(wǎng)站宣傳的方法有哪些
  • 無錫網(wǎng)站制作的公司小紅書推廣怎么收費(fèi)
  • 怎么開網(wǎng)站做網(wǎng)紅怎么提高百度搜索排名
  • 做視頻網(wǎng)站賺做視頻網(wǎng)站賺百度服務(wù)中心投訴
  • 大港油田建設(shè)官方網(wǎng)站谷歌google地圖
  • 京東網(wǎng)站怎么做seo是什么縮寫
  • 做兼職的網(wǎng)站貼吧網(wǎng)絡(luò)怎么推廣自己的產(chǎn)品
  • 長清治做網(wǎng)站b站推廣渠道
  • 網(wǎng)站做多長時(shí)間才會成功舉例一個(gè)成功的網(wǎng)絡(luò)營銷案例
  • 東莞住建局投訴電話是多少南寧seo平臺標(biāo)準(zhǔn)
  • 做網(wǎng)站 需要工信部備案嗎網(wǎng)站開發(fā)公司排名
  • 自己的電腦做服務(wù)器 并建網(wǎng)站東莞做網(wǎng)站推廣公司
  • 虛擬機(jī)做的網(wǎng)站怎么讓外網(wǎng)訪問不了個(gè)人博客網(wǎng)站怎么做
  • wordpress主題存放微博關(guān)鍵詞排名優(yōu)化
  • 做刷網(wǎng)站怎么賺錢杭州上城區(qū)抖音seo如何
  • 網(wǎng)站開發(fā)設(shè)計(jì)師薪資個(gè)人網(wǎng)頁制作
  • 怎樣做免費(fèi)網(wǎng)站建設(shè)google推廣有效果嗎
  • 做國際網(wǎng)站花錢嗎內(nèi)蒙古seo
  • 企業(yè)做網(wǎng)站需要租服務(wù)器嗎培訓(xùn)seo哪家學(xué)校好
  • 網(wǎng)站建設(shè)與網(wǎng)頁制作盒子模型360優(yōu)化大師官方最新
  • 海南新聞中心網(wǎng)站seo批量查詢工具