一. C++ ----》可变参数包 可以获取任意参数 


// 可变参数包的本质还是跟c中可变参数列表有些不一样
// 他确实类似c将来会把数据放在一个栈上然后让可变参赛包对象来管理他 
// 但是这都是基于模板实例化来的
// 都是函数的栈帧传参的过程,根据类型来获取对应内存
// 但是c++有了模板实例化的内存推导 所以演化出可变参数包   
 
// 将来可变参数包就好像所有参数都被放到了这个包一样 
template<class ...Arg>
void showlist(Arg...arg) {

}

int main() {
    showlist();//不传任意参数
	showlist(1,"231",string(),vector<int>());
	return 0;
}

         也就是说将来我们可以直接传参任意参数(0-N)个都可以, 有可变参数包的函数可以从这个包中获取对象,但是遗憾的是我们是不能直接这样获取包里面的对象如:

arg[i]  所以我们可以这样做

二 . 模板参数包的展开符 ... 三个省略号你知道哪些

        我们刚刚提到当你声明了一个可变参数包,那意味着可以对这个声明了参数包的参数传递任意参数,但是传递是一回事,那展开呢? 也就是说我想拿出来用啊!

        如果仅仅只能传参不能使用他们太挫了:   比如线程传参了很多参数 

(1,2,23,"231313")  但是我想把这些参数一一都取出来怎么弄?  这就要提到模板参数表的展开符了如下三个场景:

    类型展开和传参展开 

template<class ...Arg>
void func(Arg... args) {

	tuple<Arg...>ret(args...);// 前面是类型展开 ,后面是值内容的展开 
	// 打印处理 , args的展开只能传递给其他的可变参数包或者一个表达式

	cout<<std::get<0>(ret)<<endl;
	cout << std::get<1>(ret) << endl;
	cout << std::get<2>(ret) << endl;

}


int main() {

	func(1,"231",string("hello this is tuple!"));
	return 0;
}

   这是一个很经典的展开符的使用,首先你要知道编译器的让步,在C++11以后,dear编译器为展开符做了特殊处理,也就是为了可变参数包对象和模板可变参数表做了一个特殊处理,将来可以在让可变参数包对象展开和类型展开。 

函数传参展开 
 

     可变参数包的展开:   当我们直接放到一个函数传参的时候直接展开当前可变参数包中的对象  如将来我们的可变参数包有{"hello",1,2,3}   func(args...)   ----> 展开为   func("hello",1,2,3)

void func3(int a ,string str,char c){
	
	////
}


template<class...Arg>
void func2(Arg...args) {

	func3(args...);//  将来对发来的args存放的参数包根据类型展开 
      // 如 func3(1,"hello",'a'); 

}



int main() {
	func2(1,"hello",'a');// 这是允许的
//	func2(1,"231",string("hello this is tuple!"));
//传参跟将来的真正使用的传参对象对不上的时候报错编译器报错
	return 0;
}

表达式展开和

void func4(Arg...args) {


	//vector<int>{args...};// 这样展开也行 
	int arr[] = {(args,0)...}; // 加入传进来的是 一个 1,2,3
	// 展开就变成了 {(1,0),(2,0),(3,0)}

}

三. 可变参数包的使用 

     当dear读者看到这,可变参数包的使用基本没有任何问题,因为可变参数包的使用都是基于展开来的,下面我们来举例几个场景吧:

 3.1 递归获取每一个参数的方式

     

/ 获取可变参数包的每一个元素 
// 1. 函数传参

template<class T,class...Args>
// 假设传进来 {"hello" ,1,3,string("test") }
// t会被实例化为 t对象 ,然后剩余的被放入参数包 
void test_get_parameters(t, Args...args) {

	// 可变参数包的原理是 每次展开拿出来的就之后就不会再指向了/
	// 所以可以一个一个的拿出来,我们可以通过参数类型推导来
	//拿出来 所以我们定义一个模板类来拿出来
	// 假设将来参数包的参数都能被打印 
	cout << t;
    //  递归调用自己 
    test_get_parameters(args...); // 这里记得展开 


}

int main(){

   test_get_parameters("hello" ,1,3,string("test")) ;
}

     上面的代码有一点小bug,如果你能理解那么也就意味着你离掌握可变参数包已经99.999% ,是的,我们每一层传参的都是把第一个参数传进去,你很好奇为什么递归调用传参可以直接把args...展开呢 ,这是因为我们的编译器在编译之前只知道你是可变参数包,才不会管你调用的对象有多少个参数呢,所以直接调用。

        是的每一次展开这个可变参数包第一个对象被T 模板对象获取到了然后打开,但是问题是最后一次比如当可变参数包没有内容了,那岂不是传一个空的参数岂不是就报错了,是的你很good! 所以正确代码如下: 定义一个递归结束条件函数

void test_get_parameters() {
	//  我们空参调用啥也不做 
}
//template<class T>
//void test_get_parameters(T t) {
//	//  只有一个参数的时候最时候的就是这个函数 调用他
//	// 同样可以作为递归结束函数
// cout << t;
//}

// 获取可变参数包的每一个元素 
// 1. 函数传参

template<class T,class...Args>
void test_get_parameters(T t, Args...args) {

	// 可变参数包的原理是 每次展开拿出来的就之后就不会再指向了/
	// 所以可以一个一个的拿出来,我们可以通过参数类型推导来
	//拿出来 所以我们定义一个模板类来拿出来
	// 假设将来参数包的参数都能被打印 
	cout << t;


}

3.2  表达式获取每一个参数

3.2.1 sizeof...

        熟话说为了爱不顾一切,c++为了计算可变参数包的存放的参数大小也是有一个运算符的,他不同于sizeof 计算的是类型大小。

        sizeof... 是专门计算可变参数包的参数个数,注意是个数

3.2.2 模板参数获取
// 表达式获取 
template<class... Arg>
void test_exprision(Arg...args) {


	//将来传进来的多个参数我们直接放入一个元组中
	// c++创建了一个sizeof...
	int size = sizeof...(args);
	std::tuple<Arg...>t;// 依旧认为传递进来的参数都能被我们打印
	   
	 // 假设传进来的就有三个参数 我们自己是知道的 
	cout << get<0>(t) << endl;
	cout << get<1>(t) << endl;
	cout << get<2>(t) << endl;

}


int main() {

	test_exprision("hello",1,2,3,42321,string("dasdddasdsa"));
	return 0;
	
}

3.2.3 表达式获取

  

template<class... Arg>
void test_exprision(Arg...args) {

	
	int arr[] = {(use(args), 0)...};
	// 将来这个数组的调用会被展开为 
	// (use("hello"),0) ,(use(0),0),()  每一个括号都是对应的都会表达式 
}
int main() {

	test_exprision("hello",1,2,3,4,string("dasdsda"));

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐