C语言变参问题
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语言变参问题的更多相关文章
- 嵌入式 C 语言的可变参数表函数的设计
首先在介绍可变参数表函数的设计之前,我们先来介绍一下最经典的可变参数表printf函数的实现原理.一.printf函数的实现原理在C/C++中,对函数参数的扫描是从后向前的.C/C++的函数参数是通过 ...
- C 语言的可变参数表函数的设计
在c语言中使用变长参数最常见的就是下面两个函数了: int printf(const char *format, ...); int scanf(const char *format, ...); 那 ...
- C语言可变参实现参数累加返回
C语言可变参的作用真的是非常大,自从发表了可变参如何实现printf,fprintf,sprintf的文章以来,便有不少博友私信问我实现的机制,我也解释了相关的知识点.今天,我们借着这个机会,再来举一 ...
- c语言可变参
一.什么是可变参数 在C语言编程中有时会遇到一些参数个数可变的函数,例如printf(),scanf()函数,其函数原型为: int printf(const char* format,…),int ...
- C语言函数的变参实用与分析
实现变参传递的关键是: 传入参数在内存中是连续分布的. #define va_list void* #define va_arg(arg, type) *(type*)arg; arg = (char ...
- 【golang】go语言,进行并发请求的wrap变参封装
package main import ( "fmt" "sync" "time" ) type WaitGroupWrapper stru ...
- C 语言 define 变参__VA_ARGS__使用
在C语言的标准库中,printf.scanf.sscanf.sprintf.sscanf这些标准库的输入输出函数,参数都是可变的.在调试程序时,我们可能希望定义一个参数可变的输出函数来记录日志,那么用 ...
- C语言之可变参实现scanf函数
既然有printf函数可变参实现,那就一定有scanf函数的可变参实现.废话不多说,源码奉上: 本源码不过多分析,如要明白原理,请翻本博客以往的文章看说明. 欢迎关注新浪微博:http://weibo ...
- C语言之linux内核可变参实现printf,sprintf
昨天,我发表了一篇用可变参实现的fprintf函数,其实说实话还不完全是可变参实现的,因为用到了FILE * 这样的指针,需要包含stdio.h这个头文件才能实现这个函数,今天我们就来看看,如何抛弃s ...
随机推荐
- Js中各类型数据到bool的转换
在返回Json字符串给前台时遇到的问题,返回的bool数据总是为TRUE 特意查了一下,发现了Js中各类数据转换到bool型是的结果. 希望能给遇到同样问题的人一点帮助. 数据类型 转换为bool ...
- csharp: Procedure with DAO(Data Access Object) and DAL(Data Access Layer)
sql script code: CREATE TABLE DuCardType ( CardTypeId INT IDENTITY(1,1) PRIMARY KEY, CardTypeName NV ...
- Fluent NHibernate and Mysql,SQLite,PostgreSQL
http://codeofrob.com/entries/sqlite-csharp-and-nhibernate.html https://code.google.com/archive/p/csh ...
- iostat命令
http://www.orczhou.com/index.php/2010/03/iostat-detail/ Linux系统出现了性能问题,一般我们可以通过top.iostat.free.vmsta ...
- [PE结构分析] 8.输入表结构和输入地址表(IAT)
在 PE文件头的 IMAGE_OPTIONAL_HEADER 结构中的 DataDirectory(数据目录表) 的第二个成员就是指向输入表的.每个被链接进来的 DLL文件都分别对应一个 IMAGE_ ...
- 微软Asp.net MVC5生命周期流程图
.NET WEB Development blog 发布了Asp.net MVC5生命周期文档, 这个文档类似Asp.net应用程序生命周期,您以前开发ASP.NET WEB应用程序应该 ...
- [Architecture Design] 累进式Domain Layer
[Architecture Design] 累进式Domain Layer 前言 本篇的内容大幅度的简化了分析设计.面向对象等等相关知识,用以传达累进式Domain Layer的核心概念.实际开发软件 ...
- R语言学习笔记:向量
向量是R语言最基本的数据类型. 单个数值(标量)其实没有单独的数据类型,它只不过是只有一个元素的向量. x <- c(1, 2, 4, 9) x <- c(x[1:3], 88, x[4] ...
- Android项目实战(四):ViewPager切换动画(3.0版本以上有效果)
学习内容来自“慕课网” 一般APP进去之后都会有几张图片来导航,这里就学习怎么在这张图片切换的时候添加切换动画效果 先看布局文件 activity_main.layout <?xml versi ...
- Hibernate框架的基本搭建(一个小的java project的测试向数据库中插入和查询数据的功能)
Hibernate介绍:Hibernate是一种“对象-关系型数据映射组件”,它使用映射文件将对象(object)与关系型数据(Relational)相关联,在Hibernate中映射文件通常以&qu ...