赞
踩
在C语言中,printf
是一个我们常用的函数,我们可以无限传入不同类型的参数,这里就用到了可变参数。我们一般使用形参包...
或者是std::initializer_list
来实现可变参数。不仅函数可以有可变参数,模板也可以有。因此形参包一般可以分为模板形参包和函数形参包。
形参包的声明就是英文省略号,即三个点(...
)。比如如下声明就是没问题的,在C和C++中都可以通过编译:
void print(const char* fmt, ...);
如果是C++的话,可以省略前面那个逗号。下面的声明只能在C++中编译通过,C语言会报错。所以为了与C的兼容性,还是建议使用第一个声明。
void print(const char* fmt...);
要注意的是,形参包只能放在形参列表的末尾。下面的声明就是错误的:
void print(..., const char* fmt); //incorrect
形参列表只包含形参包是合法的,但是我们后续就无法访问实参,原因下文会讲到。
void print(...); //correct, but the params could not be used
在 <stdarg.h> 中提供了va_list
、va_start
、va_arg
、va_copy
、va_end
这一个类型和四个宏函数来实现实参的访问:
在访问之前声明一个va_list类型的变量,比如args
:
void func(int count,...){
va_list args;
}
之后调用va_start初始化,第一个参数传入args
,第二个传入形参包前一个参数,这里是count
,然后在最后调用va_end停止遍历,这就是刚才我们说的如果只在参数列表中声明...
不能访问的原因:
void func(int count,...){
va_list args;
va_start(args, count);
// do something here
va_end(args);
}
va_copy可以拷贝一个va_list到另一个va_list:
std::va_list args1;
va_start(args1, count);
std::va_list args2;
va_copy(args2, args1);
va_arg 需要两个参数,第一个是va_list类型,比如刚才的args
,然后是需要获取的类型。每调用一次,都会修改args使其指向下一个参数。如果下个参数没有访问则是默认值,比如int
类型就是0
。下面有个加法函数的例子:
#include <iostream> #include <cstdarg> int add(int count, ...){ int sum = 0; std::va_list args; va_start(args, count); for (int i = 0; i < count; i++) { int num = va_arg(args, int); sum += num; } va_end(args); return sum; } int main(){ std::cout << add(5, 62 , 13, 7, 45, 11) << '\n'; }
输出内容为:
138
我们以std::vsprintf为例,剩下的还有std::vprintf
, std::vfprintf
, std::vsnprintf
等等,具体可以参考C-style file input/output - cppreference.com。vsprintf是一个把变参格式化并打印到字符串的函数,这样我们可以自己写一个print
函数:
#include <iostream> #include <cstdarg> void print(const char* fmt, ...){ char buf[1024]; std::va_list args; va_start(args, fmt); vsprintf(buf, fmt, args); va_end(args); printf("%s",buf); } int main(){ print("My name is %s. And I'm %d years old.","Chris",17); }
输出内容如下:
My name is Chris. And I'm 17 years old.
我们依旧使用形参包...
传参,但是我们要使用__VA_ARGS__
访问,并且需要传入另一个函数。这时我们就可以直接只在参数列表中声明...
,比如:
#define log(...) print(__VA_ARGS__)
void print(const char* fmt, ...){
char buf[1024];
std::va_list args;
va_start(args, fmt);
vsprintf(buf, fmt, args);
va_end(args);
printf("%s",buf);
}
声明如下:
template <typename T, typename ... Args>
void func(T t,Args ... args);
template<class... Us>
void f(Us... pargs) {}
template<class... Ts>
void g(Ts... args)
{
f(&args...); // “&args...” 是包展开
// “&args” 是它的模式
}
g(1, 0.2, "a"); // Ts... args 会展开成 int E1, double E2, const char* E3
// &args... 会展开成 &E1, &E2, &E3
// Us... 会展开成 int* E1, double* E2, const char** E3
如果两个形参包在同一模式中出现,那么它们同时展开而且长度必须相同:
template<typename...> struct Tuple {}; template<typename T1, typename T2> struct Pair {}; template<class... Args1> struct zip { template<class... Args2> struct with { typedef Tuple<Pair<Args1, Args2>...> type; // Pair<Args1, Args2>... 是包展开 // Pair<Args1, Args2> 是模式 }; }; typedef zip<short, int>::with<unsigned short, unsigned>::type T1; // Pair<Args1, Args2>... 会展开成 // Pair<short, unsigned short>, Pair<int, unsigned int> // T1 是 Tuple<Pair<short, unsigned short>, Pair<int, unsigned>> typedef zip<short>::with<unsigned short, unsigned>::type T2; // 错误:包展开中的形参包包含不同长度
如果包展开内嵌于另一个包展开中,那么它所展开的是在最内层包展开出现的形参包,并且在外围(而非最内层)的包展开中必须提及其它形参包:
template<class... Args>
void g(Args... args)
{
f(const_cast<const Args*>(&args)...);
// const_cast<const Args*>(&args) 是模式,它同时展开两个包(Args 与 args)
f(h(args...) + args...); // 嵌套包展开:
// 内层包展开是 “args...”,它首先展开
// 外层包展开是 h(E1, E2, E3) + args 它其次被展开
// (成为 h(E1, E2, E3) + E1, h(E1, E2, E3) + E2, h(E1, E2, E3) + E3)
}
#include <iostream>
using namespace std;
template<typename... Args>
int getArgNums(Args... args){
return sizeof...(args);
}
int main(){
cout<<getArgNums("Hello World",666,'c');
return 0;
}
结果输出如下:
3
可变参数可以使用递归的方式进行遍历,每次从可变参数中取出第一个元素,然后向下一个参数推导,直到包为空。比如:
#include <iostream> using namespace std; template<typename T> void print(T arg){ cout << arg << endl; } template<typename T, typename... Args> void print(const T &arg, Args... args){ cout << arg << endl; print(args...); } int main(){ print("Hello World",666,'c'); return 0; }
结果输出如下:
Hello World
666
c
C++另外提供了initializer_list来实现变参。不过传参必须使用花括号括起来,而且只能是单一类型,然后可以使用迭代器访问:
#include <iostream>
#include <string>
using namespace std;
void print(initializer_list<string> ls) {
for (auto i = ls.begin(); i != ls.end(); ++i)
cout << *i << " ";
cout << endl;
}
int main(){
print({"Hello World","lalala","cpp"});
return 0;
}
输出如下:
Hello World lalala cpp
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。