可变参数列表解析

在日常写代码时,经常会用到“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. 这些宏不能直接判断实际存在参数的数量,也无法判断每个参数的类型;

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值