stdarg.h可变参数列表(非格式控制)

1.C语言可变参数的概念

最常见的就是scanf和printf函数:

int scanf(const char * restrict format,...);
int printf(const char *fmt, ...);

你可以输入任意类型的任意个参数,但是必须在格式化字符串中确定输入参数的个数和类型。

那么我们如何自定义可变参数函数呢?
就需要使用stdarg.h头文件了。stdarg的全称就是standard arguments(标准参数),主要目的就是为了让函数能够接收可变参数。
它为用户定义了4个标准宏:

/* Define the standard macros for the user,
   if this invocation was from the user program.  */
#ifdef _STDARG_H
 
#define va_start(v,l)	__builtin_va_start(v,l)
#define va_end(v)	__builtin_va_end(v)
#define va_arg(v,l)	__builtin_va_arg(v,l)
#if !defined(__STRICT_ANSI__) || __STDC_VERSION__ + 0 >= 199900L || defined(__GXX_EXPERIMENTAL_CXX0X__)
#define va_copy(d,s)	__builtin_va_copy(d,s)
#endif

注意:如果想要使用stdarg.h中的宏定义和类型对象,必须显示定义头文件#include <stdarg.h>

同时它定义了一个类型va_list。
函数首先需要定义一个va_list型的变量,这个变量是指向参数的指针. 用来存放可变参数的类型。va_list的定义如下:

typedef struct
 {
	char *a0; /* pointer to first homed integer argument */
	int offset; /* byte offset of next parameter */
} va_list

接下来先介绍4个宏定义:

void va_start(va_list ap, last);

va_start函数初始化了va_list对象ap,为之后的va_arg和va_end函数作准备,所以必须首先调用。
参数last指的是变量参数列表之前的参数名,也就是调用函数中最后一个已知参数类型的参数。比如,printf函数中的fmt
因为last参数的地址会在va_start函数中使用,所以last不应该是一个寄存器变量,函数或者数组类型。

type va_arg(va_list ap, type);

va_arg函数返回ap当前指向的参数的值。
参数ap就是va_start初始化的va_list对象;
参数type是一个类型名,比如“char”,“int”等,表示当前ap指向的参数的类型
每次调用va_arg后,ap就会指向下一个参数。但如果已经遍历完参数列表,或者参数type并不是当前参数的实际类型名,此时调用va_arg函数将会发生随机错误。
ap被参数va_arg函数使用过后,将无法回到最开始的位置

void va_end(va_list ap);

va_end函数和va_start相对应。在同一个函数中,调用过va_start之后就必须调用va_end。
使用va_end以后,变量ap将重置为空,并释放内存。

void va_end(va_list ap);

C99标准。如果想要多次使用参数列表,那么可以使用va_copy函数。
每次调用过va_copy函数后,必须相应的在同一个函数中调用va_end函数,比如:

va_list aq;
va_copy(aq, ap);

va_end(aq)

有些情况下,va_copy函数已经在其他地方有所定义,所以使用相同功能的函数__va_copy。
注意:va_start/va_arg/va_end函数符合C89标准。而va_copy是C99定义的

2.例子1(不用格式控制,参数同一类型int,可变参数个数固定)

#include <stdio.h>
#include <stdarg.h>

//v1.0 最简单的实现,只处理两个参数
void my_format(int a,...)
{
	va_list var;
	int b,c;
	
	va_start(var,a);
	b = va_arg(var,int);
	c = va_arg(var,int);
	va_end(var);

	printf("a=%d,b=%d,c=%d\n",a,b,c);
}
int main()
{
	my_format(6,7);
	my_format(6,7,8);
	my_format(6,7,8,9);
	
	return 0;
}

在这里插入图片描述
说明:
1.定义一个va_list类型的变量,这里是var;
2.定义两个int型的变量,用来接收可变参数,这里是b和c,只定义了两个,所以只能接收两个参数(这里先讨论几个宏的使用,后面再慢慢讨论可变参数)。
3.使用va_start宏初始化,把最后一个确定的参数a指针告诉va_list变量var,并且告诉a的类型(相当于知道了指针移动的距离,就是偏移量),也就相当于知道了第一个可变参数的位置。
4.通过va_start初始化后,知道了第一个可变参数(这里的b,下同)的位置后,通过va_arg获取b的值。var知道b的开始指针,后面的参数接着告诉需要指针移动的距离(取几个字节),这样就能正确的获取b的值。如果这里使用float或者char与实际传入不一样的类型,是错误的(或许巧合可以得到正确的结果)。
5.再次使用va_arg获取c的值。
6.va_end释放变量var的内存,销毁var变量。

这里函数实现的是两个可变参数,main里面分别用三种情况调用:
1.可变参数少于my_fmt处理的个数,1个
2.可变参数等于my_fmt处理的个数,2个
3.可变参数大于my_fmt处理的个数,3个
通过结果大家可以看到,当参数个数小于实际处理的参数个数后,后面va_arg获取的值是随机的。当参数个数大于实际处理的参数个数后,后面的不再处理。所以实现可变参数的函数一定要考虑到各种情况。

3.例子2(不用格式控制,参数类型相同int,可变参数个数随机)

#include <stdio.h>
#include <stdarg.h>

//v2.0 处理可变参数(传入特殊值控制)
void my_format(int a,...)
{
	va_list var;
	int temp;
	
	va_start(var,a);
	printf("a=%d\n",a);
	while (-1 != (temp = va_arg(var,int)))
	{
		printf("temp=%d\n",temp);
	}
	va_end(var);
	printf("\n");
}
int main()
{
	int a = 427653;
	my_format(a,-1);
	my_format(a,-6,7,-1);
	my_format(15,16,-17,18,-1);
	
	return 0;
}
结果:

在这里插入图片描述
这里要说明的是,va_arg获取完可变参数后如果再往后获取,就会得到一个不确定的值,一般我们采取参数传入参数个数和给特定值表示可变参数结束。这里采用的是后者,比如如果是-1则表示可变参数结束。

下面介绍的是前面的情况,传入可变参数的个数

#include <stdio.h>
#include <stdarg.h>

//v2.1 实现可变参数(传入参数个数控制)
void my_format(int size,...)
{
	va_list var;
	int temp;
	
	va_start(var,size);

	while (0 < (size--))
	{	
		temp = va_arg(var,int);
		printf("%d,",temp);
	}
	va_end(var);
	
}
int main()
{
	int size = 1;
	
	printf("\n1.1: ");
	my_format(size);
	printf("\n1.2: ");	
	my_format(size,5);
	printf("\n1.3: ");	
	my_format(size,5,6);

	size = 2;
	printf("\n2.1: ");
	my_format(size,5);
	printf("\n2.2: ");
	my_format(size,5,6);
	printf("\n2.3: ");
	my_format(size,5,6,7);

	printf("\n");
	
	return 0;
}

在这里插入图片描述

4.例子3(不用格式控制,参数类型不相同,参数个数固定)

#include <stdio.h>
#include <stdarg.h>

//v3.0 参数类型不同
void my_format(char a,...)
{
	va_list var;
	char b;
	float c;
	
	va_start(var,a);
	b = va_arg(var,char);
	c = va_arg(var,float);
	va_end(var);

	printf("a=%c,b=%d,c=%f\n",a,b,c);
	
}
int main()
{
	char a = 'a';
	short b = 5;
	float c = 3.14;
	my_format(a,b,c);
	
	return 0;
}

编译发现报错:
在这里插入图片描述
根据提示,发现是默认参数提升造成的,至于什么是默认参数提升,详情请参考:
https://blog.csdn.net/qq_33706673/article/details/84679343
所以修改代码如下:

#include <stdio.h>
#include <stdarg.h>

//v3.0 参数类型不同
void my_format(char a,...)
{
	va_list var;
	char b;
	float c;
	
	va_start(var,a);
	b = (char)va_arg(var,int);
	c = (float)va_arg(var,double);
	va_end(var);

	printf("a=%c,b=%d,c=%f\n",a,b,c);
	
}
int main()
{
	char a = 'a';
	short b = 5;
	float c = 3.14;
	my_format(a,b,c);
	
	return 0;
}

运行结果:
在这里插入图片描述

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值