C++中有函数重载这种方法,以供我们调用时要可以不确定实参的个数,其实 C 语言也可以,而且更高明!

我们在stdio.h 中可以看到 printf() 函数的原型:

int printf(char * format,...)

事实上,我们如果要写这样的函数也可以类似的写,那么在定义函数时用上这个符号“ ... ” ,它叫占位符,喊它 “ 三个点 ” 也可以,只要你愿意!那么我可以这样定义我的函数:

fun(int a,...)
{ }

这是个空函数,它是什么都不做的,光这样写还不行的,具体应该怎样定义呢?

且听我介绍3 个知识:

1、 va_list

2、 va_arg()

3、 va_start()

程序在执行时,会将函数存储到内存中去。现在深入的讲一点点,存储函数时,参数传递的过程是怎样实现的呢?所谓的形式参数(局部变量)实质上又是什么呢?把这些问题连起来想想,想通了,你的思维势如破竹!

在调用函数时,程序同样会把实参传入,在函数存储区保存起来,如果有很多参数,将一起保存起来。

这时候就要用到va_list 了,这是个类型定义,我们可以把它理解成一个指针,它指向第一个参数的地址。

如果,我们这样定义: va_list pp ;

则pp 就是这样一种变量,它是指向所有参数中的第一个参数的。它不同于一般的指针变量,它是个复合变量,什么是复合变量啊?结构体类型的嘛,呵呵。如果 a 是第一个参数,能不能写成 pp=a 呢?

假设我定义了char d[]="ruixin",e[]="gelin"; 我要把 e 的值赋给 d ,能不能写成 d=e 呢?得用 strcpy() ,是吧!呵呵,一样的道理,这儿我们也用一个函数来实现,它就是 va_start();

如果这样写:va_start(pp,a);

那么pp 就指向第一个参数 a 了,并且可得到 a 的类型 int 。

这时候如果有下一个参数,就需要使pp 指向下一个参数,并且得到它的类型。同样需要使用函数来实现,这个函数是: va_arg()

可以这样写:va_arg(pp, 类型 ) ,这样 pp 就指向一个参数,并且可以得到那个参数的类型了。

注意!类型非常重要,学过指针的都应该清楚,指针的类型如果弄错的话,位置正确,取出来的数可能也是乱七八糟的。

下面我们看一个简单的例子:

 #include <stdio.h>
#include<stdarg.h> void fun(int a,...)
{
va_list pp;
int n=;//使用 n 计量参数个数
va_start(pp,a);
do
{
printf("第 %d 个参数 =%d/n",n++,s);
a=va_arg(pp,int);//使 pp 指向下一个参数,将下一个参数的值赋给变量 a
}
while (a!=);//直到参数为 0 时停止循环
} main()
{ fun(,,,,);
}

注意!

一定要有上面两个文件包含命令,因为程序中用到的那3个点都在那个文件里。其实真正意义上应该说那是函数,实质上那不过是两个宏

VA_LIST 是在C语言中解决变参问题的一组宏

VA_LIST的用法:     
       (1)首先在函数里定义一具VA_LIST型的变量,这个变量是指向参数的指针
      (2)然后用VA_START宏初始化变量刚定义的VA_LIST变量,这个宏的第二个参数是第一个可变参数的前一个参数,是一个固定的参数。
       (3)然后用VA_ARG返回可变的参数,VA_ARG的第二个参数是你要返回的参数的类型。
       (4)最后用VA_END宏结束可变参数的获取。然后你就可以在函数里使用第二个参数了。如果函数有多个可变参数的,依次调用VA_ARG获取各个参数。

VA_LIST在编译器中的处理:
1)在运行VA_START(ap,v)以后,ap指向第一个可变参数在堆栈的地址。
(2)VA_ARG()取得类型t的可变参数值,在这步操作中首先apt = sizeof(t类型),让ap指向下一个参数的地址。然后返回ap-sizeof(t类型)的t类型*指针,这正是第一个可变参数在堆栈里的地址。然后用*取得这个地址的内容。
(3)VA_END(),X86平台定义为ap = ((char*)0),使ap不再指向堆栈,而是跟NULL一样,有些直接定义为((void*)0),这样编译器不会为VA_END产生代码,例如gcc在Linux的X86平台就是这样定义的。

要注意的是:由于参数的地址用于VA_START宏,所以参数不能声明为寄存器变量,或作为函数或数组类型。

使用VA_LIST应该注意的问题:
  
(1)因为va_start, va_arg,
va_end等定义成宏,所以它显得很愚蠢,可变参数的类型和个数完全在该函数中由程序代码控制,它并不能智能地识别不同参数的个数和类型.
也就是说,你想实现智能识别可变参数的话是要通过在自己的程序里作判断来实现的.
    (2)另外有一个问题,因为编译器对可变参数的函数的原型检查不够严格,对编程查错不利.不利于我们写出高质量的代码。

小结:可变参数的函数原理其实很简单,而
VA系列是以宏定义来定义的,实现跟堆栈相关。我们写一个可变函数的C函数时,有利也有弊,所以在不必要的
场合,我们无需用到可变参数,如果在C++里,我们应该利用C++多态性来实现可变参数的功能,尽量避免用C语言的方式来实现。

转载自:http://blog.csdn.net/aking1314/article/details/5874195

C语言变参问题的更多相关文章

  1. 嵌入式 C 语言的可变参数表函数的设计

    首先在介绍可变参数表函数的设计之前,我们先来介绍一下最经典的可变参数表printf函数的实现原理.一.printf函数的实现原理在C/C++中,对函数参数的扫描是从后向前的.C/C++的函数参数是通过 ...

  2. C 语言的可变参数表函数的设计

    在c语言中使用变长参数最常见的就是下面两个函数了: int printf(const char *format, ...); int scanf(const char *format, ...); 那 ...

  3. C语言可变参实现参数累加返回

    C语言可变参的作用真的是非常大,自从发表了可变参如何实现printf,fprintf,sprintf的文章以来,便有不少博友私信问我实现的机制,我也解释了相关的知识点.今天,我们借着这个机会,再来举一 ...

  4. c语言可变参

    一.什么是可变参数 在C语言编程中有时会遇到一些参数个数可变的函数,例如printf(),scanf()函数,其函数原型为: int printf(const char* format,…),int ...

  5. C语言函数的变参实用与分析

    实现变参传递的关键是: 传入参数在内存中是连续分布的. #define va_list void* #define va_arg(arg, type) *(type*)arg; arg = (char ...

  6. 【golang】go语言,进行并发请求的wrap变参封装

    package main import ( "fmt" "sync" "time" ) type WaitGroupWrapper stru ...

  7. C 语言 define 变参__VA_ARGS__使用

    在C语言的标准库中,printf.scanf.sscanf.sprintf.sscanf这些标准库的输入输出函数,参数都是可变的.在调试程序时,我们可能希望定义一个参数可变的输出函数来记录日志,那么用 ...

  8. C语言之可变参实现scanf函数

    既然有printf函数可变参实现,那就一定有scanf函数的可变参实现.废话不多说,源码奉上: 本源码不过多分析,如要明白原理,请翻本博客以往的文章看说明. 欢迎关注新浪微博:http://weibo ...

  9. C语言之linux内核可变参实现printf,sprintf

    昨天,我发表了一篇用可变参实现的fprintf函数,其实说实话还不完全是可变参实现的,因为用到了FILE * 这样的指针,需要包含stdio.h这个头文件才能实现这个函数,今天我们就来看看,如何抛弃s ...

随机推荐

  1. private、 protected、 public、 internal 修饰符

    private : 私有成员, 在类的内部才可以访问. protected : 保护成员,该类内部和继承类中可以访问. public : 公共成员,完全公开,没有访问限制. internal: 在同一 ...

  2. MySQL数据库 安装图解

    下面的是MySQL安装的图解,用的可执行文件:下载地址:http://www.jinhusns.com/Products/Download/?type=xcj相关下载 mysql安装向导启动,按“Ne ...

  3. 重新想象 Windows 8.1 Store Apps (93) - 控件增强: GridView, ListView

    [源码下载] 重新想象 Windows 8.1 Store Apps (93) - 控件增强: GridView, ListView 作者:webabcd 介绍重新想象 Windows 8.1 Sto ...

  4. 划分树---Feed the dogs

    POJ  2761 Description Wind loves pretty dogs very much, and she has n pet dogs. So Jiajia has to fee ...

  5. oauth授权协议的原理

    http://oauth.net/2/ 协议的原文.原来是1.0版本,现在是2.0版本了 https://ruby-china.org/topics/15396 https://blog.yorkxi ...

  6. Head First Design Patterns学习笔记-观察者模式

    认识观察者模式 首先来看看报纸订阅的过程 1.报社的业务就是出版报纸 2.向某家报社订阅报纸,只要他们有新报纸出版,就会送过来,只要你是他们的订户 3.当你不想再看报纸的时候,取消订阅,他们就不会再送 ...

  7. android onNewIntent

    在Android应用程序开发的时候,从一个Activity启动另一个Activity并传递一些数据到新的Activity上非常简单,但是当您需要让后台运行的Activity回到前台并传递一些数据可能就 ...

  8. Android5.0新特性——阴影和剪裁(shadow)

    阴影和剪裁 View的z属性 Material Design建议为了凸显布局的层次,建议使用阴影效果,并且Android L为了简化大家的工作,对View进行了扩展,能使大家非常方便的创建阴影效果: ...

  9. domain规划

    user-generated content 用户产生内容 和 admin-generated content 管理员产生内容,要区分开来,便于不同的图片压缩.备份.带宽.CDN.审核方案. 图片(j ...

  10. AT NEW F、AT END OF F注意事项

    1.F只能是内表的第一个字段 2.AT NEW F.AT END OF F使用F之后内表内容会变为* 解决出现*的办法: FIELD-SYMBOLS:<ITAB> LIKE  ITAB L ...