一、可变参数函数的实例
大家熟知的printf()函数声明如下:
int printf(const char * format, ...);
它除了有一个参数format固定以外,后面跟的参数的个数和类型是
可变的,例如我们可以有以下不同的调用方法:
printf("%d",i);
printf("%s",s);
printf("the number is %d ,string is:%s", i, s);
二、如何编写一个自已的可变参数函数.
查了一下,在<stdarg.h>中定义了三个宏va_start()、va_arg()和va_end()用于实现可变参数。
void va_start( va_list arg_ptr, prev_param );
type va_arg( va_list arg_ptr, type );
void va_end( va_list arg_ptr );
试编了一个:
#include <stdio.h>
#include <stdarg.h>
#define ENDING_INT 0
int SumAll(int number1,...) //把参数加总
{
va_list arg_pointer; //首先定义一个va_list型的变量,这个变量是指向参数的指针.
int current_number; //当前的数字
int total; //数字之和
//用va_start初始化变量arg_pointer,这个宏的第二个参数是第一个可变参数(一个固定的参数)
va_start(arg_pointer,number1);
total=number1;
do
{
//用va_arg返回后续的可变参数, 类型是 int
current_number=va_arg(arg_pointer,int);
total += current_number;
}
while (current_number!=ENDING_INT); //如果参数是结束标识(这里是ENDING_INT),则结束
va_end(arg_pointer); //结束参数列表
return total;
}
int main(int argc, char* argv[])
{
int n;
n=SumAll(100,200,ENDING_INT); //返回结果是300
printf("%d /n",n);
n=SumAll(100,200); //由于没有结束标识,返回结果不确定
printf("%d /n",n);
}
因为va_start, va_arg, va_end等定义成宏,所以可变参数的类型和个数需要由程序代码控制。
一般来说,设一个结束标识,这里是 ENDING_INT。用它来识别不同参数的个数。
SumAll(100,200,ENDING_INT); //调用方式正确,返回结果是300
SumAll(100,200); //调用方式不正确,由于没有结束标识,返回结果不确定。
三、理解va_start、va_arg和va_end
看一下<stdarg.h>中宏的定义
定义:typedef char * va_list;
理解:va_list 就是一个指针,指向参数列表。
定义:#define _INTSIZEOF(n) ((sizeof(n)+sizeof(int)-1)&~(sizeof(int) - 1) )
定义:#define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) )
理解:va_start 宏, 就是把ap赋值为参数v起始的参数列表的下一个参数
定义:#define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
理解:va_arg宏,就是把ap赋值为下一个参数
定义:#define va_end(ap) ( ap = (va_list)0 )
理解:va_end宏,就是把ap赋值为空(0)
从va_*的实现可以看出,充分运用指针,把C语言的灵活特性表现得淋漓尽致。当然,用不好也容易出错。
va_*中,为了得到所有传递给函数的参数,需要用va_arg依次遍历。但是有两个要求:
(1)要确定参数的类型。
一般来说,各个参数的类型是一样的。
(2)要有结束标志。如果没有结束标志,va将按默认类型依次返回内存中的内容,直到访问到非法内存而出错退出。
所以上述的调用 SumAll(100,200); 返回的结果是不确定的。
四、再写一个参数类型是 char * 的 可变参数函数.
#include <stdio.h>
#include <string.h>
#include <alloc.h>
#include <stdarg.h>
#define ENDING_STRING NULL
//把多个字符串连接起来
char *StrCat(char *src,...)
{
va_list va;
const char *src_pointer;
char *dest; /* 结果字符串 */
size_t dest_size; /* 结果字符串的大小*/
/* 计算字符串的大小 */
va_start (va, src); /* 开始变长参数处理 */
src_pointer = src;
dest_size = 1;
while (src_pointer!=ENDING_STRING) /* ENDING_STRING == NULL */
{
dest_size += strlen (src_pointer);
src_pointer = va_arg (va, char *); /* 取下一个参数 */
}
va_end (va); /* 结束变长参数处理 */
/* 申请内存 */
dest = malloc( dest_size );
if (dest == NULL) return (NULL);
/* 逐个复制字串到结果字符串 */
va_start (va, src); /* 开始变长参数处理 */
src_pointer = src;
dest [0] = '/0'; /* 先设置为空串 */
while (src_pointer!=ENDING_STRING) /* ENDING_STRING == NULL */
{
strcat (dest, src_pointer); /* 复制字串到结果字符串 */
src_pointer = va_arg (va, char *);
}
va_end (va); /* 结束变长参数处理 */
return (dest);
}
int main(int argc, char* argv[])
{
int n;
char *s;
s=StrCat("hello"," ","world",NULL);
printf("%s /n",s); //返回结果是 hello world
}
嗯,还是比较好用的,千万不要忘记: 调用时要加上结束标识符哦。
可能要问,为什么 printf() 函数调用时没有结束标识符呢?
了解了一下 printf()的源码,我是这样理解的。
printf(char *format,...) 中,在format参数中,就可以判断出后续参数的个数和类型,因此不需要结束标识符就可以知道参数的个数了。比如:
printf( "%s %d", "hello", 1);
"%s %d"表明后续参数个数为2个,第一个是 string类型,第二个是 int 类型。
试一下,如果写成这样
printf( "%s %d %d", "hello", 1);
"%s %d %d"表明后续参数个数为3个,实际上是两个,由于缺了一个,返回结果是不可确定的。
呵呵,调用printf()是要小心出错。