如何实现带可变长参数的函数

关于如何实现带可变长参数的函数,先看这两篇文章。

https://www.ibm.com/developerworks/cn/linux/l-va/index.html

以及

https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/va-arg-va-copy-va-end-va-start?view=vs-2019

统称带可变长参数的函数为VA函数,variable argument function。

1,声明

VA函数的原型声明包括两部分:个数确定的固定参数部分和个数不确定的可选参数部分。

type VAFunction(type arg1, type arg2, …);

其中可选参数部分由 … 表示。

2,思路简单描述

VA函数的实现简单说就是对参数指针的使用和控制。对于固定参数部分,可以直接从函数定义中获得;对于可选参数部分,先将指针指向第一个可选参数,然后依次后移指针,直到指针指向结束标志为止。因此VA函数必须先约定好结束标志。

3,VA的宏定义

C提供了一系列独立于硬件架构和硬件平台的宏用于实现VA函数,这些宏都定义在stdarg.h中。关键的几个如下:

1) va_list arg_prt;

va_list是指向char的指针,X86下va_list的定义如下:

typedef char* va_list;

arg_prt初始值应指向第一个可选参数。这是如何实现的呢?通过va_start(arg_ptr, argN)。

2) va_start(arg_ptr, argN);

va_start(arg_ptr, argN);的作用是使指针指向第一个可选参数。其中argN是位于第一个可选参数之前的固定参数(亦即最后一个固定参数,在…最前面)。如有意VA函数void va_test(char a, char b, char c, …),则它的固定参数依次是a,b,c,最后一个固定参数argN是c,因此就是va_start(arg_ptr, c)。

3) va_arg(arg_ptr, type);

确定第一个可变参数的位置之后,接下来要做的就是移动指针直至其指向结束标志。这通过va_arg(arg_ptr, type);实现。va_arg(arg_ptr, type)返回指针指向的参数,返回类型为type,并使指针指向参数列表中下一个参数。

4) va_end(arg_ptr);

清空参数列表,并置参数指针无效。每次调用va_start()/va_copy()后,应有相应的va_end()与之匹配。

其他VA宏说明可参考微软的文档。

上述的宏的实现如下:

. #define _INTSIZEOF(n)  ((sizeof(n)+sizeof(int)-1)&~(sizeof(int) - 1) )

. #define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) )          //第一个可选参数地址

. #define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) ) //下一个参数地址

. #define va_end(ap)   ( ap = (va_list)0 )

详细解释参考IBM的文档。

4,举例

下面是一个计算任意个自然数平方的例子。

int SqSum(int n1, ...)

{

va_list arg_ptr;     // 声明指针

int nSqSum = 0;

int n = n1;         // 指定最后一个固定参数

va_start(arg_ptr, n1);  // 指针指向第一个可选参数

while (n > 0)      // 指定结束字符

{

nSqSum += (n * n);

n = va_arg(arg_ptr, int);  // 返回参数列表中下一个可选参数,类型为int,并指向下一个参数

}

va_end(arg_ptr);   // va_end()与va_start()对应

return nSqSum;

}

// 调用时

int nSqSum = SqSum(7, 2, 7, 11, -1);

5,利用VA函数写简洁的调试打印函数

调试时,如果直接使用printf()捉着fprintf(),调试结束后删除它们会非常麻烦。如果写成这样的宏:

#ifdef DEBUG

    printf(…);

#endif

如果数量很多,代码看起来也很不好看。

可以考虑写一个VA函数,包住fprintf(),然后在此函数中引入一个全局变量,以控制是否输出调试信息。

例如:

void debug_write(char* fmt, …)

{

    va_list ap;

    va_start(ap, fmt);

    vfprintf(stderr, fmt, ap); 

    va_end(ap);

}

6,顺便说一下vprintf系列函数

int vprintf / vscanf(const char * format, va_list ap); // 从标准输入/输出格式化字符串
int vfprintf / vfsacanf(FILE * stream, const char * format, va_list ap); // 从文件流
int vsprintf / vsscanf(char * s, const char * format, va_list ap); // 从字符串

posted on 2019-10-15 11:28  freshair_cn  阅读(512)  评论(0编辑  收藏  举报

导航