C中的可变参数列表的使用

本文探讨了C语言中可变参数列表的应用,包括printf和Cocos2d示例,强调其在灵活性和优雅性上的优势,同时揭示了C语言中可变参数的局限性,如长度不可确定和类型检查缺失。通过关键宏的讲解和一个简单示例,展示了如何使用va_list进行可变参数处理。
摘要由CSDN通过智能技术生成

一、可变参数列表的用途

可变参数列表可以传递一组长度不定的入参。最典型的使用场景有 stdlib 的 printf,另外在cocos2d中也大量使用,例如CCMenu的create方法,ccanimation的创建之类的。

不过按照我的理解,几乎80%的使用可变参数列表的场景,都可以使用数组来解决。但是使用可变参数列表的函数可以提供一种数组无法提供的东西:优雅。

可以对比以下两种调用方式:

    int main(int argc, char ** argv) {
        varInputTest(1, 2, 3, 4);
     
        int array[4] = {1, 2, 3, 4};
        inputTestWithArray(4, array);
     
    }

后一种对调用者来说要麻烦一点。

在另一种场景中,可变参数列表的解决方案也要优秀一些,这就是例如printf这种需要传递一个长度可变、且类型可变的列表来匹配前面的通配符的场景。虽然也可以用void *来实现,不过同上面一样,还是用可变参数列表实现来让调用者更舒服。

二、C语言中的可变参数的限制

和java中相比,C中的可变参数要弱不少,主要有两个缺点:

  • 1、无法确定可变参数列表的长度。这就是为什么printf要提供通配符,在cocos2d中要用NULL结尾

  • 2、不能提供类型检查。由于不能判定入参的类型,所以在从实参到形参的复制过程中可能会有问题,所以一般都建议只传递基本类型,或者是类类型的指针。但是这一点意外的也有一些用处,比如上面说到的printf

三、几个关键的宏

需要 #include <stdarg.h>

1、va_list

1)用法: va_list args;

如上,定义了一个 va_list类型的变量 args,可以用该变量作为保存可变参数列表的指针。实际使用中, 如果把可变参数列表的入参看做一个数组 array,那么这个 va_list 就相当于其迭代器 iterator。

这个宏本身只是个定义,真正赋予其意义的在于下面的几个宏。

2)实现:实际上只是一个 char * 类型的指针,原因这里不能判定类型,所以用size为1的char类型指针会方便移动。

2、va_start

1)用法:

    void varInputTest(int firtInt, ...) {
        va_list itor;
        //va_start 使得 args 指向...的第一个参数的地址,例如在 varInputTest(1, 2, 3, 4); 的调用情况下,就是指向2
        va_start(itor, firtInt);
        …………
    }

这个宏需要两个参数,第一个是上面定义的 va_list, 第二个是可变参数列表之前的那个参数。

作用就是使得 va_list 的变量指向可变参数列表的首地址。这才是一般意义上的对 va_list的初始化。

2)实现:

#define va_start ( ap, v ) ( ap = (va_list)&v + _INTSIZEOF(v) )

很容易看懂,就是将ap指向v后的地址。

3)注意:

按照规范,va_start中的参数,一定要是最后一个参数,也就是…之前的那个参数,不然可能会有问题。尤其是windows和linux的函数参数入栈顺序不同,会有可移植性问题

3、va_arg

1)用法:

    void varInputTest(int firtInt, ...) {
        va_list itor;
        va_start(itor, firtInt);
     
        int current =  va_arg(itor, int);
        cout << current << endl;
同样有两个参数,第一个是前面已经初始化好的 va_list,第二个是类型,比如这里可变参数列表的第一个参数是int类型,那么就传int。

这个函数实现了类似于迭代器的功能,他的返回值是当前itor指向的 int类型值(类型是第二个参数所描述的),同时会移动 itor,使得其指向可变参数列表的下一个参数。

2)实现:未研究,后面看了再补。

4、va_end

1)用法:

va_end(itor);

2)实现: 这个宏实际上是个空实现,更多是提高代码的可读性,同时方便后面的扩展。

5、缺少的…

作为一个迭代器,缺少的最关键一环就是判定结尾,可惜这里是没法提供的。还是老老实实的结尾传个NULL吧。

四、一段简单的程序

    #include <iostream>
    #include <stdarg.h>
     
    using namespace std;
     
    void varInputTest(int unusedInt, ...) {
        va_list itor;
        //va_start 使得 args 指向...的第一个参数的地址,例如在 varInputTest(1, 2, 3, 4); 的调用情况下,就是指向2
        //这里第一个参数不用,不过也不好用占位符,毕竟还需要他的名称
        va_start(itor, unusedInt);
     
        int current = va_arg(itor, int);
        while ( current != -1 ) {
            cout << current << endl;
            current = va_arg(itor, int);
        }
        va_end(itor);
    }
     
    int main(int argc, char ** argv) {
        varInputTest(0, 1, 2, 3, 4, -1);
    }

原文链接:https://blog.csdn.net/ronintao/article/details/10070485

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值