C函数要在程序中用到以下这些宏:

<pre lang="c" escaped="true">
void va_start( va_list arg_ptr, prev_param );
type va_arg( va_list arg_ptr, type );
void va_end( va_list arg_ptr );
</pre>

&nbsp;

va_list:用来保存宏va_start、va_arg和va_end所需信息的一种类型。为了访问变长参数列表中的参数,必须声明
va_list类型的一个对象 定义: typedef char * va_list;
va_start:访问变长参数列表中的参数之前使用的宏,它初始化用va_list声明的对象,初始化结果供宏va_arg和
va_end使用;
va_arg: 展开成一个表达式的宏,该表达式具有变长参数列表中下一个参数的值和类型。每次调用va_arg都会修改
用va_list声明的对象,从而使该对象指向参数列表中的下一个参数;
va_end:该宏使程序能够从变长参数列表用宏va_start引用的函数中正常返回。
va在这里是variable-argument(可变参数)的意思.
这些宏定义在stdarg.h中,所以用到可变参数的程序应该包含这个头文件.下面我们写一个简单的可变参数的函数,改函数至少有一个整数参数,第二个参数也是整数,是可选的.函数只是打印这两个参数的值.

<pre lang="c" escaped="true">
#include <stdio.h>;
#include <string.h>;
#include <stdarg.h>;

/* ANSI标准形式的声明方式,括号内的省略号表示可选参数 */

int demo(char *msg, ... )
{
va_list argp; /* 定义保存函数参数的结构 */
int argno = 0; /* 纪录参数个数 */
char *para; /* 存放取出的字符串参数 */

/* argp指向传入的第一个可选参数, msg是最后一个确定的参数 */
va_start( argp, msg );

while (1)
{
para = va_arg( argp, char *); /* 取出当前的参数,类型为char *. */
if ( strcmp( para, "/0") == 0 )
/* 采用空串指示参数输入结束 */
break;
printf("Parameter #%d is: %s/n", argno, para);
argno++;
}
va_end( argp ); /* 将argp置为NULL */
return 0;
}

void main( void )
{
demo("DEMO", "This", "is", "a", "demo!" ,"333333", "/0");

}

</pre>

从这个函数的实现可以看到,我们使用可变参数应该有以下步骤:
1)首先在函数里定义一个va_list型的变量,这里是arg_ptr,这个变量是指向参数的指针.
2)然后用va_start宏初始化变量arg_ptr,这个宏的第二个参数是第一个可变参数的前一个参数,是一个固定的参数.
3)然后用va_arg返回可变的参数,并赋值给整数j. va_arg的第二个参数是你要返回的参数的类型,这里是int型.
4)最后用va_end宏结束可变参数的获取.然后你就可以在函数里使用第二个参数了.如果函数有多个可变参数的,依次调用va_arg获取各个参数.

二、可变参类型陷阱

下面的代码是错误的,运行时得不到预期的结果:

view plaincopy to clipboardprint?
va_start(pArg, plotNo);
fValue = va_arg(pArg, float); // 类型应改为double,不支持float
va_end(pArg);
va_start(pArg, plotNo);
fValue = va_arg(pArg, float); // 类型应改为double,不支持float
va_end(pArg);

下面列出va_arg(argp, type)宏中不支持的type:

—— char、signed char、unsigned char
—— short、unsigned short
—— signed short、short int、signed short int、unsigned short int
—— float

在C语言中,调用一个不带原型声明的函数时,调用者会对每个参数执行“默认实际参数提升(default argument promotions)”。该规则同样适用于可变参数函数——对可变长参数列表超出最后一个有类型声明的形式参数之后的每一个实际参数,也将执行上述提升工作。

提升工作如下:
——float类型的实际参数将提升到double
——char、short和相应的signed、unsigned类型的实际参数提升到int
——如果int不能存储原值,则提升到unsigned int

然后,调用者将提升后的参数传递给被调用者。

所以,可变参函数内是绝对无法接收到上述类型的实际参数的。

关于该陷井,C/C++著作中有以下描述:

在《C语言程序设计》对可变长参数列表的相关章节中,并没有提到这个陷阱。但是有提到默认实际参数提升的规则:
在没有函数原型的情况下,char与short类型都将被转换为int类型,float类型将被转换为double类型。
——《C语言程序设计》第2版 2.7 类型转换 p36

在其他一些书籍中,也有提到这个规则:

事情很清楚,如果一个参数没有声明,编译器就没有信息去对它执行标准的类型检查和转换。
在这种情况下,一个char或short将作为int传递,float将作为double传递。
这些做未必是程序员所期望的。
脚注:这些都是由C语言继承来的标准提升。
对于由省略号表示的参数,其实际参数在传递之前总执行这些提升(如果它们属于需要提升的类型),将提升后的值传递给有关的函数。——译者注
——《C++程序设计语言》第3版-特别版 7.6 p138

…… float类型的参数会自动转换为double类型,short或char类型的参数会自动转换为int类型 ……
——《C陷阱与缺陷》 4.4 形参、实参与返回值 p73

这里有一个陷阱需要避免:
va_arg宏的第2个参数不能被指定为char、short或者float类型。
因为char和short类型的参数会被转换为int类型,而float类型的参数会被转换为double类型 ……
例如,这样写肯定是不对的:
c = va_arg(ap,char);
因为我们无法传递一个char类型参数,如果传递了,它将会被自动转化为int类型。上面的式子应该写成:
c = va_arg(ap,int);
——《C陷阱与缺陷》p164

c可变参数函数的更多相关文章

  1. C语言学习020:可变参数函数

    顾名思义,可变参数函数就是参数数量可变的函数,即函数的参数数量是不确定的,比如方法getnumbertotal()我们即可以传递一个参数,也可以传递5个.6个参数 #include <stdio ...

  2. Swift开发第十篇——可变参数函数&初始化方法顺序

    本篇分为两部分: 一.Swift中的可变参数函数 二.初始化方法的顺序 一.Swift中的可变参数函数 可变参数函数指的是可以接受任意多个参数的函数,在 OC 中,拼接字符串的函数就属于可变参数函数 ...

  3. C语言中可变参数函数实现原理

    C函数调用的栈结构 可变参数函数的实现与函数调用的栈结构密切相关,正常情况下C的函数参数入栈规则为__stdcall, 它是从右到左的,即函数中的最右边的参数最先入栈.例如,对于函数: void fu ...

  4. C可变参数函数 实现

    转自:http://blog.csdn.net/weiwangchao_/article/details/4857567 C函数要在程序中用到以下这些宏: void va_start( va_list ...

  5. C语言可变参数函数实现原理

    一.可变参数函数实现原理 C函数调用的栈结构: 可变参数函数的实现与函数调用的栈结构密切相关,正常情况下C的函数参数入栈规则为__stdcall, 它是从右到左的,即函数中的最右边的参数最先入栈. 本 ...

  6. 【转】C/C++中可变参数函数的实现

    转自:http://www.cnblogs.com/cylee025/archive/2011/05/23/2054792.html 在C语言的stdarg.h头文件中提供了三个函数va_start, ...

  7. PHP中的可变参数函数和可选参数函数

    1)可选参数函数.例如: <?phpfunction add($var1,$var2,$var3=0,$var4=0){ return$var1+$var2+$var3+$var4;}echo ...

  8. C语言可变参数函数的编写

    1. 引言 C语言我们接触的第一个库函数是 printf(“hello,world!”);其参数个数为1个. 然后,我们会接触到诸如: printf(“a=%d,b=%s,c=%c”,a,b,c);此 ...

  9. 可变参数函数(stdarg.h)的使用

    2013/5/3记录: stdarg.h是C语言中C标准函数库的头文件,stdarg是由standard(标准) arguments(参数)简化而来,主要目的为让函数能够接收可变参数.   stdar ...

随机推荐

  1. DevExpress TreeList 那些事儿

    1:TreeList绑定数据源 当我们给予TreeList 的 parentFieldName 和 KeyFieldName 两个属性之后 会自动的生成树结构. 1 var sql = @" ...

  2. POJ 3150 Cellular Automaton(矩阵乘法+二分)

    题目链接 题意 : 给出n个数形成环形,一次转化就是将每一个数前后的d个数字的和对m取余,然后作为这个数,问进行k次转化后,数组变成什么. 思路 :下述来自here 首先来看一下Sample里的第一组 ...

  3. ExtJs之Ext.core.DomQuery

    <!DOCTYPE html> <html> <head> <title>ExtJs</title> <meta http-equiv ...

  4. Good Bye 2014 D. New Year Santa Network 图论+期望

    D. New Year Santa Network   New Year is coming in Tree World! In this world, as the name implies, th ...

  5. C Primer Plus之指针

    c之精髓——指针(pointer)——用来存储地址的变量.一般来讲,指针是一个其数值为地址的变量(或更一般地说是一个数据对象). 一元运算符&可以取得变量的存储地址,一个变量的地址可以被看作是 ...

  6. iOS开发swift--函数

    函数返回值 ---函数 // 自加函数 func Add(num : Int)->Int{ } //自减函数 func zj(num : Int)->Int{ } //定义返回函数类型 f ...

  7. JavaWeb项目开发案例精粹-第4章博客网站系统-006View层

    1.showAllArticle.jsp <%@ page language="java" contentType="text/html; charset=gb23 ...

  8. QScrollArea可以帮助我们实现让一个widget的内容带有滚动条(QWidget里内置QScrollArea,QScrollArea里再内置其它QWidget)

    使用QScrollArea可以帮助我们实现让一个widget的内容带有滚动条,用户可以通过拖动滚动条来查看更多内容, 代码示例如下: 1.带有滚动条的widget列表 #include "w ...

  9. Hibernate笔记——第一个简单实例

     1. 首先进行框架配置 导包 hibernate配置xml文件 ======================= 2. 创建并配置POJO类 Hibernate是操作持久化层的框架,和数据库打交道,其 ...

  10. Android 时间戳简单转化

    时间戳就是如1377216000000 这种格式我们在mysql数据库中会经常用到把时间转换成时间戳或把时间戳转换成日期格式了,下面我来介绍安卓中时间戳操作转换方法. 一.原理 时间戳的原理是把时间格 ...