最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

C++可變參數(shù)模板深入深剖

 更新時間:2022年10月17日 09:15:00   作者:喬喬家的龍龍  
個可變參數(shù)模板(variadic template)就是一個接受可變數(shù)目參數(shù)的函數(shù)模板或類模板,下面這篇文章主要給大家介紹了關(guān)于C++可變參數(shù)模板的相關(guān)資料,文中通過實例代碼介紹的非常詳細(xì),需要的朋友可以參考下

概念

C++11 新增一員猛將就是可變參數(shù)模板,他可以允許可變參數(shù)的函數(shù)模板和類模板來作為參數(shù),使得參數(shù)高度泛化。

在 C++11 之前類模板和函數(shù)模板中只能包含固定數(shù)量模板參數(shù),而且也有可變參數(shù)的概念,比如 printf 函數(shù)就能夠接收任意多個參數(shù),但這是函數(shù)參數(shù)的可變參數(shù),并不是模板的可變參數(shù)??勺兡0鍏?shù)無疑是一個巨大的改進(jìn),但由于可變參數(shù)模板比較抽象,因此使用起來并不會太簡單。

模板定義

函數(shù)的可變參數(shù)模板定義方式如下:

template<class …Args>
返回類型 函數(shù)名(Args… args)
{
??//函數(shù)體
}

比如:

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

注意這里的書寫格式,模板參數(shù)Args前面有省略號,代表它是一個可變模板參數(shù), 我們把帶省略號的參數(shù)稱為參數(shù)包 \color{red} {我們把帶省略號的參數(shù)稱為參數(shù)包} 我們把帶省略號的參數(shù)稱為參數(shù)包,參數(shù)包里面可以包含0到 N(N≥0) 個模板參數(shù), 而 a r g s 則是一個函數(shù)形參參數(shù)包 \color{red} {而 args 則是一個函數(shù)形參參數(shù)包} 而args則是一個函數(shù)形參參數(shù)包。

模板參數(shù)包 Args 和函數(shù)形參參數(shù)包 args 的名字可以任意指定,并不是說必須叫做 Args 和 args 。

那么現(xiàn)在函數(shù)傳參就可以實不同類型了:

int main()
{
	ShowList();
	ShowList(1);
	ShowList(1, 'A');
	ShowList(1, 'A', string("hello"));
	return 0;
}

然后在函數(shù)模板中通過sizeof計算參數(shù)包中參數(shù)的個數(shù):

template<class ...Args>
void ShowList(Args... args)
{
	cout << sizeof...(args) << endl; //獲取參數(shù)包中參數(shù)的個數(shù)
}

現(xiàn)在最大的難點就是我們無法直接獲取參數(shù)包中的每個參數(shù),語法并不支持使用 args[i] 的方式來獲取參數(shù)包中的參數(shù),只能通過展開參數(shù)包的方式來獲取,這是使用可變參數(shù)模板的一個主要特點。

template<class ...Args>
void ShowList(Args... args)
{
	//錯誤示例:
	for (int i = 0; i < sizeof...(args); i++)
	{
		cout << args[i] << " "; //打印參數(shù)包中的每個參數(shù)
	}
	cout << endl;
}

參數(shù)包展開

遞歸函開

該方法大概分為三步:

  • 給函數(shù)模板增加一個模板參數(shù),從接收的參數(shù)包中分離出一個參數(shù)出來
  • 在函數(shù)模板中遞歸調(diào)用該函數(shù)模板,調(diào)用時傳入剩下的參數(shù)包
  • 繼續(xù)遞歸,直到參數(shù)包中所有參數(shù)都被取出來

比如:

template<class T, class ...Args>
void ShowList(T value, Args... args)
{
	cout << value << " "; //打印分離出的第一個參數(shù)
	ShowList(args...);    //繼續(xù)遞歸調(diào)用
}

那么最后還有一個問題就是:遞歸展開該如何終止?

方法其實挺簡單就是寫一個無參的遞歸終止函數(shù),該函數(shù)的函數(shù)名與展開函數(shù)的函數(shù)名相同,如果傳入的參數(shù)包中參數(shù)個數(shù)是 0,那么就會匹配到這個無參遞歸終止函數(shù),這樣就結(jié)束了遞歸:

//遞歸終止函數(shù)
void ShowList()
{
	cout << endl;
}
//展開函數(shù)
template<class T, class ...Args>
void ShowList(T value, Args... args)
{
	cout << value << " "; //打印分離出的第一個參數(shù)
	ShowList(args...);    //繼續(xù)遞歸調(diào)用
}

但是外部調(diào)用 ShowList 時不會傳入?yún)?shù),就會直接匹配到無參遞歸終止函數(shù)。而我們本意是想讓外部調(diào)用 ShowList 函數(shù)時匹配到函數(shù)模板,并不是直接匹配遞歸終止函數(shù)。

因此我們可以將展開函數(shù)和遞歸調(diào)用函數(shù)的函數(shù)名改為 ShowListArg,然后重新編寫一個 ShowList 函數(shù)模板,在該函數(shù)模板的函數(shù)體中要做的就是調(diào)用ShowListArg 的展開參數(shù)包 :

void ShowListArg()
{
	cout << endl;
}
//展開函數(shù)
template<class T, class ...Args>
void ShowListArg(T value, Args... args)
{
	cout << value << " "; 
	ShowListArg(args...); //繼續(xù)遞歸
}
//供外部調(diào)用的函數(shù)
template<class ...Args>
void ShowList(Args... args)
{
	ShowListArg(args...);
}

這樣無論外部調(diào)用時傳入多少個參數(shù),最終匹配到的都是同一個函數(shù)了,那么如何編寫帶參的遞歸終止函數(shù)呢

比如帶一個參數(shù)的:

template<class T>
void ShowListArg(const T& t)
{
	cout << t << endl;
}
//展開函數(shù)
template<class T, class ...Args>
void ShowListArg(T value, Args... args)
{
	cout << value << " "; 
	ShowList(args...);    //繼續(xù)遞歸
}
//供外部調(diào)用的函數(shù)
template<class ...Args>
void ShowList(Args... args)
{
	ShowListArg(args...);
}

但該方法有一個缺陷,在調(diào)用 ShowList 函數(shù)時至少要傳入一個參數(shù),否則就會報錯,因為此時無論是調(diào)用遞歸終止函數(shù)還是展開函數(shù),都需要至少一個參數(shù),那我們能不能先計算一下參數(shù)包中的參數(shù)個數(shù)呢?

答案是:No!可能你會覺得 sizeof 這里也可以直接計算參數(shù)個數(shù),來康康 錯誤示范 \color{red} {錯誤示范} 錯誤示范:

template<class T, class ...Args>
void ShowList(T value, Args... args)
{
	cout << value << " "; //打印傳入的第一個參數(shù)
	if (sizeof...(args) == 0)
	{
		return;
	}
	ShowList(args...);    //繼續(xù)遞歸
}

首先函數(shù)模板并不能調(diào)用,函數(shù)模板需要在編譯時根據(jù)傳入的實參類型進(jìn)行推演,生成對應(yīng)的函數(shù)才能夠被調(diào)用,而這個推演過程是在編譯時進(jìn)行的,當(dāng)推演到參數(shù)包 args 中參數(shù)個數(shù)為 0 時,函數(shù)不會停下會繼續(xù)推演完畢,這時就會繼續(xù)傳入 0 個參數(shù)時的 ShowList 函數(shù),此時就會報錯 ShowList 函數(shù)沒有參數(shù)。

這里編寫的 if 判斷是運(yùn)行時才跑的邏輯,也就是運(yùn)行時邏輯,而函數(shù)模板的推演是一個編譯時邏輯!

逗號表達(dá)式展開

我們知道數(shù)組可以通過列表進(jìn)行初始化。如果參數(shù)包中各個參數(shù)類型都是整型,那么也可以把這個參數(shù)包放到列表中,初始化這個整型數(shù)組,此時參數(shù)包中參數(shù)就放到數(shù)組中了:

template<class ...Args>
void ShowList(Args... args)
{
	int arr[] = { args... }; //列表初始化
	//打印參數(shù)包中的各個參數(shù)
	for (auto e : arr)
	{
		cout << e << " ";
	}
	cout << endl;
}

這樣就可以傳入多個參數(shù)了:

int main()
{
	ShowList(1);
	ShowList(1, 2);
	ShowList(1, 2, 3);
	return 0;
}

但 C++ 并不像 Python 一樣激進(jìn)敢秀,C++ 規(guī)定器中存儲的數(shù)據(jù)類型是相同的,因此調(diào)用 ShowList 時傳入的參數(shù)只能是整型,并且還不能傳入 0 個參數(shù),因為數(shù)組的大小不能為 0,因此還需要在此基礎(chǔ)上借助逗號表達(dá)式來展開參數(shù)包

逗號表達(dá)式規(guī)則是會從左到右依次計算各個表達(dá)式,并將最后一個表達(dá)式的值作為返回值返回,我們將最后一個表達(dá)式設(shè)為整型值,確保最后返回的是一個整型。

將處理參數(shù)個數(shù)的動作封裝成一個函數(shù),將該函數(shù)作為逗號表達(dá)式的第一個表達(dá)式

template<class T>
void PrintArg(const T& t)
{
	cout << t << " ";
}
//展開函數(shù)
template<class ...Args>
void ShowList(Args... args)
{
	int arr[] = { (PrintArg(args), 0)... }; //列表初始化+逗號表達(dá)式
	cout << endl;
}

我們這里要做的就是打印參數(shù)包中的各個參數(shù),因此處理函數(shù)當(dāng)中要做的就是將傳入的參數(shù)進(jìn)行打印即可

可變參數(shù)的省略號需要加在逗號表達(dá)式外面,表示需要先將逗號表達(dá)式展開,如果直接加在 args 后面,那么參數(shù)包將會被展開后全部傳入 PrintArg ,代碼中會展開成 {(PrintArg(arg1), 0), (PrintArg(arg2), 0), (PrintArg(arg3), 0), etc…}

//支持無參調(diào)用
void ShowList()
{
	cout << endl;
}
//處理函數(shù)
template<class T>
void PrintArg(const T& t)
{
	cout << t << " ";
}
//展開函數(shù)
template<class ...Args>
void ShowList(Args... args)
{
	int arr[] = { (PrintArg(args), 0)... }; //列表初始化+逗號表達(dá)式
	cout << endl;
}

當(dāng)然,我們也可以不使用逗號表達(dá)式,這里的問題是初始化整型數(shù)組時必須用整數(shù),那我們可以將處理函數(shù)的返回值設(shè)為整型,然后用這個返回值去初始化整型數(shù)組也是可以的:

void ShowList()
{
	cout << endl;
}
//處理函數(shù)
template<class T>
int PrintArg(const T& t)//返回值為int類型
{
	cout << t << " ";
	return 0;
}
//展開函數(shù)
template<class ...Args>
void ShowList(Args... args)
{
	int arr[] = { PrintArg(args)... }; //列表初始化
	cout << endl;
}

emplace

C++11 給 STL 容器增加 emplace 的插入接口,比如 list 容器的 push_front、push_back 和insert 函數(shù),都有了對應(yīng)的 emplace_front、emplace_back 和 emplace 函數(shù):

這些emplace版本的插入接口支持模板的可變參數(shù),比如list容器的emplace_back函數(shù)的聲明如下:

emplace 接口的可變模板參數(shù)類型都帶有KaTeX parse error: Expected '}', got '&' at position 14: \color{red} {&?&} ,這個表示的是萬能引用,而不是右值引用。

使用方法

emplace 接口使用方式與容器原有的插入接口使用方式類似,但又有一些不同之處,以 list 的 emplace_back 和 push_back 為例:

調(diào)用 push_back 插入元素時,可以傳入左值對象或右值對象,也可以使用列表進(jìn)行初始化;調(diào)用emplace_back 插入元素時,也可以傳入左值對象或右值對象,但不可以使用列表進(jìn)行初始化。

除此之外,emplace系列接口最大的特點就是,插入元素可傳入用于構(gòu)造元素的參數(shù)包

int main()
{
	list<pair<int, string>> mylist;
	pair<int, string> kv(10, "111");
	mylist.push_back(kv);                              //左值
	mylist.push_back(pair<int, string>(20, "222"));    //右值
	mylist.push_back({ 30, "333" });                   //列表初始化

	mylist.emplace_back(kv);                           //左值
	mylist.emplace_back(pair<int, string>(40, "444")); //右值
	mylist.emplace_back(50, "555");                    //參數(shù)包
	return 0;
}

工作原理

emplace 接口先通過空間配置器為新結(jié)點獲取一塊內(nèi)存空間,注意這里只會開辟空間,不會自動調(diào)用構(gòu)造函數(shù)對這塊空間進(jìn)行初始化。

然后調(diào)用 allocator_traits::construct 函數(shù)對這塊空間進(jìn)行初始化,調(diào)用該函數(shù)會傳入這塊空間的地址和用戶傳入的參數(shù),注意要完美轉(zhuǎn)發(fā);在 allocator_traits::construct 中會使用定位 new 表達(dá)式,顯示調(diào)用構(gòu)造函數(shù)對這塊空間進(jìn)行初始化,調(diào)用構(gòu)造函數(shù)時會傳入用戶傳入的參數(shù),這里同樣需要完美轉(zhuǎn)發(fā)

最后將初始化好的新結(jié)點插入到對應(yīng)的數(shù)據(jù)結(jié)構(gòu)中,比如 list 就是將新結(jié)點插入到底層的雙鏈表中

意義

emplace 接口的可變參數(shù)模板類型都是萬能引用,因此既可以接收左值,也可以接收右值,還可以接收參數(shù)包

如果調(diào)用 emplace 接口時傳入的是左值,首先需要先在此之前調(diào)用構(gòu)造函數(shù)實例化出一個左值對象,最后使用定位 new 表達(dá)式調(diào)用構(gòu)造函數(shù)對空間進(jìn)行初始化時,會匹配到拷貝構(gòu)造函數(shù)

如果調(diào)用 emplace 接口時傳入的是右值,那么就需要在此之前調(diào)用構(gòu)造函數(shù)實例化出一個右值對象,最終在使用定位new表達(dá)式調(diào)用構(gòu)造函數(shù)對空間進(jìn)行初始化時,就會匹配到移動構(gòu)造函數(shù)

如果調(diào)用 emplace 接口時傳入的是參數(shù)包,就可以直接調(diào)用函數(shù)進(jìn)行插入,并最終使用定位 new 表達(dá)式調(diào)用構(gòu)造函數(shù)對空間進(jìn)行初始化時,匹配到構(gòu)造函數(shù)

一句話就是:

傳入左值,調(diào)用構(gòu)造函數(shù)+拷貝構(gòu)造函數(shù)。
傳入右值,調(diào)用構(gòu)造函數(shù)+移動構(gòu)造函數(shù)。
傳入?yún)?shù)包,只需要調(diào)用構(gòu)造函數(shù)

注意,這里前提是容器中存儲的是一個需要深拷貝的類,并且該類實現(xiàn)了移動構(gòu)造函數(shù),否則傳入左值和傳入右值的效果是一樣的,都會調(diào)用一次構(gòu)造和一次拷貝構(gòu)造

因為容器原有的 push_back、push_front 和 insert 也提供了右值引用的接口,所以 emplace 的部分功能和原有容器是重復(fù)的,如果調(diào)用時傳入右值,那么最終也會調(diào)用對應(yīng)的移動構(gòu)造函數(shù)進(jìn)行資源轉(zhuǎn)移。

emplace 最大特點就是支持傳入?yún)?shù)包,用這些參數(shù)包直接構(gòu)造出對象,這樣就能減少一次拷貝,這就是為什么有人說 emplace 系列接口更高效的原因

但 emplace 并不是在所有場景下都比原有的插入接口高效,如果傳入的是左值對象或右值對象,那么 emplace 系列接口的效率其實和原有的效率是一樣的

emplace 真正高效的情況是傳入?yún)?shù)包的時候, 直接通過參數(shù)包構(gòu)造出對象,避免了中途的一次拷貝 \color{red} {直接通過參數(shù)包構(gòu)造出對象,避免了中途的一次拷貝} 直接通過參數(shù)包構(gòu)造出對象,避免了中途的一次拷貝

namespace cl
{
	class string
	{
	public:
		//構(gòu)造函數(shù)
		string(const char* str = "")
		{
			cout << "string(const char* str) -- 構(gòu)造函數(shù)" << endl;

			_size = strlen(str); 
			_capacity = _size; 
			_str = new char[_capacity + 1]; //開辟空間(多開一個用于存放'\0')
			strcpy(_str, str); //將C字符串拷貝到已開好的空間
		}
		//交換兩個對象數(shù)據(jù)
		void swap(string& s)
		{
			std::swap(_str, s._str); //交換兩個對象的C字符串
			std::swap(_size, s._size); //交換兩個對象的大小
			std::swap(_capacity, s._capacity); //交換兩個對象的容量
		}
		//拷貝構(gòu)造函數(shù)(現(xiàn)代寫法)
		string(const string& s)
			:_str(nullptr)
			, _size(0)
			, _capacity(0)
		{
			cout << "string(const string& s) -- 拷貝構(gòu)造" << endl;

			string tmp(s._str); //調(diào)用構(gòu)造函數(shù),構(gòu)造一個s._str的對象
			swap(tmp); //交換這兩個對象
		}
		//移動構(gòu)造
		string(string&& s)
			:_str(nullptr)
			, _size(0)
			, _capacity(0)
		{
			cout << "string(string&& s) -- 移動構(gòu)造" << endl;
			swap(s);
		}
		//拷貝賦值函數(shù)(現(xiàn)代寫法)
		string& operator=(const string& s)
		{
			cout << "string& operator=(const string& s) -- 深拷貝" << endl;

			string tmp(s); 
			swap(tmp); //交換
			return *this; //返回左值
		}
		//移動賦值
		string& operator(string&& s)
		{
			cout << "string& operator=(string&& s) -- 移動賦值" << endl;
			swap(s);
			return *this;
		}
		//析構(gòu)函數(shù)
		~string()
		{
			//delete[] _str;  //釋放_str指向的空間
			_str = nullptr; //置空,防止非法訪問
			_size = 0;    
			_capacity = 0; 
		}
	private:
		char* _str;
		size_t _size;
		size_t _capacity;
	};
}

這里我們用模擬實現(xiàn)的 string 來驗證 emplace 的機(jī)制:

int main()
{
	list<pair<int, cl::string>> mylist;
	
	pair<int, cl::string> kv(1, "one");
	mylist.emplace_back(kv);                              //左值
	cout << endl;
	mylist.emplace_back(pair<int, cl::string>(2, "two")); //右值
	cout << endl;
	mylist.emplace_back(3, "three");                      //參數(shù)包
	return 0;
}

結(jié)果如下:

我們自己實現(xiàn)的 string 的拷貝構(gòu)造函數(shù)復(fù)用了他的拷貝函數(shù),所以在調(diào)用 string 的拷貝構(gòu)造的時候會緊跟一次拷貝函數(shù)的調(diào)用。

當(dāng)然,如果想要更加完美的體現(xiàn) emplace 的作用,這里存的是 char 類型,為了體現(xiàn)參數(shù)包的概念,可以將 list 中更換成 pair 類型對象,這里不贅述了,有興趣的可自行實現(xiàn)。

總結(jié)

到此這篇關(guān)于C++可變參數(shù)模板的文章就介紹到這了,更多相關(guān)C++可變參數(shù)模板內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 深入第K大數(shù)問題以及算法概要的詳解

    深入第K大數(shù)問題以及算法概要的詳解

    本篇文章是對第K大數(shù)問題以及算法概要進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下
    2013-05-05
  • Qt創(chuàng)建并顯示柱狀圖的方法

    Qt創(chuàng)建并顯示柱狀圖的方法

    Qt Charts 模塊提供了一套易于使用的圖表組件,本文主要介紹了Qt創(chuàng)建并顯示柱狀圖,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2021-05-05
  • C語言中的文件操作詳解

    C語言中的文件操作詳解

    這篇文章主要介紹了C語言中的文件操作詳解,使用文件可以將數(shù)據(jù)直接存放到電腦的硬盤上,做到了數(shù)據(jù)的持久化
    2022-07-07
  • 基于Qml實現(xiàn)水印工具

    基于Qml實現(xiàn)水印工具

    這篇文章主要介紹了如何在?Qml?中實現(xiàn)一個簡單但功能強(qiáng)大的水印工具,包括水印文本的透明度、顏色、字體大小、旋轉(zhuǎn)角度等自定義功能,需要的可以參考下
    2024-12-12
  • C++ 如何將string轉(zhuǎn)換成全小寫

    C++ 如何將string轉(zhuǎn)換成全小寫

    這篇文章主要介紹了C++ 如何將string轉(zhuǎn)換成全小寫問題,具有很好的參考價值,希望對大家有所幫助。
    2022-11-11
  • C++11 寫一個只觸發(fā)一次槽函數(shù)的Qt connect函數(shù)

    C++11 寫一個只觸發(fā)一次槽函數(shù)的Qt connect函數(shù)

    這篇文章主要為大家介紹了C++11 寫一個只觸發(fā)一次槽函數(shù)的Qt connect函數(shù)實例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-09-09
  • C++實現(xiàn)LeetCode(19.移除鏈表倒數(shù)第N個節(jié)點)

    C++實現(xiàn)LeetCode(19.移除鏈表倒數(shù)第N個節(jié)點)

    這篇文章主要介紹了C++實現(xiàn)LeetCode(19.移除鏈表倒數(shù)第N個節(jié)點),本篇文章通過簡要的案例,講解了該項技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下
    2021-07-07
  • C語言深入講解動態(tài)內(nèi)存分配函數(shù)的使用

    C語言深入講解動態(tài)內(nèi)存分配函數(shù)的使用

    這篇文章主要介紹了C語言動態(tài)內(nèi)存分配,C語言內(nèi)存管理相關(guān)的函數(shù)主要有realloc、calloc、malloc、free、柔性數(shù)組等,下面這篇文章帶大家了解一下
    2022-05-05
  • C++模擬實現(xiàn)vector流程詳解

    C++模擬實現(xiàn)vector流程詳解

    這篇文章主要介紹了C++容器Vector的模擬實現(xiàn),Vector是一個能夠存放任意類型的動態(tài)數(shù)組,有點類似數(shù)組,是一個連續(xù)地址空間,下文更多詳細(xì)內(nèi)容的介紹,需要的小伙伴可以參考一下
    2022-08-08
  • C++構(gòu)造函數(shù)的類型,淺拷貝與深拷貝詳解

    C++構(gòu)造函數(shù)的類型,淺拷貝與深拷貝詳解

    這篇文章主要為大家詳細(xì)介紹了C++構(gòu)造函數(shù)的類型,淺拷貝與深拷貝,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助
    2022-03-03

最新評論

焦作市| 区。| 微山县| 福建省| 和硕县| 霍州市| 遂平县| 巴林左旗| 台江县| 长子县| 安义县| 陕西省| 县级市| 五河县| 治县。| 安远县| 芜湖县| 简阳市| 安国市| 图木舒克市| 云梦县| 乌海市| 北宁市| 青川县| 海伦市| 阜新| 河源市| 锡林郭勒盟| 文水县| 清流县| 额敏县| 葫芦岛市| 温泉县| 治多县| 博兴县| 黔西| 元阳县| 盐源县| 江源县| 瑞昌市| 洪泽县|