C学习—可变参数列表

一、引言

回忆一下自己常用的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类型的变量名一个参数。

三、注意事项

使用时需要注意的几点:

  1. 可变参数列表中的参数必须从头到尾按顺序逐个访问。但是对参数的访问允许中途退出,但是不允许跳跃访问,即不是按顺序的连续访问(注意,可以外加控制对某些参数跳过),换句话说这个可变参数列表的访问有点类似于单向链表的访问,每次都要从头开始,并且需要逐一的链接指向进行访问,不能向数组一样进行随机访问。
  2. 所有作为可变参数列表的参数在传递个函数值时都将执行缺省参数类型的提升。
  3. stdarg.h头文件定义的这些宏并没有办法来判断实际存在的参数的数量,也无法判断每个参数的类型。(但是这个可以用命名参数来实现,如printf函数中的,后面给出printf函数的实现)。

四、printf函数的实现

这里贴出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

你可能感兴趣的:(c,可变参数列表,函数参数)