可变参数
可变参数看起来好像只是高级语言的特性,其实C语言也有,这种能力来自于指针和编译器的支持。
访问main()函数中的参数
main()函数可以接受控制台命令传入的任意参数,它将参数放入一个指针数组中,在学习指针之前我们不知道如何访问这些参数,现在再来看就会觉得很简单。main()函数的格式为:
int main(int argc, char *argv[])
{
return 0;
}
其中argc是参数个数,*argv[]是指针数组,存放参数的字符串,现在获取命令行参数很简单了,例如:
#include<stdio.h>
int main(int argc, char* argv[])
{
int i;
if(argc>0) for(i=0;i<argc;i++) puts(argv[i]);
return 0;
}
由于argc和argv本身就可以当作变量使用,遍历的代码可以更简单:
#include<stdio.h>
int main(int argc, char* argv[])
{
while(argc--) puts(*argv++);
return 0;
}
打开命令控制台,输入demo.exe a b c,结果为:
demo.exe
a
b
c
可以看到程序名作为第一个参数传入,如果demo.exe不在当前目录下,还会传入完整的路径名,例如:e:\demo\debug\demo.exe,这对于解析路径很有用。为了方便调试很多IDE都支持传入参数,例如在VS的项目属性面板中设置命令参数:

函数接受可变参数
我们已经看到C语言中不少标准库提供的方法可以接受任意参数,像printf()和scanf()是用的最多的,那么如何自定义函数让它接受任意参数呢?我们首先想到main()函数,它将所有接受的参数放到一个数组argv[]中,但那不是C语言定义的标准而是编译器在背后帮我们做的事情,程序启动后自动将控制台输入的字符串解析后放入到数组argv[]中的,实际传给main()函数的参数只有2个。现在我们要求接受的参数是任意个,C语言的确有接受可变参数的函数写法,例如:
void fun(int first, …)
像printf(),scanf()都是使用这个标准,那么它的原理和使用方法是怎么样的呢?stdarg.h库提供了对可变参数的支持,它支持带省略号的函数原型。函数原型必须至少包含一个参数,可变参数必须放在最后,省略号前面一个参数起着特殊作用,它帮助stdarg库进行初始化变参列表,通常表示变参个数。变参被放入一个列表中,通过调用stdarg提供的函数进行访问,一个典型的过程如下:
#include<stdio.h>
#include<stdarg.h>
void fun(int len, ...) //len是变参个数
{
va_list pr; //声明一个参数列表指针
va_start(pr, len); //初始化参数列表,需要省略号前面的参数,初始化后变参会被放入这个参数列表中
//va_arg()是一个宏,它传入列表指针和需要访问的参数类型
int arg1 = va_arg(pr,int); //第一次调用返回变参第一个参数,返回类型与传入类型相同
float arg2 = va_arg(pr,float);//第二次调用返回变参第二个参数,返回类型与传入类型相同
double arg3 = va_arg(pr,double); //第三次调用返回变参第三个参数,返回类型与传入类型相同
va_end(pr); //关闭pr指针,完成宏清理工作
printf("%d,%f,%f",arg1,arg2,arg3);
}
int main(void)
{
fun(5, 2.0,3.0);
return 0;
}
注意va_start()和va_arg()均是宏,va_start()通过第一个参数len设置pr的位置,让pr指向变参列表首地址,va_arg()根据int或float类型移动读取指针,当调用va_end()后pr会被设置为NULL,只有重新用va_start()初始化pr后才能再次使用,为了方便编程,stdarg库提供一个克隆宏va_copy()用来备份pr指针,例如:
va_copy(prcopy,pr);
如果函数接受的参数类型相同就可以使用循环来遍历参数,如下:
#include<stdio.h>
#include<stdarg.h>
void fun(int len, ...) //len是变参个数
{
va_list pr; //参数指针
va_start(pr, len); //初始化变参列表
int arg; //可变参数
for(int i = 0; i < len; i++)
{
arg = va_arg(pr, int); //获取变参
printf("%d\n", arg);
}
va_end(pr); //关闭pr指针,使其指向null
}
int main(void)
{
fun(5,100,101,102,103,104);
return 0;
}
我们也可以将省略号前面的参数看作第一个参数,但需要设置结束标志,例如:
#include<stdio.h>
#include<stdarg.h>
void fun(int first, ...)
{
va_list pr;
int value =first; //需要输出的可变参数
int count=0; //统计可变参数的数目
va_start(pr,first);
do
{
count++;
printf("第%d个参数: %d\n",count,value); //输出各参数的值
value = va_arg(pr, int);
} while(value != -1); //因为变参个数未知,必须手动设置结束条件,这里条件为参数值为-1
va_end(pr);
}
int main(void)
{
fun(1,2,3,-1);
fun(100,101,102,103,104,-1);
return 0;
}
pr实际上是读写指针,类似顺序读写,每次调用va_arg()后指针移到下一个参数,因此也可以直接通过*pr获取。上面代码通过设置结束条件为-1来结束循环,适合输入正整数或字符。为什么printf()中不必设置参数个数或结束条件呢?因为格式化字符串中已经给出了参数类型和参数个数,只需从格式化字符串中读取就行了。如果我们也想按照这种思路编写,就需要自己编写解析格式符的代码,但解析格式符很复杂,因为C包含众多的格式符,为了方便调试C提供了一些能接受格式符和变参的函数:
vprintf () 将变参按照格式符输出到标准输出
vfprintf() 将变参按照格式符输出到文件
vsprintf() 将变参按照格式符写入到字符串
三个函数使用方法大体相同,下面用vsprintf()举一个例子:
#include <stdio.h>
#include <stdarg.h>
#include<string.h>
char buffer[80];
int vspfunc(char* format, ...)
{
va_list aptr;
int ret;
va_start(aptr, format);
int count = 0;
int len = strlen(format);
char* p=format;
while (p = strchr(p, '%'))
{
count++;
p++;
}
printf("count=%d\n",count);
ret = vsprintf(buffer, format, aptr);
va_end(aptr);
return(ret);
}
int main()
{
int i = 5;
float f = 27.0;
char str[50] = "runoob.com";
vspfunc("%d %f %s", i, f, str);
printf("%s\n", buffer);
return(0);
}
如果需要将函数变参输出到控制台或写入文本,可以直接调用vprintf()和vfprintf()。
结合变参宏
C还为函数变参提供了处理变参的宏,在宏中将…解释为__VA_ARGS__,现在为上例中的函数vspfunc()定义宏VSP:
#include <stdio.h>
#include <stdarg.h>
#include<string.h>
#define VSP(format, ...) vspfunc(format, __VA_ARGS__)
char buffer[80]="";
int vspfunc(char* format, ...)
{
va_list aptr;
int ret;
va_start(aptr, format);
int count = 0;
int len = strlen(format);
char* p = format;
while (p = strchr(p, '%'))
{
count++;
p++;
}
printf("count=%d\n", count);
ret = vsprintf(buffer, format, aptr);
va_end(aptr);
return(ret);
}
int main()
{
int i = 5;
float f = 27.0;
char str[50] = "runoob.com";
VSP("%d %f %s", i, f, str);
printf("%s\n", buffer);
return(0);
}
在宏VSP中,第一个参数format是定参,第二个…是变参,不管在函数中还是在宏中…都必须放在最后。
C语言通过指针和编译器支持实现可变参数功能。本文介绍了如何访问main()函数中的参数,如何自定义接受可变参数的函数,以及如何结合变参宏进行操作。示例代码展示了如何使用stdarg.h库中的宏来处理可变参数列表。
1142

被折叠的 条评论
为什么被折叠?



