可变参数列表源码分析及函数设计

目录

一、什么是可变参数?

二、可变参数的实现

三、可变参数源码

四、模拟实现printf函数


一、什么是可变参数?

    可变参数是一个比较有意思的实现,通过将函数实现为可变参数的形式,可以使得函数可以接受1个以上的任意多个参数。      

    一般情况下,我们写的函数参数的数目是固定不变的,调用函数时要给出相应的实参,但有些时候,为了做同样的事情,由于参数个数不同,我们总不能写很多个函数,这就有点冗余了。比如:我们想求两个数或10个数的平均数,可以用同一个函数实现。

二、可变参数的实现

C语言头文件stdarg.h中提供了一个va_list的数据类型和va_start,va_arg,va_end三个宏

        1.声明一个 va_list 类型的变量 arg ,它用于访问参数列表的未确定部分。
        2.这个变量是调用 va_start 来初始化的。它的第一个参数是 va_list 的变量名,第2个参数是省略号前最后一个有名字的参数。初始化过程把 arg 变量设置为指向可变参数部分的第一个参数。
        3.为了访问参数,需要使用 va_arg ,这个宏接受两个参数: va_list 变量和参数列表中下一个参数的类型。在这个例子中所有的可变参数都是整型。va_arg````返回这个参数的值,并使用 va_arg```指向下一个可变参数。
        4.最后,当访问完毕最后一个可变参数之后,我们需要调用 va_end 。

举例:求不同个数的平均数

int avg(int n, ...)
{
	va_list arg;
	va_start(arg, n);//arg指向可变参数的第一个参数
	int sum = 0;
	for (int i = 0; i < n; i++)
	{
		sum += va_arg(arg, int);//返回该参数的值并自动指向下一个参数
	}
	va_end(arg);
	return sum / n;
}
int main()
{
	int a = 1, b = 2, c = 3,d=10;
        printf("%d\n", avg(4,a, b, c,d));
	printf("%d\n", avg(2,a, c));
}

并不是所有的函数都可以使用可变参数列表,使用时要注意:

1>可变参数必须从头到尾逐个访问。如果在访问了几个可变参数之后想半途终止,这是可以的,但是,如果一开始就访问参数列

表中间的参数,那是不行的。

2>参数列表中至少有一个命名参数。如果连一个命名参数都没有,就无法使用va_start。

3>这些宏是无法直接判断实际存在参数的数量;这些宏无法判断每个参数类型。
 

三、可变参数源码

可变参数的实现过程是使用宏的封装。只要完成替换,就可以自行分析了。

1.va_list:是一种数据类型,可以说就是char*。

        typedef char* va_list;

2.va_start:初始化arg为未知参数列表的第一个参数的地址,va_start其实是一个宏函数,第一个参数是一个可以表示后面的参数个数和参数类型的char* ,第二个参数表示就是用于指明arg是指针名,n代表第一个参数,用于表示起始端。

  #define _INTSIZEOF(n)          ((sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1))

  #define _ADDRESSOF(v) (&(v))
  
  #define __crt_va_start_a(ap, v) ((void)(ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v)))

3.va_arg:返回当前参数再指向下一个参数。

 #define _INTSIZEOF(n)          ((sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1))

 #define __crt_va_arg(ap, t)     (*(t*)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)))

通过 va_arg(arg, int) 拿到后面的参数:

1.ap表示可变参数指针,而t表示数据类型。使用 ((sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1)) ,传入char,float,double等小于4字节的类型,都会返回4。如果类型大小是,5,6,7的话,则返回8。即返回当前一组数中靠近4的倍数的值。 

2.( (t )((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) ) 该表达式先让指针ap加上4字节的大小,再把减去4字节大小处所对应的值返回。用t强制类型转换,再解引用,此处 t是传入参数的数据类型。

4.va_end:结束。arg赋值为空指针。

  #define __crt_va_end(ap)        ((void)(ap = (va_list)0))

四、模拟实现printf函数

模拟实现printf函数,可完成下面的功能 
//能完成下面函数的调用。 
//print("s ccc d.\n","hello",'b','o','y',100); 
 

void print_num(int n)
{
	if (n > 9)
		print_num(n / 10);
	putchar(n % 10 + '0');
}
void print(const char* format, ...)
{
	va_list arg;
	va_start(arg, format);
	
	while (*format)
	{
		switch (*format)
		{
			case 's':
			{
				char *str = va_arg(arg, char*);
				while (*str)
				{
					putchar(*str);
					str++;
				}
			}
				break;
			case 'c':
			{
				char ch = va_arg(arg, char);
				putchar(ch);
			}
			break;
			case 'd':
			{
				int tmp = va_arg(arg, int);
				print_num(tmp);
			}
			break;
			default:
				putchar(*format);
				break;
		}
		format++;
	}
}

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值