写可变参数的C函数要在程序中用到以下这些定义和宏:
va_list arg_ptr;
void va_start( va_list arg_ptr, argN ); // 使参数列表指针arg_ptr指向函数参数列表中的第一个可选参数,说明:argN是位于第一个可选参数之前的固定参数,(或者说,最后一个固定参数;…之前的一个参数)
type va_arg( va_list arg_ptr, type );//返回参数列表中指针arg_ptr所指的参数,返回类型为type,并使指针arg_ptr指向参数列表中下一个参数。
void va_end( va_list arg_ptr );//清空参数列表,并置参数指针arg_ptr无效。说明:指针arg_ptr被置无效后,可以通过调用
va_start()恢复arg_ptr。每次调用va_start()后,必须得有相应的va_end()与之匹配。参数指针可以在参数列表中随意地来回移动,但必须在va_start() …
va_end()之内。
va在这里是variable-argument(可变参数)的意思. 这些宏定义在stdarg.h中,所以用到可变参数的程序应该包含这个 头文件.示例程序如下:
#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>
#include <stdarg.h>
int average( int first, ... );
void main(void)
{
printf( "Average is: %d\n", average( 2, 3, 4,4, -1 ) );
}
int average( int first, ... ) /* 可变参数:…所隐含的或者所省略的参数
固定参数:凡是写出来的参数都是固定参数.
最后一个固定参数:固定参数是在可变参数之前的 位置.紧挨着…并在其之前的那个固定参数称做最后一个固定参数.*/
{
int count=0,i=first,sum=0;
va_list maker; //va_list 类型数据可以保存函数的所有参数,做为一个列表一样保存。Va_list即是char*表明maker是一个字符型的指针。
va_start(maker,first); //设置列表的起始位置,这并不说明maker指向frist而是指向first之后的第一个可变的参数,而frist是作为一个固定参数,因为它在…之前。
while(i!=-1)
{
sum+=i;
count++;
i=va_arg(maker,int);//返回maker列表的当前值,并指向列表的下一个位置
}
return sum/count;
}
|
第一次:I=2,sum=2;
第二次:I=3,因为va_start(maker,first);则sum=2+3=5;同时i=va_arg(maker,int)又使I=4;
第三次:I=4,sum=5+4=9,同理I=4;
第四次I=4,sum=9+4=13同理I=-1
从这个函数的实现可以看到,我们使用可变参数应该有以下步骤:
1)首先在函数里定义一个va_list型的变量,这里是arg_ptr,这个变 量是指向参数的指针.
2)然后用va_start宏初始化变量arg_ptr,这个宏的第二个参数是第 一个可变参数的前一个参数,是一个固定的参数.
3)然后用va_arg返回可变的参数,并赋值给整数i. va_arg的第二个参数是你要返回的参数的类型,这里是int型.
4)最后用va_end宏结束可变参数的获取.然后你就可以在函数里使 用第二个参数了.如果函数有多个可变参数的,依次调用va_arg获 取各个参数. va_start,va_arg,va_end是在stdarg.h中被定义成宏的, 由于1)硬件平台的不同 2)编译器的不同,所以定义的宏也有所不同,下 面以VC++中stdarg.h里x86平台的宏定义摘录如下:
typedef char * va_list;
#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 )
|
定义_INTSIZEOF(n)主要是为了某些需要内存的对齐的系统.
我 们看到va_list被定义成char*,有一些平台或操作系统定义为void*.
再 看va_start的定义,定义为&v+_INTSIZEOF(v),而&v是固定参数在堆栈的 地址,所以我们运行va_start(ap, v)以后,ap指向第一个可变参数在堆 栈的地址。
我 们看一下va_arg取int型的返回值: i= ( *(int*)((ap += _INTSIZEOF(int))-_INTSIZEOF(int)) );
首先ap+=sizeof(int),已经指向下一个参数的地址了.然后返回 ap-sizeof(int)的int*指针,这正是第一个可变参数在堆栈里的地址 。.然后用*取得这个地址的内容(参数值)赋给
要说的是va_end宏的意思,x86平台定义为ap=(char*)0;使ap不再 指向堆栈,而是跟NULL一样.有些直接定义为((void*)0),这样编译器不 会为va_end产生代码,例如gcc在linux的x86平台就是这样定义的. 在这里大家要注意一个问题:由于参数的地址用于va_start宏,所 以参数不能声明为寄存器变量或作为函数或数组类型. 关于va_start, va_arg, va_end的描述就是这些了,我们要注意的 是不同的操作系统和硬件平台的定义有些不同,但原理却是相似的.可变参数在编程中要注意的问题
因为va_start, va_arg, va_end等定义成宏,所以它显得很愚蠢, 可变参数的类型和个数完全在该函数中由程序代码控制,它并不能智能地识别不同参数的个数和类型. 有人会问:那么printf中不是实现了智能识别参数吗?那是因为函数 printf是从固定参数format字符串来分析出参数的类型,再调用va_arg 的来获取可变参数的.也就是说,你想实现智能识别可变参数的话是要通过在自己的程序里作判断来实现的.
另外有一个问题,因为编译器对可变参数的函数的原型检查不够严 格,对编程查错不利.如下;
void simple_va_fun(int i, ...)
{
va_list arg_ptr;
char *s=NULL;
va_start(arg_ptr, i);
s=va_arg(arg_ptr, char*);
va_end(arg_ptr);
printf("%d %s\n", i, s);
return;
}
|
可变参数为char*型,当我们忘记用两个参数来调用该函数时,就会出现 core dump(Unix) 或者页面非法的错误(window平台).但也有可能不出 错,但错误却是难以发现,不利于我们写出高质量的程序. 可变参数的函数原理其实很简单,而va系列是以宏定义来定义的,实 现跟堆栈相关.我们写一个可变函数的C函数时,有利也有弊,所以在不必 要的场合,我们无需用到可变参数.如果在C++里,我们应该利用C++的多 态性来实现可变参数的功能,尽量避免用C语言的方式来实现.
由于在C语言中没有函数重载,解决不定数目函数参数问题变得比较麻烦;即使采用C++,如果参数个数不能确定,也很难采用函数重载.对这种情况,就要用上面的方法。
虽然可以通过在堆栈中遍历参数列表来读出所有的可变参数,但是由于不知道可变参数有多少个,什么时候应该结束遍历,如果在堆栈中遍历太多,那么很可能读取一些无效的数据.解决办法:a.可以在第一个起始参数中指定参数个数,那么就可以在循环还中读取所有的可变参数;b.定义一个结束标记,在调用函数的时候,在最后一个参数中传递这个标记,这样在遍历可变参数的时候,可以根据这个标记结束可变参数的遍历;下面是一段示例代码://第一个参数定义可选参数个数,用于循环取初参数内容
void arg_cnt(int cnt, ...);
int main(int argc,char *argv[])
{
int int_size = _INTSIZEOF(int);
printf("int_size=%d\n", int_size);
arg_cnt(4,1,2,3,4);
return 0;
}
void arg_cnt(int cnt, ...)
{
int value=0;
int i=0;
int arg_cnt=cnt;
va_list arg_ptr;
va_start(arg_ptr, cnt);
for(i = 0; i < cnt; i++)
{
value = va_arg(arg_ptr,int);
printf("value%d=%d\n", i+1, value);
}
}
|
虽
然可以根据上面两个办法解决读取参数个数的问题,但是如果参数类型都是不定的,该怎么办,如果不知道参数的类型,即使读到了参数也没有办法进行处
理.解决办法:可以自定义一些可能出现的参数类型,这样在可变参数列表中,可以可变参数列表中的那类型,然后根据类型,读取可变参数值,并进行准确地转
换.传递参数的时候可以这样传递:参数数目,可变参数类型1,可变参数值1,可变参数类型2,可变参数值2,.... 这里给出一个完整的例子:#include <stdio.h>
#include <stdarg.h>
const int INT_TYPE = 100000;
const int STR_TYPE = 100001;
const int CHAR_TYPE = 100002;
const int LONG_TYPE = 100003;
const int FLOAT_TYPE = 100004;
const int DOUBLE_TYPE = 100005;
//第一个参数定义可选参数个数,用于循环取初参数内容
//可变参数采用arg_type,arg_value...的形式传递,以处理不同的可变参数类型
void arg_type(int cnt, ...);
int main(int argc,char *argv[])
{
int int_size = _INTSIZEOF(int);
printf("int_size=%d\n", int_size);
arg_test(0, 4);
arg_cnt(4,1,2,3,4);
arg_type(2, INT_TYPE, 222, STR_TYPE, "ok,hello world!");
return 0;
}
void arg_type(int cnt, ...)
{
int arg_type = 0;
int int_value=0;
int i=0;
int arg_cnt=cnt;
char *str_value = NULL;
va_list arg_ptr;
va_start(arg_ptr, cnt);
for(i = 0; i < cnt; i++)
{
arg_type = va_arg(arg_ptr,int);
switch(arg_type)
{
case INT_TYPE:
int_value = va_arg(arg_ptr,int);
printf("value%d=%d\n", i+1, int_value);
break;
case STR_TYPE:
str_value = va_arg(arg_ptr,char*);
printf("value%d=%d\n", i+1, str_value);
break;
default:
break;
}
}
}
|
阅读(755) | 评论(0) | 转发(0) |