在日常写代码时,经常会用到“printf”函数,而printf函数可以传递多个参数,可以1个,2个,多个。而我们写函数时,会注意到自己定义的形参数量是已经定义好的,对比printf函就会发现该函数参数数量却是传递任意个数都可以输出的。如:
1 printf("%d\n", 2018);
2 printf("%s\n","hello world");
3 printf("%d %s\n", 2018,"I'm fine.");
查阅一下printf函数原型:
int printf ( const char * format, ... );
这时,我们发现参数列表里里有“…”。因此,我们就要讨论一下可变参数,注意:可变参数中参数值至少为1。 而要使用可变参数,就要先了解这些宏:
1 va_list arg;
2 void va_start( va_list arg_ptr, prev_param );
3 type va_arg( va_list arg_ptr, type );
4 void va_end( va_list arg_ptr );
而这些宏应该怎么用呢,我就用一个简单的求最大值算法,在代码中注释来给大家解释下。
#define _CRT_SECURE_NO_WARNINGS//此处用于在VS2015中防止编译警告
#include<stdarg.h>//调用上述宏所需要声明的头文件
#include<stdio.h>//输入、输出函数所需要声明的头文件
int CalculateMax(int n, ...)
{
int max = 0;
int i = 0;
int middle = 0;//存放表达式:va_arg(arg,int)的值
va_list arg;// 定义一个名称为arg的指针
va_start(arg, n);//用于指明arg是指针名,n代表第一个参数,用于表示起始端
max = va_arg(arg, int);
while (i < n - 1)
{
if (max < (middle = va_arg(arg, int)))//如果middle = va_arg(arg, int)的值小于max,进入判断
{
max = middle;
}
i++;
}
va_end(arg);
return max;
}
int main()
{
int result = CalculateMax(5, -5, -9, -8, -3, -6);
printf("The biggest number is %d\n", result);
return 0;
}
va_list arg; 转到定义:typedef char* va_list;重命名char* 为va_list。此处va_list就相当于 char*了,可以定义指针。
va_arg(arg, int);转到定义:#define va_arg __crt_va_arg,__crt_va_arg。
转到定义: #define __crt_va_arg(ap, t) ((t)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t))) 。
ap表示可变参数指针,而t表示数据类型。右边语句_INTSIZEOF(t)是什么意思呢?
#define _INTSIZEOF(n) ((sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1))。这又是什么意思呢?
(sizeof(int) - 1),该表达式结果是3,二进制位为0000 0000 0000 0000 0000 0000 0000 0011。加取反操作~结果是1111 1111 1111 1111 1111 1111 1111 1100 。 当参数t为char时,’&’前面的表达式结果取最小值4,二进制结果是0000 0000 0000 0000 0000 0000 0000 0100。因此,两边表达式运算结果是4。
那为什么要搞这么复杂的表达式呢?用sizeof关键字就好了咩。一般数据类型确实看上去求算有点复杂了,但是如果是结构体呢?结构体内有多种类型变量,如果继续使用简单的sizeof关键字求解的话,未免有些不太适合了。使用 ((sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1)),传入char,float,double等小于4字节的类型,都会返回4。相应的传入如果类型大小是,5,6,7的话,则返回8。即返回当前一组数中靠近4的倍数的值。
再来看看 (* (t*)( (ap += _INTSIZEOF(t) ) - _INTSIZEOF(t) ) )。注意括号套着哪个括号。该表达式先让指针ap加上4字节的大小,再把减去4字节大小处所对应的值返回。用t强制类型转换,再解引用。注意此处t是传入参数的数据类型。
最后谈谈va_end(arg);转到定义:#define va_end __crt_va_end,继续转到定义:#define __crt_va_end(ap) ((void)(ap = (va_list)0)),由此看出,该语句把整型0强制转换为字符0,然后传给指针ap,由此可知将ap清空。
最后我们来看下可变参数的限制:
1. 可变参数必须从头到尾的访问,可以半途停止,但不允许直接访问中间参数,
2. 参数必须要有一个命名参数;
3. 这些宏不能直接判断实际存在参数的数量,也无法判断每个参数的类型;