??樊梓慕:個(gè)人主頁(yè)
???個(gè)人專欄:《C語(yǔ)言》《數(shù)據(jù)結(jié)構(gòu)》《藍(lán)橋杯試題》《LeetCode刷題筆記》《實(shí)訓(xùn)項(xiàng)目》《C++》《Linux》《算法》
??每一個(gè)不曾起舞的日子,都是對(duì)生命的辜負(fù)
目錄
前言
可變參數(shù)模板的定義方式
可變參數(shù)模板的使用?
編譯時(shí)遞歸展開(kāi)參數(shù)包
可變參數(shù)模板的應(yīng)用:emplace系列函數(shù)
對(duì)比emplace_back與push_back
emplace系列真正的優(yōu)勢(shì)在于淺拷貝的類(lèi)
總結(jié)
List類(lèi)增添模擬實(shí)現(xiàn)emplace系列函數(shù)
構(gòu)造
emplace_back()
emplace()
前言
其實(shí)我們之前經(jīng)常使用可變參數(shù)模板,C語(yǔ)言的printf函數(shù)大家一定非常熟悉,其實(shí)這就是一種可變參數(shù)模板:
那么在C++11引入可變參數(shù)模板的設(shè)計(jì)可以帶來(lái)什么變化呢?讓我們一起來(lái)學(xué)習(xí)下吧!?
歡迎大家??收藏??以便未來(lái)做題時(shí)可以快速找到思路,巧妙的方法可以事半功倍。?
=========================================================================文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-846316.html
GITEE相關(guān)代碼:??樊飛 (fanfei_c) - Gitee.com??
=========================================================================
可變參數(shù)模板的定義方式
template<class ...Args>
返回類(lèi)型 函數(shù)名(Args... args)
{
??//函數(shù)體
}
例如:
// Args是一個(gè)模板參數(shù)包,args是一個(gè)函數(shù)形參參數(shù)包
// 聲明一個(gè)參數(shù)包Args...args,這個(gè)參數(shù)包中可以包含0到任意個(gè)模板參數(shù)。
template <class ...Args>
void ShowList(Args... args)
{}
- 模板參數(shù)Args前面有『 省略號(hào)』,代表它是一個(gè)可變模板參數(shù),我們把帶省略號(hào)的參數(shù)稱為參數(shù)包,參數(shù)包里面可以包含0到任意個(gè)模板參數(shù),而args則是一個(gè)函數(shù)形參參數(shù)包。
- 模板參數(shù)包Args和函數(shù)形參參數(shù)包args的名字可以任意指定,并不是說(shuō)必須叫做Args和args,判斷是否為參數(shù)包的主要關(guān)鍵在『 省略號(hào)』。
可變參數(shù)模板的使用?
此時(shí)我們可以傳入任意多個(gè)參數(shù)了,并且這些參數(shù)可以是不同類(lèi)型的:
int main()
{
?? ?ShowList();
?? ?ShowList(1);
?? ?ShowList(1, 'A');
?? ?ShowList(1, 'A', string("hello"));
?? ?return 0;
}
我們可以在函數(shù)模板中通過(guò)sizeof計(jì)算參數(shù)包中參數(shù)的個(gè)數(shù):
template<class ...Args>
void ShowList(Args... args)
{
?? ?cout << sizeof...(args) << endl; //獲取參數(shù)包中參數(shù)的個(gè)數(shù)
}
但是,我們?nèi)绾谓馕鰠?shù)包中的內(nèi)容呢?
我們可不可以這樣獲?。?/p>
template<class ...Args>
void ShowList(Args... args)
{
?? ?for (int i = 0; i < sizeof...(args); i++)
?? ?{
?? ??? ?cout << args[i] << " ";
?? ?}
?? ?cout << endl;
}
答案是不可以!
注意:可變參數(shù)模板,既然是模板就是編譯時(shí)解析,就不能使用如上這種運(yùn)行時(shí)解析的邏輯獲取。
因此要獲取參數(shù)包中的各個(gè)參數(shù),可以通過(guò)『 編譯時(shí)遞歸』的方式解析數(shù)據(jù)。
編譯時(shí)遞歸展開(kāi)參數(shù)包
如何實(shí)現(xiàn)編譯時(shí)遞歸呢?那肯定是利用編譯器的解析機(jī)制,我們給函數(shù)模板增加一個(gè)模板參數(shù),每次從接收到的參數(shù)包中剝離出來(lái)一個(gè)參數(shù),然后在函數(shù)模板中遞歸調(diào)用該函數(shù)模板,調(diào)用時(shí)傳入剩下的參數(shù)包,如此遞歸下去,每次剝離出參數(shù)包中的一個(gè)參數(shù),直到參數(shù)包中的所有參數(shù)都被取出來(lái)。
比如:
//展開(kāi)函數(shù)
template<class T, class ...Args>
void _ShowList(T value, Args... args)
{
cout << value << " ";
_ShowList(args...);//遞歸
}
那么如何終止遞歸呢?
我們每次都剝離下一個(gè)參數(shù),最后必然就沒(méi)有參數(shù)了,那么根據(jù)編譯器的『最匹配原則 』,我們可以實(shí)現(xiàn)一個(gè)『 無(wú)參』的遞歸終止函數(shù):
//遞歸終止函數(shù)
void _ShowList()
{
cout << endl;
}
//展開(kāi)函數(shù)
template<class T, class ...Args>
void _ShowList(T value, Args... args)
{
cout << value << " ";
_ShowList(args...); //遞歸
}
然后再封裝起來(lái)如下:
//遞歸終止函數(shù)
void _ShowList()
{
cout << endl;
}
//展開(kāi)函數(shù)
template<class T, class ...Args>
void _ShowList(T value, Args... args)
{
cout << value << " "; //打印傳入的若干參數(shù)中的第一個(gè)參數(shù)
_ShowList(args...); //將剩下參數(shù)繼續(xù)向下傳
}
//供外部調(diào)用的函數(shù)
template<class ...Args>
void ShowList(Args... args)
{
_ShowList(args...);
}
這種『 編譯時(shí)遞歸』的思想可謂是非常新奇,值得我們學(xué)習(xí)。
可變參數(shù)模板的應(yīng)用:emplace系列函數(shù)
對(duì)比emplace_back與push_back
還記得么?
&&這里為萬(wàn)能引用,不是單純的右值引用。
?相對(duì)于push_back,emplace_back支持萬(wàn)能引用和可變參數(shù)模板。
他們都是尾插『 一個(gè)』數(shù)據(jù),注意這里不要看可變參數(shù)模板就以為是插入幾個(gè)值,這里是『 類(lèi)型』。
對(duì)比push_back與emplace_back:
int main()
{
std::list<pair<F::string, F::string>> lt2;
pair<F::string, F::string> kv1("xxxx", "yyyy");
lt2.push_back(kv1);
lt2.push_back(move(kv1));
cout << "=============================================" << endl;
pair<F::string, F::string> kv2("xxxx", "yyyy");
lt2.emplace_back(kv2);
lt2.emplace_back(move(kv2));
cout << "=============================================" << endl;
return 0;
}
對(duì)比發(fā)現(xiàn)也沒(méi)有區(qū)別?
其實(shí)emplace_back和push_back真正的區(qū)別在于:
push_back需要先用參數(shù)構(gòu)造pair這個(gè)對(duì)象,然后再將這個(gè)對(duì)象拷貝給鏈表節(jié)點(diǎn)中的pair;
而emplace_back是直接拿著參數(shù)去構(gòu)造鏈表節(jié)點(diǎn)中的pair,中間省略了拷貝的過(guò)程;
比如:
int main()
{
std::list<pair<F::string, F::string>> lt2;
pair<F::string, F::string> kv1("xxxx", "yyyy");
lt2.push_back(kv1);
lt2.push_back(move(kv1));
cout << "=============================================" << endl;
pair<F::string, F::string> kv2("xxxx", "yyyy");
lt2.emplace_back(kv2);
lt2.emplace_back(move(kv2));
cout << "=============================================" << endl;
lt2.emplace_back("xxxx", "yyyy");
cout << "=============================================" << endl;
return 0;
}
emplace系列真正的優(yōu)勢(shì)在于淺拷貝的類(lèi)
因?yàn)閷?duì)于深拷貝的且實(shí)現(xiàn)了移動(dòng)構(gòu)造的類(lèi)來(lái)說(shuō),移動(dòng)構(gòu)造代價(jià)很小,emplace的優(yōu)勢(shì)顯現(xiàn)不出來(lái)。
比如:
int main()
{
std::list<F::string> lt1;
lt1.push_back("xxxx");
cout << "=============================================" << endl;
lt1.emplace_back("xxxx");
return 0;
}
emplace真正的優(yōu)勢(shì)在于淺拷貝的類(lèi),可以節(jié)省一個(gè)拷貝過(guò)程:
比如日期類(lèi):
class Date
{
public:
//構(gòu)造
Date(int year, int month, int day)
:_year(year)
, _month(month)
, _day(day)
{
cout << "Date(int year, int month, int day)" << endl;
}
//拷貝構(gòu)造
Date(const Date& d)
:_year(d._year)
, _month(d._month)
, _day(d._day)
{
cout << "Date(const Date& d)" << endl;
}
private:
int _year = 1;
int _month = 1;
int _day = 1;
};
同樣尾插:
int main()
{
std::list<Date> lt1;
lt1.push_back({ 2024,3,30 });
cout << "=============================================" << endl;
lt1.emplace_back(2024, 3, 30);
return 0;
}
注意:push_back支持initializer_list作為參數(shù)是因?yàn)閜ush_back的參數(shù)就是模板類(lèi)型,所以他知道插入的對(duì)象是Date類(lèi)型,就直接走多參數(shù)的隱式類(lèi)型轉(zhuǎn)換構(gòu)造一個(gè)Date對(duì)象了,而emplace不支持initializer_list作為構(gòu)造參數(shù),因?yàn)閑mplace需要可變參數(shù)模板去底層構(gòu)造list節(jié)點(diǎn),你放到initializer_list中就相當(dāng)于把參數(shù)又封裝起來(lái)了:
int main() { std::list<Date> lt1; lt1.push_back({ 2024,3,30 }); // 不支持 //lt1.emplace_back({ 2024,3,30 }); // 正確寫(xiě)法 lt1.emplace_back(2024, 3, 30); return 0; }
如果給emplace的參數(shù)是現(xiàn)成的對(duì)象(不管有名對(duì)象還是匿名對(duì)象),那emplace就沒(méi)有任何優(yōu)勢(shì)了:
int main()
{
std::list<Date> lt1;
Date d1(2023, 1, 1);
lt1.push_back(d1);
lt1.emplace_back(d1);
cout << "=============================================" << endl;
lt1.push_back(Date(2023, 1, 1));
lt1.emplace_back(Date(2023, 1, 1));
return 0;
}
總結(jié)
- emplace系列接口使用需要直接傳入?yún)?shù)包才能體現(xiàn)emplace接口的價(jià)值與意義,因?yàn)閑mplace系列接口真正高效的情況是傳入?yún)?shù)包的時(shí)候,直接通過(guò)參數(shù)包構(gòu)造出對(duì)象,避免了中途的一次拷貝。
- 所以emplace系列接口并不是在所有場(chǎng)景下都比原有的插入接口高效,如果傳入的是對(duì)象(不管有名還是匿名),那么emplace系列接口的效率其實(shí)和原有的插入接口的效率是一樣的。
- 以上兩條告訴我們:以后使用emplace就直接傳參數(shù)包,不要構(gòu)造了對(duì)象后再將該對(duì)象作為參數(shù)傳給emplace,直接傳參數(shù)包才是emplace存在的價(jià)值和意義。
- 并且emplace系列接口對(duì)于深拷貝的且實(shí)現(xiàn)了移動(dòng)構(gòu)造的類(lèi)意義不大,因?yàn)橐苿?dòng)構(gòu)造的代價(jià)很小,emplace帶來(lái)的效率提升并不會(huì)很明顯,emplace系列接口對(duì)于淺拷貝的類(lèi)可以節(jié)省拷貝(拷貝代價(jià)高,并且淺拷貝的類(lèi)沒(méi)有移動(dòng)構(gòu)造),所以emplace對(duì)于淺拷貝的類(lèi)插入提升很明顯。
List類(lèi)增添模擬實(shí)現(xiàn)emplace系列函數(shù)
構(gòu)造
template<class ...Args>
ListNode(Args&&... args)
: _next(nullptr)
, _prev(nullptr)
, _data(forward<Args>(args)...)
{}
emplace_back()
template<class ...Args>
void emplace_back(Args&&... args)
{
emplace(end(), forward<Args>(args)...);
}
emplace()
template<class ...Args>
iterator emplace(iterator pos, Args&&... args)
{
Node* cur = pos._node;
Node* prev = cur->_prev;
Node* newnode = new Node(forward<Args>(args)...);
prev->_next = newnode;
newnode->_prev = prev;
newnode->_next = cur;
cur->_prev = newnode;
//return iterator(newnode);
return newnode;
}
=========================================================================
如果你對(duì)該系列文章有興趣的話,歡迎持續(xù)關(guān)注博主動(dòng)態(tài),博主會(huì)持續(xù)輸出優(yōu)質(zhì)內(nèi)容
??博主很需要大家的支持,你的支持是我創(chuàng)作的不竭動(dòng)力??
??~ 點(diǎn)贊收藏+關(guān)注 ~??文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-846316.html
=========================================================================
到了這里,關(guān)于【C++】C++11可變參數(shù)模板的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!