可变参数列表的解析及应用

在C语言中,通过将函数实现为可变参数的形式,可以使得函数可以接受1个以上的任意多个参数(不固定)。

我们来看一个例子,并且通过剖析这个例子来了解可变参数列表

编译器:vs2013

//使用可变参数,实现函数,求函数参数的平均值
#include <stdio.h>
#include <stdarg.h>
#include <windows.h>

int average(int n, ...)
{
    va_list arg;
    int i = 0;
    int sum = 0;
    va_start(arg, n);
    for (i = 0; i < n; i++)
    {
        sum += va_arg(arg, int);
    }
    va_end(arg);
    return sum / n;
}

int main()
{
    int avg = average(4, 1, 2, 3, 5);
    printf("%d\n", avg);
    system("pause");
    return 0;
}

首先,我们来分析部分代码

1.

int average(int n, ...) // ... 未知参数列表,n为未知参数列表个数
//将部分参数(1, 2, 3, 5)传给了未知参数列表

2.

代码
va_list arg;
//char *arg 
//创建一个指向未知列表的指针
转到定义
typedef char *  va_list;
//右击鼠标,转到定义,可以观察到char *类型被重命名为 va_list

3.

代码
va_start(arg, n);
//arg = (char *)(&(n)) + 4
//初始化arg为未知参数列表的第一个参数的地址
转到定义
#define va_start _crt_va_start
//宏,_crt_va_start被重命名为va_start
#define _crt_va_start(ap,v)  ( ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v) )
//宏,( ap = (va_list)( &(v) ) + 4 )
#define _ADDRESSOF(v)   ( &(v) )
//宏,取v的地址
#define _INTSIZEOF(n)   ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )
//宏,当n所占的字节为1,2,3,4时,宏返回4;当n所占的字节为5,6,7,8时,宏返回8
//n所占的字节为4,宏返回4

4.

代码
va_arg(arg, int);
//*(int *)((arg += 4) - 4)
//根据第二个参数在未知参数列表中获取一个参数,在获取一个参数之后,让arg向后移动指向下一个未知参数
转到定义
#define va_arg _crt_va_arg
//宏,_crt_va_arg被重命名为va_arg
#define _crt_va_arg(ap,t)    ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
//宏,( *(t *)((ap += 4) - 4) )
#define _INTSIZEOF(n)   ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )
//宏,当n所占的字节为1,2,3,4时,宏返回4;当n所占的字节为5,6,7,8时,宏返回8
//n所占的字节为4,宏返回4

5.

代码
va_end(arg);
//arg = (char *)0
//把arg初始化为NULL
转到定义
#define va_end _crt_va_end
//宏,_crt_va_end被重命名为 va_end
#define _crt_va_end(ap)      ( ap = (va_list)0 )
//宏,( ap = (va_list)0 )

之后,我们对average函数的部分代码进行替换

int average(int n, ...)
{
    char *arg; //va_list arg;
    int i = 0;
    int sum = 0;
    arg = (char *)(&(n)) + 4; //va_start(arg, n);
    for (i = 0; i < n; i++)
    {
        sum += *(int *)((arg += 4) - 4); //sum += va_arg(arg, int);
    }
    arg = (char *)0; //va_end(arg);
    return sum / n;
}

综上所述,我们可以了解到

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

可变参数列表的应用

1.使用可变参数,实现函数,求函数参数的最大值

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

int Max(int n, ...)
{
    va_list arg;//创建一个指向未知列表的指针
    int i;
    int max = 0;
    va_start(arg, n);//初始化arg为未知参数列表的第一个参数的地址
    max = va_arg(arg, int); //获取第一个参数赋给max,之后,并且让arg向后移动指向下一个未知参数
    for (i = 1; i < n; i++)
    {
        int tmp = va_arg(arg, int);//获取一个参数赋给max,之后,并且让arg向后移动指向下一个未知参数
        if (max < tmp)
            max = tmp;
    }
    va_end(arg); //把arg初始化为NULL
    return max;
}

int main()
{
    int ret = Max(4, 1, 2, 3, 4);
    printf("%d\n", ret);
    system("pause");
    return 0;
}

模拟实现简单的printf函数

模拟实现简单的printf函数,需要利用传入的第一个参数,对字符串经行遍历,若遇到字符为d,c,s等等(模拟%d,%c,%s),则利用va_age获取并输出后面所对应的未知参数列表中的参数,若没有遇到,则原样输出。

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

void print(const char *format, ...)
{
    char *str = (char *)format; //保存
    va_list arg; //创建指向未知参数列表的指针
    va_start(arg, format);//初始化arg为可变参数列表的第一个参数的地址
    while (*str != '\0')
    {
        int ch;
        char *s;
        switch (*str)
        {
        case 'd':  //查到数字 print("%d\n", d);

            ch = va_arg(arg, int);
            while (ch > 10)
            {
                int d = 0;
                int i = 1;
                d = ch / 10;
                while (d > 10)//每次都得到ch第一位
                {
                    d = d / 10;
                    i++;
                }
                d = d + 48; //转化为字符
                putchar(d);
                i = (int)pow(10, i);
                ch = ch % i; //得到ch的后几位
            }
            ch = ch + 48;
            putchar(ch); //输出ch的最后一位
            str++;
            break;
        case 'c':  //查到单字符 print("%c\n", 'c');
            putchar(va_arg(arg, char));
            str++;
            break;
        case 's':  //查到字符串 print("%s\n", "s");,
            s = va_arg(arg, char*);//在未知参数列表中获取所对应字符串首字符的地址
            while (*s != '\0')
            {
                putchar(*s);
                s++;
            }
            str++;
            break;
        default: //查到其它时原样输出
            putchar(*str);
            str++;
            break;
        }           
    }
}

int main()
{
    print("s ccc d.\n", "hello", 'b', 'i', 't', 1234);
    system("pause");
    return 0;
}

猜你喜欢

转载自blog.csdn.net/lw13572259173/article/details/80171774