15. C语言可变参数列表解析

  C语言的可变参数列表是一项非常有意思的技术,它可以使函数接收不确定个数个参数,来达到某些特定场合的要求。

  另外可变参数列表与C程序的栈帧是紧密结合的,所以,建议大家先参考一下这篇讲解C语言栈帧的文章

函数的调用过程(栈帧)详解

  来看一个例子:

    #include  //头文件

    //可变参数的平均值函数
    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;
    }

  本例就是应用了可变参数列表,其中出现了几个 va 前缀的类型和新用法,这就是C语言可变参数列表的语法,接下来我们研究一下这些。

1.va_list arg;

  创建一个可变参数,本质上是一个 char* 的指针。

va_start

2. va_start

  用可变参数列表中的已知参数初始化可变参数。

  我们转到 va_start 的定义,可以看到, va_start 是一个宏,修饰了一个叫 _crt_va_start 的东西。

va_start1

  我们再转到 _crt_va_start,在这里可以看到它的定义。

va_start2

  我们再看一下这个 [_ADDRESSOF和 _INTSIZEOF 是什么。

_ADDRESSOF

_INTSIZEOF

  看到这里,想必大家都明白了吧。va_start翻译过来其实就是:

    arg = char*(&n) + /*向上取整型,n = 1,2,3,4为4,5,6,7,8为8,类推*/
    // va_start:初始化arg,让arg指向未知参数部分的第一个参数
    // n为可变参数列表未知参数前面第一个有名字的参数(已知参数)
    // 不一定为可变参数参数列表第一个参数,因为可能会有多个已知参数

  这里运用了栈帧的原理,因为进入 add 函数时参数压栈顺序是从左到右,所以可变未知参数是先于已知参数压栈的,所以知道可变未知参数前第一个已知参数,就可以将 arg 定位到第一个未知参数,直接在栈上找到它。

3. va_arg

  我们转到 va_arg 的定义来看一下。

va_arg1

  和 va_start 一样,这里也进行了封装,我们继续。

va_arg2

  这里运用到了两次 _INTSIZEOF 宏,我们再看一下。

_INTSIZEOF

  还是函数栈帧的使用,我们将例子中的 va_arg 语句翻译一下。

sum += va_arg(arg, int);
    // define va_arg(arg,int) (*(int *))((arg += _INTSIZEOF(int)) - _INTSIZEOF(int))
    // ==> sum += (*(int *)((arg += 4) - 4));

  因为 arg 是一个 char* 指针,参数是一个 int类型,所以每次给 arg 加4 即指向下一个未知参数。因为参数是从左到右传入,所以加4即下一个未知参数。然后sum又是arg-4,即当前未知参数。

4. va_end

  因为 arg 是一个指针变量,使用完必须进行销毁,所以想必 va_end 的作用就是进行指针销毁。
  打开 va_end 的定义。

va_end1

  不出意外,它也是做了封装,我们继续点进去。

va_end2

  这就是它的庐山真面目了。

5. 可变参数的限制

  • 可变参数必须从头到尾逐个访问。如果你在访问了几个可变参数之后想半途中止,这是可以的,但是,如果你想一开始就访问参数列表中间的参数,那是不行的(可以把想访问的中间参数之前的参数读取但是不使用,曲线救国)。

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

  • 这些宏是无法直接判断实际实际存在参数的数量。

  • 这些宏无法判断每个参数的类型。所以在 va_arg 的时候一定要指定正确的类型。

  • 如果在va_arg中指定了错误的类型,那么后果是不可预测的

  • 第一个参数也未必要是可变参数个数,例如printf()。

练习:编写printf,返回打印字符个数,只有s c d 字符串 字符 整形 三个选项

    #include 
    #include 
    void display(int n,int* count)
    {
        if (n > 9)
        {
            display(n / 10,count);
        }
        (*count)++;
        putchar(n % 10 + '0');
    }
    int print(const char* format,...)
    {
        int count = 0;
        va_list arg;
        va_start(arg, format);
        while (*format != '\0')
        {
            switch (*format)
            {
            case 's':
                {
                        char* ptr = va_arg(arg, char*);
                        while (*ptr != '\0')
                        {
                            putchar(*ptr);
                            ptr++;
                            count++;
                        }
                }
                break;
            case 'd':
                {
                        int ret = va_arg(arg, int);
                        display(ret,&count);
                }
                break;
            case 'c':
                {
                        char ret = va_arg(arg, char);
                        count++;
                        putchar(ret);
                }
                break;
            default:
                putchar(*format);
                count++;
                break;
            }
            format++;
        }
        va_end(arg);
        return count;
    }
    int main()
    {
        char* p = "abcdef";
        int age = 20;
        char f = 'f';
        int num = print("s d f ", p, age, f);
        printf("\n%d\n", num);
        return 0;
    }

你可能感兴趣的:(C语言,C语言进阶学习,可变参数列表)