浅析可变参数列表

C语言中的可变参数列表
首先写一段简单的代码用来理解可变参数列表
分别使用可变参数,实现函数,求函数参数的平均值和最大值。

#include<stdio.h>
#include<stdarg.h>
int average(int n, ...)
{
	va_list arg;
	int i = 0;
	int sum = 0;
	va_start(arg, n);//初始化arg为未知参数列表的第一个参数的地址
	for (i = 0; i < n; i++)
	{
		sum += va_arg(arg, int);
	}
	return sum / n;
	va_end(arg);
}
int max(int n, ...)
{
	va_list arg;
	int i = 0;
	int max = 0;
	va_start(arg, n);
	for (i = 0; i < n; i++)
	{
		int val = va_arg(arg, int);
		if (val>max)
		{
			max = val;
		}
	}
	va_end(arg);
	return max;
}
int main()
{
	int a = 1;
	int b = 2;
	int c = 3;
	int avg1 = average(2, a, c);
	int avg2 = average(3, a,b, c);
	int max1 = max(2, a, c);
	int max2 = max(3, a, b, c);
	printf("avg1=%d\n", avg1);
	printf("avg2=%d\n", avg2);
	printf("max1=%d\n", max1);
	printf("max2=%d\n", max2);
	return 0;
}

      可变参数列表是通过宏来实现的,这些宏定义在stdarg.h头文件中,在这个头文件中声明了 一个类型va_list和三个宏va_start、va_arg、va_end配合使用,访问参数的值
在vs中我们可以转到定义处查看各个类型和宏具体是怎样实现的
1.va_list  arg;   的意思是定义了一个arg变量,这个arg用于访问参数列表未确定的部分。相当于 一个声明。
typedef char * va_list
#define _INTSIZEOF(n) ((sizeof(n)+sizeof(int)-1)&~(sizeof(int) - 1) )
#define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) )

2. va_start(arg,num)    这个宏的意义是对 arg 进行初始化,其第一个参数是va_list变量的名字arg,第二个参数是省略号前最后有明确参数的变量。在初始化的过程,arg被设置为指向可变参数部分第一个参数

#define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
NTSIZEOF(n);//当n的大小为1或2或3字节时,INSIZEOF(n)=4 
             //当n的大小为5或6或7个字节时,INSIZEOF(n)=8 
             //...

即上面的代码中va_start(arg, n)就可以替换为:
arg=(char *)&n + 4;//把n的地址取出并强制类型转换为char*类型
                   //再给n的地址加4并让arg指向将这个地址
                   //即让arg指向参数部分的第一个参数

3.   va_arg(arg,int)这个宏的意思时按第二个参数类型读取可变参数列表。va_list定义的变量arg和列表中下一个参数的类型。特别注意va_arg这个宏会返回这个参数的值,并且指向下一个可变参数。
#define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )

将va_arg(arg, int)进行替换:

sum+=(*(int *)((arg+=4)-4));//先将arg加4的值赋给arg
                            //使得arg指向的位置向后挪动4个字节
                            //再将arg-4 使得表达式结果所产生的地址指向原先的位置
                            //将所得到的结果强制类型转换为int*类型并进行解引用

这段代码的作用:取出当前位置参数并为下一次获取参数做好准备。

4.   va_end(arg)宏的意思是使指针置为无效,相当于p=nuLL.

可变参数的局限性

1.我们至少必须知道一个明确的参数,当然如果明确参数的有多个,选择最右的已知参数,因为它距离可变参数变量最近。如果你在访问了几个可变参数之后想半途终止,这是可以的,但是如果你想开始就访问参数列表中间的参数,那是不行的。

2.宏是无法判断数量的,所有需要把长度传过去。不然可能不会终止的向后,导致报错。

3.宏是无法判断类型的,你定义已什么类型读,就会已什么类型读取。所以,可变参数函数在调用时非明确的实参应该是同一类型的。
4.如果在va_arg中指定了错误的类型,那么后果是不可预测的。
下面模拟实现printf函数,可完成下面的功能
//
//能完成下面函数的调用。
//print("s ccc d.\n","hello",'b','i','t',100);
//函数原型:
//print(char *format, ...)
//

#include<stdio.h>
#include<stdarg.h>
#include<string.h>
void print_int(int n)//递归打印,注意这里的(args,int)是int,整形,不是全都charcharchar

{
	if (n > 9)
	{
		print_int(n / 10);
	}
	putchar(n %10+'0');
}
void my_printf(const char *format, ...)
{
	va_list arg;
	va_start(arg, format);
	while(*format)
	{
		switch (*format)
		{
		case 's':
		{
					char *str = va_arg(arg, char*);//因为是字符串,所以要用char*解引用
					while (*str)
					{
						putchar(*str);
						str++;
					}
		}
			break;
		case 'c':
		{
					char ch = va_arg(arg, char);//单个字符打印
					putchar(ch);
		}
			break;
		case 'd':
		{
					int ret = va_arg(arg, int);
					printf_int(ret);
		}
			break;
		defult:
			putchar(*format);
			break;
		}
		format++;
	}
}
int main()
{
	my_printf("s ccc.\n", "hello", 'b', 'i', 't');
	system("pause");
	return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_41268108/article/details/80431958