回忆一下自己常用的printf库函数,你可能会发现一些端倪。
#include <stdio.h> int main(int argc, char const *argv[]) { printf("hello c\n"); printf("%s\n""hello world\n""%d\n", argv[0], argc); return 0; }
printf库函数接收的参数可以是多个,这里的多个是不缺定的数字,可能只有一个参数,或者两个甚至更多。但是最少有一个参数。让我们来查一下库函数printf的声明:
int printf (__const char *__restrict __format, ...);
从声明中可以看出,printf中第一个参数是固定的(一个格式化的字符串常量),而后面的参数列表没有给定。printf库函数的实现运用了C语言中的一个特性,支持可变参数列表。首先,printf库函数族与scanf库函数族都运用了此特性,说明这个特性的存在肯定是有意义的。
接下来来介绍一下可变参数列表怎么使用。
借助于printf库函数的声明可以看出,怎么定义可变参数列表。首先确定的参数要在前面列出来,之后不缺定的参数在后面以"..."的形式给出。
那么怎么来使用后面可变的参数列表呢?这里要用到一些宏。这些宏存在于stdarg.h这个头文件中,声明了一个类型va_list和三个宏va_start, va_arg, va_end。要想访问并使用后面的可变的参数列表,就绪要借助于这些。先面先给出一个简单的例子,再来讲解这些宏是怎么使用的。这个简单的例子看似没有太大的实际意义,只供讲解使用。
这个例子要实现计算某些月的工资的平均值,这里有几个月的工资是不缺定的,而且每个月的工资数也是不确定的。返回结果只需是平均值的整数部分即可。实现代码如下:
#include <stdio.h> #include <stdarg.h> int main(int argc, char const *argv[]) { int average_salary(int count_months, ...); int first_result = average_salary(3, 9000, 9200, 9700); int second_result = average_salary(1, 9700); printf("Three-month average wage is : %d.\n", first_result); printf("One-month salary is : %d.\n", second_result); return 0; } int average_salary(int count_months, ...) { va_list my_args; int count= count_months; int sum_salary = 0; va_start(my_args, count_months); while(count-- > 0) sum_salary += va_arg(my_args, int ); va_end(my_args); return sum_salary / count_months; }
下面来讲解一下代码的实现。首先,要先声明一个va_list类型的变量my_args。之后调用宏va_start来进行初始化工作,宏va_start的第1个参数为va_list变量的名字,第2个参数是省略号"..."前面的最后一个有指出的参数名。这里再解释一下,如果我的average_salary的参数为(int count_months, int total, ...),那么调用宏va_start时第二个参数应为total。va_arg宏是用来访问可变参数列表的,第1个参数为va_list变量的名字,第2个参数为可变参数列表中下一个参数的类型。调用va_arg宏之后会返回这个参数的值,并将my_args指向下一个可变参数。最后在访问可变参数列表结束后调用宏va_end来结束对可变参数列表的访问,这个宏只接收va_list类型的变量名一个参数。
使用时需要注意的几点:
这里贴出FreeBSD系统中的实现(见参考资料2),只给出部分调用,有兴趣可自行查阅。
int printf(char const * __restrict fmt, ...) { int ret; va_list ap; va_start(ap, fmt); ret = vfprintf(stdout, fmt, ap); va_end(ap); return (ret); }
int vfprintf(FILE * __restrict fp, const char * __restrict fmt0, va_list ap) { return vfprintf_l(fp, __get_locale(), fmt0, ap); }
int vfprintf_l(FILE * __restrict fp, locale_t locale, const char * __restrict fmt0, va_list ap) { int ret; FIX_LOCALE(locale); FLOCKFILE(fp); /* optimise fprintf(stderr) (and other unbuffered Unix files) */ if ((fp->_flags & (__SNBF|__SWR|__SRW)) == (__SNBF|__SWR) && fp->_file >= 0) ret = __sbprintf(fp, locale, fmt0, ap); else ret = __vfprintf(fp, locale, fmt0, ap); FUNLOCKFILE(fp); return (ret); }
static int __sbprintf(FILE *fp, locale_t locale, const char *fmt, va_list ap) { int ret; FILE fake = FAKE_FILE; unsigned char buf[BUFSIZ]; /* XXX This is probably not needed. */ if (prepwrite(fp) != 0) return (EOF); /* copy the important variables */ fake._flags = fp->_flags & ~__SNBF; fake._file = fp->_file; fake._cookie = fp->_cookie; fake._write = fp->_write; fake._orientation = fp->_orientation; fake._mbstate = fp->_mbstate; /* set up the buffer */ fake._bf._base = fake._p = buf; fake._bf._size = fake._w = sizeof(buf); fake._lbfsize = 0; /* not actually used, but Just In Case */ /* do the work, then copy any error status */ ret = __vfprintf(&fake, locale, fmt, ap); if (ret >= 0 && __fflush(&fake)) ret = EOF; if (fake._flags & __SERR) fp->_flags |= __SERR; return (ret); }
__vfprintf函数实现太长,请自行查阅。
1. Pointers on c, Kenneth A. Reek
2. http://www.freebsd.org/cgi/cvsweb.cgi/src/lib/libc/stdio/#dirlist
说明:
如有错误还请各位指正,欢迎大家一起讨论给出指导。
上述例子程序完整代码的下载链接:
https://github.com/zeliliu/BlogPrograms/tree/master/C%20C%2B%2B%E5%AD%A6%E4%B9%A0/variable%20parameters
最后更新时间:2013-06-13