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


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

    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); // 从字符串

  • 相关阅读:
    visual studio 注释
    EF Core导航属性
    【转】前端UI框架小汇总
    C#三种定时器的实现
    【转】自学MVC看这里——全网最全ASP.NET MVC 教程汇总
    【转】C#进阶系列——WebApi 接口参数不再困惑:传参详解
    【转】c# WebApi之解决跨域问题:Cors
    优秀.NET开源项目
    Linux简介及最常用命令
    C#中使用Socket实现简单Web服务器
  • 原文地址:https://www.cnblogs.com/freshair_cnblog/p/11676616.html
Copyright © 2020-2023  润新知