目錄
創(chuàng)新互聯(lián)主要從事成都做網(wǎng)站、成都網(wǎng)站設(shè)計、網(wǎng)頁設(shè)計、企業(yè)做網(wǎng)站、公司建網(wǎng)站等業(yè)務(wù)。立足成都服務(wù)隨縣,十余年網(wǎng)站建設(shè)經(jīng)驗,價格優(yōu)惠、服務(wù)專業(yè),歡迎來電咨詢建站服務(wù):189820811081.可變參數(shù)模板
1.1可變參數(shù)的模板?
1.2參數(shù)包的展開方式?
1.21遞歸函數(shù)展開參數(shù)包
1.3逗號表達式展開參數(shù)包
2.STL庫中的emplace相關(guān)接口
3.lambda表達式?
3.1lambda的引入
3.2lambda的介紹
列表使用 lambda 表達式捕獲
lambda實現(xiàn)swap函數(shù)?
lambda表達式之間不能相互賦值?
1.1可變參數(shù)的模板?可變參數(shù)模板可以說是C++的一大新收獲,也是C++11新增的最強大的特性之一,它對參數(shù)高度泛化,能夠讓我們創(chuàng)建可以接受可變參數(shù)的函數(shù)模板和類模板。但是可變參數(shù)模板上手需要一定的技巧性,以及對C++進行初步的認識,所以我們來具體講解一下。
template
返回類型 函數(shù)名(Args... args) { //函數(shù)主體 } //Args+省略號叫做模板參數(shù)包,args是函數(shù)形參參數(shù)包 //Args... args代表參數(shù)從0到多個 例如:
template
void ShowList(Args... args) { //函數(shù)主體 }
接下來我們用ShowList寫一個傳入不同參數(shù)的代碼。
//可變參數(shù) template
void ShowList(Args...args) { cout<< sizeof...(args)<< endl; //獲取函數(shù)包中參數(shù)的個數(shù) } 接下來傳參------》
int main() { string str("hello"); ShowList(); //0 ShowList(1); //1 ShowList(11,str); //2 ShowList(1,'A',string("hello,world"), 12); //4 }
特別提醒:但是我們無法直接獲取參數(shù)包中的每個參數(shù),只能通過展開參數(shù)包的方式來獲取,這是使用可變參數(shù)模板的一個主要特點?。語法并不支持使用args[i]
的方式來獲取參數(shù)包中的參數(shù)同樣也不支持范圍for那個。
1.2參數(shù)包的展開方式? 1.21遞歸函數(shù)展開參數(shù)包template
void ShowList(Args...args) { cout<< sizeof...(args)<< endl; //打印傳入?yún)?shù)的個數(shù) for (size_t i = 0; i< sizeof...(args); i++) { cout<< args[i]<< " "; } cout<< endl; }
1.3逗號表達式展開參數(shù)包這里用到兩部分遞歸函數(shù),但是這兩部分遞歸函數(shù)的函數(shù)名相同:
- 遞歸函數(shù)
- 遞歸終止函數(shù)
遞歸展開參數(shù)包的方式如下:
- 給函數(shù)模板增加一個模板參數(shù),這樣就可以從接收到的參數(shù)包中分離出一個參數(shù)出來。
- 在函數(shù)模板中遞歸調(diào)用該函數(shù)模板,調(diào)用時傳入剩下的參數(shù)包。
- 如此遞歸下去,每次分離出參數(shù)包中的一個參數(shù),直到參數(shù)包中的所有參數(shù)都被取出來。
每次我們都要先寫遞歸函數(shù),而遞歸終結(jié)函數(shù)可以理解為當(dāng)參數(shù)包中所有參數(shù)都被取出后,就會調(diào)用這個殺死循環(huán)的終止函數(shù)了。
template
void ShowList(const T& val, Args... args) { cout<< val<< " "; ShowList(args...); } 這個可變參數(shù)的整體就寫好了,最棘手的就是那個遞歸終結(jié)函數(shù)了。
此時這個終結(jié)函數(shù)又會分為兩類有參和無參兩種情況,這兩種情況還不太一樣。
我們先看一下無參的情況:
void ShowList() { cout<< endl; } template
void ShowList(const T& val, Args... args) { cout<< val<< " "; ShowList(args...); } 此時當(dāng)參數(shù)包的個數(shù)為0個的時候,就會走此函數(shù),完成遞歸終止。
- 如果外部調(diào)用ShowList函數(shù)時就沒有傳入?yún)?shù),那么就會直接匹配到無參的遞歸終止函數(shù)。
- 而我們本意是想讓外部調(diào)用ShowList函數(shù)時匹配的都是函數(shù)模板,并不是讓外部調(diào)用時直接匹配到這個遞歸終止函數(shù)。也就是說這個通道是我的老員工用的并且我也不想借給外界人員用,所以我們不得不再給外部人員提供一個新道路。
//遞歸終止函數(shù) void ShowListArg() { cout<< endl; } //展開函數(shù) template
void ShowListArg(T value, Args... args) { cout<< value<< " "; //打印傳入的若干參數(shù)中的第一個參數(shù) ShowListArg(args...); //將剩下參數(shù)繼續(xù)向下傳 } //外部人員專業(yè)通道 template void ShowList(Args... args) { ShowListArg(args...); } 帶參的情況:
//遞歸終止函數(shù)-------》帶參情況 template
void ShowListArg(const T& val) //終止函數(shù)帶參 { cout<< endl; } //展開函數(shù) template void ShowListArg(T value, Args... args) { cout<< value<< " "; //打印傳入的若干參數(shù)中的第一個參數(shù) ShowListArg(args...); //將剩下參數(shù)繼續(xù)向下傳 } //外部人員專業(yè)通道 template void ShowList(Args... args) { ShowListArg(args...); } int main() { ShowListArg(1,'A',string("hello,world"), 12); //4 }
- 當(dāng)傳入?yún)?shù)包中參數(shù)的個數(shù)為1時,就會傳入這個終止函數(shù)中。
- 但該方法有一個弊端就是,我們在調(diào)用ShowList函數(shù)時必須至少傳入一個參數(shù),否則就會報錯。因為此時無論是調(diào)用遞歸終止函數(shù)還是展開函數(shù),都需要至少傳入一個參數(shù)。
2.STL庫中的emplace相關(guān)接口C++只允許數(shù)組里面是同一種類型,但是模板的可變參數(shù)就意味著我參數(shù)包的類型并不統(tǒng)一,會出現(xiàn)一會是int,一會是char……。為了解決此問題,我們可以單獨封裝一層函數(shù)(PrintArg),此函數(shù)專門用于獲得參數(shù)包的每個數(shù)據(jù)并輸出,我們可以使用逗號表達式將返回值重新放到數(shù)組中。
- 逗號表達式會從左到右依次計算各個表達式,并且將最后一個表達式的值作為返回值進行返回。
- 將逗號表達式的最后一個表達式設(shè)置為一個整型值,確保逗號表達式返回的是一個整型值。
- 將處理參數(shù)包中參數(shù)的動作封裝成一個函數(shù),將該函數(shù)的調(diào)用作為逗號表達式的第一個表達式。
//逗號表達式展開參數(shù)包 template
void PrintArg(const T& t) { cout<< t<< " "; } //展開函數(shù) template void ShowList(Args... args) { int arr[] = { (PrintArg(args), 0)... }; //列表初始化+逗號表達式 cout<< endl; } int main() { ShowList(1,'A',string("hello,world"), 12); //4 }
- 可變參數(shù)的省略號需要加在逗號表達式外面,表示需要將逗號表達式展開,如果將省略號加在args的后面,那么參數(shù)包將會被展開后全部傳入PrintArg函數(shù),代碼中的{(PrintArg(args), 0)...}將會展開成{(PrintArg(arg1), 0), (PrintArg(arg2), 0)(PrintArg(arg3), 0), etc...}。
vector - C++ Reference (cplusplus.com)?我們可以進入cplusplus官網(wǎng)搜索vector,就可以看到C++11新增的emplace
template
void emplace_back (Args&&... args); &&不是特指右值引用而是代表左右值引用都可以。
我們以vector的emplace_back和push_back為例進行說明?:
//emplace 相關(guān)應(yīng)用
int main()
{
vector>vec;
pairkv(22, "emplace");
vec.push_back(kv); //傳左值
vec.push_back(pair(1222, "2022")); //傳右值
vec.push_back({ 30, "abc" }); //列表初始化
vec.emplace_back(kv); //傳左值
vec.emplace_back(pair(1222, "2022")); //傳右值
vec.emplace_back(50, "555"); //傳參數(shù)包
for (auto e : vec)
{
cout<< e.first<< ": "<< e.second<< endl;
}
}
3.lambda表達式? 3.1lambda的引入由于emplace系列接口的可變模板參數(shù)的類型都是萬能引用,因此既可以接收左值對象,也可以接收右值對象,還可以接收參數(shù)包。
- 如果調(diào)用emplace系列接口時傳入的是左值對象,那么首先需要先在此之前調(diào)用構(gòu)造函數(shù)實例化出一個左值對象,最終在使用定位new表達式調(diào)用構(gòu)造函數(shù)對空間進行初始化時,會匹配到拷貝構(gòu)造函數(shù)。
- 如果調(diào)用emplace系列接口時傳入的是右值對象,那么就需要在此之前調(diào)用構(gòu)造函數(shù)實例化出一個右值對象,最終在使用定位new表達式調(diào)用構(gòu)造函數(shù)對空間進行初始化時,就會匹配到移動構(gòu)造函數(shù)。
- 如果調(diào)用emplace系列接口時傳入的是參數(shù)包,那就可以直接調(diào)用函數(shù)進行插入,并且最終在使用定位new表達式調(diào)用構(gòu)造函數(shù)對空間進行初始化時,匹配到的是構(gòu)造函數(shù)。
使用emplace的原因:
- 之所以說emplace系列接口更高效是因為emplace系列接口大的特點就是支持傳入?yún)?shù)包,用這些參數(shù)包直接構(gòu)造出對象,這樣就能減少一次拷貝,
- 但emplace系列接口并不是在所有場景下都比原有的插入接口高效,如果傳入的是左值對象或右值對象,那么emplace系列接口的效率其實和原有的插入接口的效率是一樣的。
3.2lambda的介紹lambda表達式有如下優(yōu)點:
- 聲明式編程風(fēng)格:就地匿名定義目標(biāo)函數(shù)或函數(shù)對象,不需要額外寫一個命名函數(shù)或者函數(shù)對象。以更直接的方式去寫程序,好的可讀性和可維護性。
- 簡潔:不需要額外再寫一個函數(shù)或者函數(shù)對象,避免了代碼膨脹和功能分散,讓開發(fā)者更加集中精力在手邊的問題,同時也獲取了更高的生產(chǎn)率。
- 在需要的時間和地點實現(xiàn)功能閉包,使程序更靈活。
但是lambda具體的應(yīng)用在什么地方?--------->
在學(xué)校這個完整的教育系統(tǒng)中老師會根據(jù)學(xué)生的考試成績進行排名,而學(xué)校會根據(jù)班級成績從而對教師進行考核,現(xiàn)在學(xué)校的后勤部要根據(jù)這些指標(biāo)進行升序,降序排列。這里就會用到lambda表達式,這里一般會選擇仿函數(shù)來指定排序的主要方式。
//lambda表達式 struct Teacher { string _name; // 名字 double _point; // 班級成績 int _evaluate; // 總評分 Teacher(const char* str, double price, double evaluate) :_name(str) , _point(price) , _evaluate(evaluate) {} }; struct ComparePointLess { bool operator()(const Teacher& gl, const Teacher& gr) { return gl._point< gr._point; } }; struct ComparePointGreater { bool operator()(const Teacher& gl, const Teacher& gr) { return gl._point >gr._point; } }; int main() { vector
v = { { "李老師", 88, 8.9 }, { "張老師", 91, 8.7 }, { "徐老師", 86,9 }, { "王老師", 87, 8.7 } }; sort(v.begin(), v.end(), ComparePointLess());//按成績升序排 sort(v.begin(), v.end(), ComparePointGreater());//按成績降序排 } 這里我們就可以用lambda表達式解決這個問題了。
int main() { vector
v = { { "李老師", 88, 8.9 }, { "張老師", 91, 8.7 }, { "徐老師", 86,9 }, { "王老師", 87, 8.7 } }; sort(v.begin(), v.end(), [](const Teacher& g1, const Teacher& g2) { return g1._point< g2._point; }); sort(v.begin(), v.end(), [](const Teacher& g1, const Teacher& g2) { return g1._point >g2._point; }); sort(v.begin(), v.end(), [](const Teacher& g1, const Teacher& g2) { return g1._evaluate< g2._evaluate; }); sort(v.begin(), v.end(), [](const Teacher& g1, const Teacher& g2) { return g1._evaluate >g2._evaluate; }); }
列表使用 lambda 表達式捕獲lambda 表達式定義了一個匿名函數(shù),并且可以捕獲一定范圍內(nèi)的變量。lambda 表達式的語法形式可簡單歸納如下:
[ capture -list] ( params ) mutable ->return { body; };
- 其中 capture 是捕獲列表,params 是參數(shù)表,
- mutable 默認情況下,lambda函數(shù)總是一個const函數(shù),
mutable
可以取消其常量性。使用該修飾符時,參數(shù)列表不可省略(即使參數(shù)為空)。- return 是返回值類型,body是函數(shù)。
lambda函數(shù)的參數(shù)列表和返回值類型都是可選部分,但捕捉列表和函數(shù)體是不可省略的,因此最簡單的lambda函數(shù)如下:
int main() { []{}; //最簡單的lambda表達式 return 0; }
一個完整的 lambda 表達式看起來像這樣:
auto f = [](int a) ->int { return a + 1; }; std::cout<< f(1)<< std::endl; // 輸出: 2
需要注意的是,初始化列表不能用于返回值的自動推導(dǎo):
auto x1 = [](int i){ return i; };? // OK: return type is int auto x2 = [](){ return { 1, 2 }; };? // error: 無法推導(dǎo)出返回值類型
這時我們需要顯式給出具體的返回值類型。
另外,lambda 表達式在沒有參數(shù)列表時,參數(shù)列表是可以省略的。因此像下面的寫法都是正確的:auto f1 = [](){ return 1; }; auto f2 = []{ return 1; };? // 省略空參數(shù)表
lambda實現(xiàn)swap函數(shù)?lambda 表達式還可以通過捕獲列表捕獲一定范圍內(nèi)的變量:
- [] 不捕獲任何變量。
- [&] 捕獲外部作用域中所有變量,并作為引用在函數(shù)體中使用(按引用捕獲)。
- [bar] 按值捕獲 bar 變量,同時不捕獲其他變量。
- [&bar] 按值捕獲外部作用域中所有變量,并按引用傳遞捕獲bar變量。
- [=] 表示值傳遞方式捕獲所有父作用域中的變量包括this。(按值捕獲)。
- [this] 捕獲當(dāng)前類中的 this?指針,讓 lambda 表達式擁有和當(dāng)前類成員函數(shù)同樣的訪問權(quán)限。如果已經(jīng)使用了 & 或者 =,就默認添加此選項。捕獲 this 的目的是可以在 lamda 中使用當(dāng)前類的成員函數(shù)和成員變量。
說明:
- 父作用域指的是包含lambda函數(shù)的語句塊。
- 語法上捕捉列表可由多個捕捉項組成,并以逗號分割。比如[=, &a, &b]。
- 捕捉列表不允許變量重復(fù)傳遞,否則會導(dǎo)致編譯錯誤。比如[=, a]重復(fù)傳遞了變量a。
- 在塊作用域以外的lambda函數(shù)捕捉列表必須為空,即全局lambda函數(shù)的捕捉列表必須為空。
- 在塊作用域中的lambda函數(shù)僅能捕捉父作用域中的局部變量,捕捉任何非此作用域或者非局部變量都會導(dǎo)致編譯報錯。
- lambda表達式之間不能相互賦值,即使看起來類型相同。
//lambda實現(xiàn)swap
int main()
{
int a=5, b=7;
cout<<"a: "<< a<<" "<< "b: "<< b<< endl;
auto swap = [](int& a, int& b)->void
{
int tmp = a;
a = b;
b = tmp;
};
swap(a, b); //別忘了分號
cout<< "a: "<< a<< " "<< "b: "<< b<< endl;
return 0;
}
- lambda表達式是一個匿名函數(shù),該函數(shù)無法直接調(diào)用,如果想要直接調(diào)用,可借助auto將其賦值給一個變量,此時這個變量就可以像普通函數(shù)一樣使用。
- lambda表達式的函數(shù)體在格式上并不是必須寫成一行,如果函數(shù)體太長可以進行換行,但換行后不要忘了函數(shù)體最后還有一個分號。
其實這個代碼還可以進行優(yōu)化,我們還可以利用捕捉列表進行捕捉 :
int main()
{
int a=5, b=7;
cout<<"a: "<< a<<" "<< "b: "<< b<< endl;
//auto swap = [](int& a, int& b)->void
//{
// int tmp = a;
// a = b;
// b = tmp;
//}; //不要忘了后面的分號
//swap(a, b);
auto swap = [&a, &b]
{
int tmp = a;
a = b;
b = tmp;
};
swap(); //()里面不需要傳參
cout<< "a: "<< a<< " "<< "b: "<< b<< endl;
return 0;
}
還有的交換函數(shù)會這樣寫:
auto swap = [a, b]()mutable
{
int tmp = a;
a = b;
b = tmp;
};
swap(); //()里面不需要傳參
cout<< "a: "<< a<< " "<< "b: "<< b<< endl;
lambda表達式之間不能相互賦值?注意:mutable的作用是取消lambda表達式的常量性,因為lambda總是一個const函數(shù),光取消const屬性還是不足以進行交換,這里還需要用到引用傳遞來捕獲變量進行數(shù)據(jù)交換。
注意任何的lambda表達式之間的不能進行相互賦值,即使是兩者的類型相同。?
int main() { int a = 5, b = 7; cout<< "a: "<< a<< " "<< "b: "<< b<< endl; auto swap1 = [](int& a, int& b)->void { int tmp = a; a = b; b = tmp; }; //不要忘了后面的分號 auto swap2 = [](int& a, int& b)->void { int tmp = a; a = b; b = tmp; }; //不要忘了后面的分號 cout<< typeid(swap1).name()<< endl; // class
cout<< typeid(swap2).name()<< endl; //class return 0; }
- lambda表達式之間不能相互賦值,就算是兩個一模一樣的lambda表達式。
- 因為lambda表達式底層的處理方式和仿函數(shù)是一樣的,在VS下,lambda表達式在底層會被處理為函數(shù)對象,該函數(shù)對象對應(yīng)的類名叫做
。 - 類名中的uuid叫做通用唯一識別碼(Universally Unique Identifier),簡單來說,uuid就是通過算法生成一串字符串,保證在當(dāng)前程序當(dāng)中每次生成的uuid都不會重復(fù)。
- lambda表達式底層的類名包含uuid,這樣就能保證每個lambda表達式底層類名都是唯一的。
- 因此每個lambda表達式的類型都是不同的,這也就是lambda表達式之間不能相互賦值的原因,我們可以通過typeid(變量名).name()的方式來獲取lambda表達式的類型。
你是否還在尋找穩(wěn)定的海外服務(wù)器提供商?創(chuàng)新互聯(lián)www.cdcxhl.cn海外機房具備T級流量清洗系統(tǒng)配攻擊溯源,準(zhǔn)確流量調(diào)度確保服務(wù)器高可用性,企業(yè)級服務(wù)器適合批量采購,新人活動首月15元起,快前往官網(wǎng)查看詳情吧