C/C++中可变参数的用法详细解析

可变参数即表示参数个数可以变化,可多可少,也表示参数的类型也可以变化,可以是int,double还可以是char*,类,结构体等等。可变参数是实现printf(),sprintf()等函数的关键之处,也可以用可变参数来对任意数量的数据进行求和,求平均值带来方便(不然就用数组或每种写个重载)。在C#中有专门的关键字parame,但在C,C++并没有类似的语法,不过幸好提供这方面的处理函数,本文将重点介绍如何使用这些函数。

第一步 可变参数表示
用三个点…来表示,查看printf()函数和scanf()函数的声明:
int printf(const char *, ...);
int scanf(const char *, ...);

这三个点用在宏中就是变参宏(Variadic Macros),默认名称为__VA_ARGS__。如:
#define WriteLine(...) { printf(__VA_ARGS__); putchar('\n');}
再WriteLine("MoreWindows");

考虑下printf()的返回值是表示输出的字节数。将上面宏改成:
#define WriteLine (...) printf(__VA_ARGS__) + (putchar('\n') != EOF ? 1: 0);
这样就可以得到WriteLine宏的返回值了,它将返回输出的字节数,包括最后的'\n'。如下例所示i和j都将输出12。


代码如下:

int i = WriteLine("MoreWindows");
       WriteLine("%d", i);
       int j = printf("%s\n", "MoreWindows");
       WriteLine("%d", j);

第二步 如何处理va_list类型
函数内部对可变参数都用va_list及与它相关的三个宏来处理,这是实现变参参数的关键之处。
在<stdarg.h>中可以找到va_list的定义:
typedef char *  va_list;
再介绍与它关系密切的三个宏要介绍下:va_start(),va_end()和va_arg()。

同样在<stdarg.h>中可以找到这三个宏的定义:
#define va_start(ap,v)  ( ap = (va_list)&v + _INTSIZEOF(v) )
#define va_end(ap)      ( ap = (va_list)0 )
#define va_arg(ap,t)    ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
其中用到的_INTSIZEOF宏定义如下:
#define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )

来分析这四个宏:
va_end(ap)这个最简单,就是将指针置成NULL。
va_start(ap,v)中ap = (va_list)&v + _INTSIZEOF(v)先是取v的地址,再加上_INTSIZEOF(v)。_INTSIZEOF(v)就有点小复杂了。( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )全是位操作,看起来有点麻烦,其实不然,非常简单的,就是取整到sizeof(int)。比如sizeof(int)为4,1,2,3,4就取4,5,6,7,8就取8。对x向n取整用C语言的算术表达就是((x+n-1)/n)*n,当n为2的幂时可以将最后二步运算换成位操作——将最低 n - 1个二进制位清 0就可以了。

va_arg(ap,t)就是从ap中取出类型为t的数据,并将指针相应后移。如va_arg(ap, int)就表示取出一个int数据并将指针向移四个字节。

因此在函数中先用va_start()得到变参的起始地址,再用va_arg()一个一个取值,最后再用va_end()收尾就可以解析可变参数了。

第三步 vfprintf()函数和vsprintf()函数
vfprintf()这个函数很重要,光从名字上看就知道它与经常使用的printf()函数有很大的关联。它有多个重载版本,这里讲解最常用的一种:
函数原型


代码如下:

int vfprintf(

FILE *stream,

const char *format,

va_list argptr

);

第一个参数为一个FILE指针。FILE结构在C语言的读写文件必不可少。要对屏幕输出传入stdout。
第二个参数指定输出的格式。
第三个参数是va_list类型,这个少见,但其实就是一个char*表示可变参参数的起始地址。
返回值:成功返回输出的字节数(不包括最后的'\0'),失败返回-1。
vsprintf()与上面函数类似,就只列出函数原型了:


代码如下:

int vsprintf(

char *buffer,

const char *format,

va_list argptr

);

还有一个int _vscprintf(const char *format, va_list argptr );可以用来计算vsprintf()函数中的buffer字符串要多少字节的空间。

代码范例
下面就给出了自己实现的printf()函数(注1)与WriteLine()函数
 

代码如下:

int Printf(char *pszFormat, ...) 
{
       va_list   pArgList;

va_start(pArgList, pszFormat);
       int nByteWrite = vfprintf(stdout, pszFormat, pArgList);
       va_end(pArgList);

return nByteWrite;
}

int WriteLine(char *pszFormat, ...)
{
       va_list   pArgList;

va_start(pArgList, pszFormat);
       int nByteWrite = vfprintf(stdout, pszFormat, pArgList);
       if (nByteWrite != -1)
              putchar('\n'); //注2
       va_end(pArgList);

return (nByteWrite == -1 ? -1 : nByteWrite + 1);
}

调用与printf()函数相同。
再给出一个用可变参数来求和,遗憾的在C,C++中无法确定传入的可变参数的个数(printf()中是通过扫描'%'个数来确实参数的个数的),因此要么就要指定个数,要么在参数的最后要设置哨兵数值:
设置哨兵数值:


代码如下:

const int GUARDNUMBER = 0; //哨兵标识
//变参参数的个数无法确定,在printf()中是通过扫描'%'个数,在这通过设置哨兵标识来确定变参参数的终止
int MySum(int i, ...)
{
       int sum = i;
       va_list argptr;

va_start(argptr, i);
       while ((i = va_arg(argptr, int)) != GUARDNUMBER)
              sum += i;
       va_end(argptr);

return sum;
}

可以这样的调用:   printf("%d\n", MySum(1, 3, 5, 7, 9, 0));
但不可以直接传入一个0:   printf("%d\n", MySum(0)); //error
指定个数:


代码如下:

int MySum(int nCount, ...)
{
       if (nCount <= 0)
              return 0;

int sum = 0;
       va_list argptr;

va_start(argptr, nCount);
       for (int i = 0; i < nCount; i++)
              sum += va_arg(argptr, int);
       va_end(argptr);

return sum;
}

调用时第一个参数表示后面参数的个数如:
       printf("%d\n", MySum(5, 1, 3, 5, 7, 9));
       printf("%d\n", MySum(0));

代码所用的头文件:
#include <stdarg.h>
#include <stdio.h>

可变参数的使用方法远远不止上述几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的'%'符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃。

可变参数的原形理涉及到调用函数时参数的入栈问题,这个下次再开一篇进行专门的探讨。

注1.网上有不用vfprintf()自己解析参数来实现printf()的,但很少能将功能做到与printf()相近(实际上能完全熟悉printf()的人已经就不多,不信的话可以先看看《C陷阱与缺陷》了解printf()很多不太常用的参数,再去Microsoft Visual Studio\VC98\CRT\SRC中查看OUTPUT.C对printf()的实现)。

注2.如果输出单个字符 putchar(ch)会比printf(“%c”, ch)效率高的多。在字符串不长的情况下,多次调用putchar()也会比调用printf(“%s\n”, szStr);的效率高。在函数大量调用时非常明显。

(0)

相关推荐

  • C++中可以接受任意多个参数的函数定义方法(详解)

    能够接受任意多个参数的函数,可以利用重载来实现.这种函数的执行过程类似于递归调用,所以必须要有递归终止条件. #include <iostream> #include <bitset> void print() {} // 递归终止条件.这是必需的. template<typename Type, typename... Types> void print(const Type& arg, const Types&... args) { std::cou

  • C++可变参数的实现方法

    可变参数的实现要解决三个问题: 1.如何调用带有可变参数的函数2.如何编译有可变参数的程序3.在带有可变参数的函数体中如何持有可变参数第一个问题, 调用时在可以传入可变参数的地方传入可变参数即可,当然,还有一些需要注意的地方,后面会提到. 第二个问题,编译器需要在编译时采用一种宽松的检查方案,,这会带来一些问题, 比如对编程查错不利. 第三个是我在这里要关心的问题,先以C语言为例分析其实现原理. printf和scanf是C语言标准库中最常见的可变参数函数, printf的签名是 复制代码 代码

  • C++可变参数的函数与模板实例分析

    本文实例展示了C++可变参数的函数与模板的实现方法,有助于大家更好的理解可变参数的函数与模板的应用,具体内容如下: 首先,所谓可变参数指的是函数的参数个数可变,参数类型不定的函数.为了编写能处理不同数量实参的函数,C++提供了两种主要的方法:如果所有的实参类型相同,可以传递一个名为initializer_list的标准库类型:如果实参的类型不同,我们可以编写可变参数模板.另外,C++还有一种特殊的省略符形参,可以用它传递可变数量的实参,不过这种一般只用于与C函数交互的接口程序. 一.可变参数函数

  • 浅析C/C++中的可变参数与默认参数

    千万要注意,C不支持默认参数 C/C++支持可变参数个数的函数定义,这一点与C/C++语言函数参数调用时入栈顺序有关,首先引用其他网友的一段文字,来描述函数调用,及参数入栈: ------------ 引用开始 ------------ C支持可变参数的函数,这里的意思是C支持函数带有可变数量的参数,最常见的例子就是我们十分熟悉的printf()系列函数.我们还知道在函数调用时参数是自右向左压栈的.如果可变参数函数的一般形式是:    f(p1, p2, p3, -)那么参数进栈(以及出栈)的顺

  • C/C++中可变参数的用法详细解析

    可变参数即表示参数个数可以变化,可多可少,也表示参数的类型也可以变化,可以是int,double还可以是char*,类,结构体等等.可变参数是实现printf(),sprintf()等函数的关键之处,也可以用可变参数来对任意数量的数据进行求和,求平均值带来方便(不然就用数组或每种写个重载).在C#中有专门的关键字parame,但在C,C++并没有类似的语法,不过幸好提供这方面的处理函数,本文将重点介绍如何使用这些函数. 第一步 可变参数表示用三个点-来表示,查看printf()函数和scanf(

  • linux中uptime命令的用法详细解析

    系统中的uptime命令主要用于获取主机运行时间和查询linux系统负载等信息.uptime命令可以显示系统已经运行了多长时间,信息显示依次为:现在时间.系统已经运行了多长时间.目前有多少登陆用户.系统在过去的1分钟.5分钟和15分钟内的平均负载. uptime命令用法十分简单:直接输入uptime即可.另外还有一个参数  -V ,是用来查询版本的. (注意是大写的字母v)[linux @ localhost]$ uptime –Vprocps version 3.2.7[linux @ loc

  • C++中函数模板的用法详细解析

    定义 我们知道函数的重载可以实现一个函数名多用,将功能相同或者类似函数用同一个名来定义.这样可以简化函数的调用形式,但是程序中,仍然需要分别定义每一个函数. C++提供的函数模板可以更加简化这个过程. 所谓函数模板实际上是建立一个通用函数,其涵涵素类型额形参类型不具体指定,用一个虚拟的类型来代表,这个通用函数就称为函数模板. 凡是函数体相同的函数都可以用这个模板来代替,不必定义多个函数,只需要在模板中定义一次即可.在调用函数时,系统会根据实参的类型来取代模板中的虚拟类型,从而实现了不同函数的功能

  • Java中的可变参数常见用法实例总结

    本文实例讲述了Java中的可变参数常见用法.分享给大家供大家参考,具体如下: 前言: 到J2SE 1.4为止,一直无法在Java程序里定义实参个数可变的方法--因为Java要求实参(Arguments)和形参(Parameters)的数量和类型都必须逐一匹配,而形参的数目是在定义方法时就已经固定下来了.尽管可以通过重载机制,为同一个方法提供带有不同数量的形参的版本,但是这仍然不能达到让实参数量任意变化的目的. 然而,有些方法的语义要求它们必须能接受个数可变的实参--例如著名的main方法,就需要

  • C语言中可变参数的使用方法示例

    前言 在C语言程序编写中我们使用最多的函数一定包括printf以及很多类似的变形体.这个函数包含在C库函数中,定义为 int printf( const char* format, ...); 除了一个格式化字符串之外还可以输入多个可变参量,如: printf("%d",i); printf("%s",s); printf("the number is %d ,string is:%s", i, s); 格式化字符串的判断本章暂且不论,下面分析一

  • Python可变参数函数用法实例

    本文实例讲述了Python可变参数函数用法.分享给大家供大家参考.具体如下: #!/usr/bin/python def f1(a,b): print a,b def f2(a,*b): print a,b def f3(a,**b): print a,b def f4(a,*b,**c): print a,b,c def f5(a,b=2,c=3): print a,b,c def f6(a,b=2,*c): print a,b,c f1(1,2) f1(b=2,a=1) f2(1,2,3,4

  • Mybatis中的config.xml配置文件详细解析

    经过前面的文章,我觉得对Mybatis的正题理解已经足够了,但是对Mybatis的使用,我觉得还是会有一点的模糊,就我个人而言,我觉得掌握好Mybatis框架,主要要明白三个文件,第一个就是等下要谈论的Mybatis-comfig.xml文件,还有就是**Mapper.xml,以及我们所定义的Mapper类,理解了这三个东西,然后有sql的基础,还有java的基础的话,后面不论是使用基于xml的方法,还是基于java-based Configuration的方法,都会简单的多. 废话不多说,现在

  • React函数式组件Hook中的useEffect函数的详细解析

    目录 前言 useEffect的作用 useEffect的使用? 1.class组件 2.函数式组件 总结 前言 React函数式编程没有生命周期,因此需要借助useEffect来实现. useEffect的作用 发ajax请求获取数据 设置订阅/获取定时器 手动更改真实DOM useEffect的使用? 1.class组件 在class组件中可以使用生命周期函数,知道组件触发的过程. 代码如下(示例): import React, { Component } from 'react' expo

  • React函数式组件Hook中的useState函数的详细解析

    目录 前言 一.什么是函数式组件 二.useState 前言 公司项目需要使用react,而函数式组件也是官方比较推荐的!!!所以学习hooks是很重要的. 一.什么是函数式组件 纯函数组件有以下特点: 没有状态 没有生命周期 没有 this 因存在如上特点,使得纯函数组件只能做UI展示的功能, 涉及到状态的管理与切换就不得不用到类组件或者redux. 但因为简单的页面也是用类组件,同时要继承一个React实例,使得代码会显得很重. 以前我们可以使用class来声明一个组件,其实使用functi

  • Sql Server中Substring函数的用法实例解析

    SQL 中的 substring 函数是用来抓出一个栏位资料中的其中一部分.这个函数的名称在不同的资料库中不完全一样: MySQL: SUBSTR( ), SUBSTRING( ) Oracle: SUBSTR( ) SQL Server: SUBSTRING( ) SQL 中的 substring 函数是用来截取一个栏位资料中的其中一部分. 例如,我们需要将字符串'abdcsef'中的'abd'给提取出来,则可用substring 来实现: select substring('abdcsef'

随机推荐