C/C++可变参数函数

可变参数的函数,即函数的参数个数和参数类型不完全确定的函数。这类函数最常见的就是printf、scanf函数。在C/C++中,为了通知编译器函数有可变参数,必须以三个点结束该函数的声明。例如:

//printf函数的声明  
int printf(const char * _Format, ...);  

//scanf函数声明  
int scanf(const char * _Format, ...);  

//自定义变长参数函数func的声明  
int func(int a, int b, ...); 

上面func函数的声明指出该函数至少有两个整型参数和紧随其后的0个或多个类型未知的参数。在C/C++中,任何使用变长参数声明的函数都必须至少有一个指定的参数(又称强制参数),即至少有一个参数的类型是已知的,而不能用三个点省略所有参数的指定,且已知的指定参数必须声明在函数最左端。

可变参数函数的实现

变长参数函数的实现关键在于怎么使用参数,指定了的参数好说,直接使用指定的参数名称访问,但未指定的参数呢?我们知道函数调用过程中参数传递是通过栈来实现的,一般调用都是从右至左的顺序压参数入栈,因此参数与参数之间是相邻的,知道前一个参数的类型及地址,根据后一个参数的类型就可以获取后一个参数的内容。对于变长参数函数,结合一定的条件,我们可以根据最后一个指定参数获取之后的省略参数内容。例如,对于函数func,我们知道了参数b的地址及类型,就可知道第一个可变参数的栈地址(如果有的话),如果知道第一个可变参数的类型,就可知道第一个可变参数的内容和第二个可变参数的地址(如果有的话)。以此类推,可以实现对可变参数函数的所有参数的访问。

那么,要怎么指定上诉的“一定的条件”呢?最简单的方法就像printf等函数一样,使用格式化占位符。分析格式化字符串参数,通过事先定义好的格式化占位符可知可变参数的类型及个数,从而获取各个参数内容。一般对于可变参数类型相同的函数也可直接在强制参数中指定可变参数的个数和类型,这样也能获取各个参数的内容。

无论哪种,都涉及对栈地址偏移的操作。结合栈存储模式和系统数据类型的字长,我们可根据可变参数的类型很容易得到栈地址的偏移量。

处理可变参数的标准宏

//可变参数标准宏头文件  
#include "stdarg.h"  

//申明va_list数据类型变量pvar,该变量访问变长参数列表中的参数。  
va_list pvar;  

//宏va_start初始化变长参数列表。pvar是va_list型变量,记载列表中的参数信息。  
//parmN是省略号"..."前的一个参数名,va_start根据此参数,判断参数列表的起始位置。  
va_start(pvar, parmN);  

//获取变长参数列表中参数的值。pvar是va_list型变量,type为参数值的类型,也是宏va_arg返回数值的类型。  
//宏va_arg执行完毕后自动更改对象pvar,将其指向下一个参数。需要注意的是通过...传进来的参数若实参为
//char、float型,其通过...会升为int、double型,所以va_arg的type需为int、double型
va_arg(pvar, type);  

//关闭本次对变长参数列表的访问。  
va_end(pvar);  

如下图所示,为后面示例中format函数的栈示意图(注意高地址是栈底)
format函数的栈示意图

示例

//va_list args;         //定义一个可变参数列表  
//va_start(args,arg);   //初始化args指向强制参数arg的下一个参数  
//va_arg(args,type);    //获取当前参数内容并将args指向下一个参数  
//va_end(args);         //释放args  

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

#define MAXBUF 512

/*
 * 功能:利用变长参数函数格式化字符串
 * @count:可变参数数量
 *
 * 返回:求和结果
 */
double sum(unsigned int count, ...)
{
    double sum = 0;
    va_list args; //定义一个可变参数列表
    va_start(args, count); //初始化args指向强制参数arg的下一个参数
    while (count > 0)
    {
        //通过va_arg(args, double)依次获取参数的值
        sum += va_arg(args, double);
        count--;
    }
    va_end(args); //释放args
    return sum;
}

/*
 * 功能:利用变长参数函数格式化字符串
 * @format:未格式化前的字符串
 *
 * 返回:格式化后的字符串
 */
std::string format(const char *format, ...)
{
    va_list ap;
    va_start(ap, format);
    char *strbuf = (char *)malloc(sizeof(char) * MAXBUF);
    vsprintf(strbuf, format, ap);
    std::string var_str = strbuf;
    va_end(ap);

    return var_str;
}

/*
 * 功能:测试
 * @count:强制参数
 *
 * 返回:格式化后的字符串
 */
void ttt(int count, ...){
    va_list args;          
    va_start(args, count);
    // char c = va_arg(args, char);     //错误
    char c = va_arg(args, int);
    // float f = va_arg(args, float);   //错误
    float f = va_arg(args, double);
    printf("%c, %f\n",c,f);
    va_end(args); 
}

int main(void)
{
    //求和
    double cnt = sum(5, 1.1, 2.2, 3.3, 4.4, 5.5);
    printf("sum: %f\n", cnt);

    //格式化字符串
    std::string str = format("%s, %s", "hello", "world");
    printf("%s \n", str.c_str());

    //测试类型
    char c = 'c';
    float f = 5.5;
    ttt(2, c, f);

    return 0;
}



参考:
C 语言的可变参数表函数的设计
【C++基础之二十】可变参数的函数
C++ 变长参数函数小结

  • 5
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值