原帖:http://blog.csdn.net/xlliu0226/archive/2008/03/31/2233529.aspx
在 C 语言中,函数参数的 传 递方式有值传和址传 . 值传是把实参的一个专用的、临时的复制值给被调函数中相应的形参被调用函数使用、修改这个传来的复制 值,不会影响实参的值 . 址传则 是把变量 ( 实参 ) 的地址传给被调 函数 . 被调函数通过这个地址找到该变量的存放位置,直接对该地址中存放的变量的内容进行存取操作 . 因此,在被调用函 数中可 以修改实参的值 . 这也是函数参数址 传的优点 . 无论是值传还是址传,都要求实参的数目及类型与形参要完全一致 . 在一般的程 序设计语言中,函数参数的数目及类型是不可变的 . 即函数被设计之 后,只能接收已固定个数和固定类型的实参 . 这样在编译时,函数形参的存储空间便于确定 . 但 是在 C 语言中,不但参数的类型可变,参数的个数也是可变的 . 也就是说,在形参 表中可以不明确指定传递参数的个数和类型,一个常见的库函数 Printf() 就是如此 . 这种函数称之为可 变长参数函数 ( 变参函数 ). 可变长参数函数的参数数目和类型虽然是可变,但其设计原理与固定参数函数的设计原理是一致的,必须 有办法告诉变参函数没有指定的参数的个数和类型。下面我们通过对可变长参数函数的理解和设计,在教 学中更有助于加深掌握 C 语言函数设计的思想方法 . 利用其 它语言所不具有的这一可变长参数功能,可以开发灵活、方便、简洁、功能强的程序模块 . 1 , 可变长参数函数 的设计方法
在标准文件 stdarg.h 中包含带参数的宏定义
#define va_arg(ap,type) (*((type *)(ap))++)
#define va_start(ap,lastfix) (ap=…)
#define va_end(ap)
(2) 指针类型 va _ list 用来说明一个变量 ap(argument pointer—— 可变参数指针 ) ,此变量将依次引 用可变参数列表中用省略号 “…” 代替的每一个参数 . 即指向将要操作的变参 .
(3) 宏 va_start (ap,lastfix) 是为了初始化变参指针 ap, 以指向可变参数列表中未命名的第一个参数,即指向 lastfix 后的 第一个变参 . 它必须在指针使用之前调用一次该宏,参数列表中至少有一个未命名的可变参数 . 从宏定义可知其 正确性 .
(4) 宏 va _ arg (ap,type) 调用,将 ap 指向下一个可变 参数,而 ap 的类型由 type 确定, type 数据类型不使用 float 类型 . 调用后将新的变参可指向一个工作变参,如 iap=va _ start (ap,int) 调用 .
(5) 宏 va _ end (ap) 从 stdarg.h 中 看出定义为空,即未定义 . 其功能完成清除变量 ap 的作用,表明程序以后不再使用,若该指针变量需再使用,必须重新调用宏 va _ start 以启 动该变量 .
利用上面讨论的一般可变长参数函数的设计方法,通过实例逐步分析其特点,以加深函数实参与形参一 致性的理解 .
2.1 变参类型相同的函数
#include < stdarg.h >
int mul( int num, int data1, )
{
int total = data1;
int arg,i;
va_list ap;
va_start(ap,data1);
for (i = 1 ;i < num;i ++ )
{
arg = va_arg(ap, int );
total *= arg;
}
va_end(ap);
return total;
}
long mul2( int i, )
{
int * p,j;
p = & i + 1 ; // p指向参数列表下一个位置
long s = * p;
for (j = 1 ;j < i;j ++ )
s *= p[j];
return s;
}
int main()
{
printf( " %d/n " ,mul( 3 , 2 , 3 , 5 ));
printf( " %d/n " ,mul2( 3 , 2 , 3 , 5 ));
return 0 ;
}
2.2 可变参数在编译器中的处理
我们知道 va_start,va_arg,va_end 是在 stdarg.h 中被定义成宏的 , 由于 1) 硬件平台的不同 2) 编译器的不同 , 所以定义的宏也 有所不同 , 下面以 VC++ 中 stdarg.h 里 x86 平台的宏定义摘录如下 (’"’ 号表示折行 ):
#define _INTSIZEOF(n) /
(( sizeof (n) + sizeof ( int ) - 1 ) &~ ( sizeof ( int ) - 1 ) )
#define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) )
#define va_arg(ap,t) /
( * (t * )((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
#define va_end(ap) ( ap = (va_list)0 )
| 函数返回地址 |
|-----------------------------|
| . |
|-----------------------------|
| 第n个参数(第一个可变参数) |
|-----------------------------|<-- va_start后ap指向
| 第n - 1个参数(最后一个固定参数) |
低地址 |-----------------------------|<-- & v
图( 1 )
首先 ap+=sizeof(int), 已经指向下一个参数的地址了 . 然后返回 ap-sizeof(int) 的 int* 指针 , 这正是第一个可变参数在堆栈里的地址 ( 图 2). 然后用 * 取得这个地址的 内容 ( 参数值 ) 赋给 j.
| 函数返回地址 |
|-----------------------------|
| . |
|-----------------------------|<-- va_arg后ap指向
| 第n个参数(第一个可变参数) |
|-----------------------------|<-- va_start后ap指向
| 第n - 1个参数(最后一个固定参数) |
低地址 |-----------------------------|<-- & v
图( 2 )
#include " stdlib.h "
#include < stdarg.h >
void myprintf( char * fmt, ) // 一个简单的类似于printf的实现, // 参数必须都是int 类型
{
// char* pArg=NULL; // 等价于原来的va_list
va_list pArg;
char c;
// pArg = (char*) &fmt; // 注意不要写成p = fmt !!因为这里要对参数取址,而不是取值
// pArg += sizeof(fmt); // 等价于原来的va_start
va_start(pArg,fmt);
do
{
c =* fmt;
if (c != ' % ' )
{
putchar(c); // 照原样输出字符
}
else
{ // 按格式字符输出数据
switch ( *++ fmt)
{
case ' d ' :
printf( " %d " , * (( int * )pArg));
break ;
case ' x ' :
printf( " %#x " , * (( int * )pArg));
break ;
case ' f ' :
printf( " %f " , * (( float * )pArg));
default :
break ;
}
// pArg += sizeof(int); // 等价于原来的va_arg
va_arg(pArg, int );
}
++ fmt;
} while ( * fmt != ' /0 ' );
// pArg = NULL; // 等价于va_end
va_end(pArg);
return ;
}
int main( int argc, char * argv[])
{
int i = 1234 ;
int j = 5678 ;
myprintf( " the first test:i=%d " ,i,j);
myprintf( " the secend test:i=%f; %x;j=%d; " ,i, 0xabcd ,j);
system( " pause " );
return 0 ;
}