C++11特性——可变参数

  在传统的C和C++中,函数形参和实参的个数不仅需要保持一致,而且需要显式定义出来,是固定的。在C++11中增加了可变参数这种特性,这篇文章就学习下C++11的可变参数。

  C++11的可变形参提供了两种使用场景:
  1.个数未知,但所有类型相同
  2.类型可能不同,可变参数模板

场景1:个数未知,但保证所有类型相同

  对于这种场景,可以使用initializer_list的标准库类型,该类型被定义在头文件#include<initializer_list> //头文件
  initializer_list提供了一些操作,如下图:
在这里插入图片描述
参考:C++ 11 新特性:可变形参_freshman94的博客-CSDN博客_c++可变形参
  有一点需要注意的是initializer_list对象中的元素永远是常量值,无法改变。这个在使用上会产生一些制约,只能提供遍历的操作。
  这里引用一个代码实例,很清楚就知道initializer_list的使用方法了:
在这里插入图片描述
参考:C++ 11 新特性:可变形参_freshman94的博客-CSDN博客_c++可变形参

场景2:个数未知,类型可能不同

  对于这种情况,比较常用的是可变参数模板。本质上还是一个模板函数,相对于普通模板提供更多泛化。
参数包指可变数目参数,参数包分为模板参数包,函数参数包
  写个简单模板(这个模板关键字写了class,typename也行):

template<class... T>   // T是模板参数包
void function(T ... args)    // args是函数参数包
{

}

  模板中的省略号…代表一个包含0到n的任意参数包,其中的任意代表任意数目和任意类型。…和参数的位置关系也有含义,…在参数右侧代表一组实参,…参数左侧代表可变参数。引用一个例子做说明:
在这里插入图片描述
  这里的1和2都是表示可变参数,都在参数左侧。而3代表实参,放在参数右侧。
参考:C++11新标准-1.可变模板参数(variadic templates) - 简书 (jianshu.com)
  上面这个例子其实有几个隐藏的知识点,首先是template<class T, class Type>这种结构,代表至少要有一个,模板如下:

template<class T1, class ... T2>
void function(T1 t1,  T2 ... t2) {

}

  上面这个就代表至少要有一个T1类型的参数,和0-n个T2模板包类型的参数。
  其次就是Print函数中本身是一个递归调用,这就涉及到可变参数的两种调用方式:递归调用、非递归调用。
  先看看递归调用,第一步调用处理包的第一个实参,然后使用剩下的实参调用自身模板,直到最后一个(也叫边界条件)。所以在递归调用的使用中是一定要设置边界条件的,看下面这个例子就明白了:

#include <iostream>

void print() {
std::cout << “hello world!<< std::endl;
}

template<typename T, typename... Types>
void print(const T& firstArg, const Types&... args) {
	std::cout << firstArg << " " << sizeof...(args) << std::endl; // sizeof ... args代表获取参数个数
	print(args...);
}

template <typename... Types>
void print(const Types&... args) {
  std::cout << "print(...)" << std::endl;
}

int main(int argc, char *argv[]) {
	print(2, "hello", 1);

	return 0;
}

这个代码的执行结果是:

2 2
hello 1
1 0
hello world

  这个代码有几点需要注意:
  1.两个模板函数重载,为什么调用第一个?
  这个是因为当较泛化和较特化模板函数同时存在时,回优先选择较特化的。第一个代表至少一个,是较特化。
  2.边界条件的存在
  边界条件是一定要存在的(不存在编译过不去),一般以同名函数的无参数版本存在。

参考:【C++】C++11可变参数模板(函数模板、类模板)_Yngz_Miao的博客-CSDN博客_c++11可变参数模板
  对于非递归调用,就是一次性直接输出了,代码如下:

#include<iostream>

// 形式1:...在左边
template<typename T, typename... Types>
void print1(const T& firstArg, const Types&... args) {
        std::cout << "print1" << std::endl;
        std::cout << firstArg << std::endl;
        (std::cout << ... << args) << std::endl;
}
// 形式2:...在右边
template<typename... Types>
void print2(const Types ... args) {
        std::cout << "print2:" << std::endl;
        ((std::cout << args<< " "), ...) << std::endl;
}


int main() {
        print1(2, "hello");
        print2(3, "world");
        return 0;
}

  有的时候可能发生在继承场景下,对子类会有限制,需要和父类保持个数一致比如:在这里插入图片描述
  子类BMW继承了Car,Car规定模板两个参数,BMW继承的也不能超过两个或者小于两个,如果是BMW<int, char, int> car报错,BMW< int> car也会报错。
参考:C++11新特性之五——可变参数模板 (bbsmax.com)

printf可变参数实现原理

  比如printf(“%d%s”, 1, “hello world”);在读取时从右往左读,可以分为三个部分(两个变量一个字符串),以压栈的方式存储。存储的内容是变量的地址,把”%d%s”的指针作为基准A,对%d%s进行解析(从左往右),分成一组一组的,比如%d或者%s。地址指针的大小是固定的,所以A+4B就是第一个参数的地址,A+8B就是第二个依次类推,根据解析出来的个数进行输出,实现可变参数。

因作者水平有限,如有错误之处,请在下方评论区指出,谢谢!

  • 10
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值