浅谈C语言参数可变函数的实现
1.需要的头文件:stdarg.h
需要的宏:va_start(a,b) va_arg(a,b) va_end(a)
需要的类型别名: va_list
2.基本用法
(1)写函数头
1 return_type function_name(first_argument,...);
eg1:
1 int fun1(int a,...); //right 2 void fun2(double b,...); //right 3 char *fun3(int a, double b,...); //right 4 int printf(const char *format,...); //right 5 int wrong_function(...); //wrong
参数数目可变的函数,其必须明确定义至少一个参数(且必须放在最前面),之后跟...表示剩下的参数数目、类型是不确定的。
(2)定义va_list变量(定义几个?根据需求而定)
eg2:
1 va_list parg;
(3)va_start(parg,first_argument);这里parg是(2)中定义的va_list类型变量,first_argument是参数列表中的第一个参数(如果在参数列表中只明确定义 了一个变量的话,那first_argument确实是参数列表中的第一个变量。但是,如果参数列表中明确定义了不止一个变量,那么first_argument指的是明确定义的 最后一个变量).
eg3:
1 int fun1(int a,...) 2 { 3 va_list pa; 4 va_start(pa,a); 5 //…… 6 }
eg4:
1 int fun1(int a,int b,int c,…) 2 { 3 va_list pa; 4 va_start(pa,c); 5 //…… 6 }
(3)用va_arg(a,b)获取不确定的参数
eg5:
1 int a=va_arg(parg,int); //int是通过分析得出的 2 char *s=va_arg(parg,char *); //char *是通过分析得出的
分析得出?什么意思呢?由于参数数目可变函数在传递参数的时候,并没有传递参数的类型,也没有传递参数的个数,所以需要以某种方式让函数知道到底传递了多少个参数,到底每个参数是什么类型的。
eg6:
1 prinf(“there are %d birds,%d dogs,10 %s”,5,3,”pigs”);
输出:there are 5 birds,3 dogs,10 pigs
printf是怎么知道有3个参数,怎么知道参数分别是int int char*型的参数呢?其实可以发现,当我们刚开始用printf的时候,就有%d%c%s%f之类的奇怪东西跟着。在eg6中,因为有%d %d %s这3个带%的东西,printf才知道有4(第一个参数+后面的三个参数)个参数;同样因为%d %d %s,printf才知道是int,int,char*类型的变量。Printf正是通过分析%的数目以及%后面跟的字母,让printf得以分析出参数的个数和类型。
(4)va_end(parg);
这个没什么好说的,只要记住函数最后要加这么一句话就对了。
3.完整的例子
eg7:
1 int add(int n,…)//计算n个数的和 2 { 3 int sum=0; 4 va_list parg; 5 6 va_start(parg,n); 7 while(n--)//循环n次 8 sum+=va_arg(parg,int); 9 10 va_end(parg); 11 return sum; 12 }
eg8:
1 int my_easy_printf(char *format,…)//%d %c %s %h %u 2 { 3 int i_a; 4 double dbl_b; 5 unsigned u_c; 6 char *p_d; 7 //这里没有定义char c_e,但是却支持%c的操作,下面会讲到 8 int print_arg=0; 9 va_list parg; 10 11 va_start(parg,format); 12 13 while(*format) 14 { 15 if(*format!=’%’) 16 putchar(*format); 17 else 18 switch(*++format) 19 { 20 case '%': 21 putchar(‘%’); 22 format++; 23 break; 24 case 'd': 25 case 'h': 26 i_a=va_arg(parg,int); 27 print_int(i_a);//这是一个输出有符号整数的函数 28 print_arg++; 29 break; 30 case 'u': 31 u_c=va_arg(parg,unsigned int); 32 print_uint(u_c); //这是一个输出无符号整数的函数 33 print_arg++; 34 break; 35 case 'c': 36 c_c=va_arg(parg,int); 37 putchar(c_c); 38 print_arg++; 39 break; 40 case 's': 41 p_d=arg(parg,char *); 42 while(*p_d) 43 putchar(*p_d++); 44 print_arg++; 45 break; 46 default: 47 va_end(parg); 48 return -1; 49 } 50 Format++; 51 } 52 va_end(parg); 53 return print_arg; 54 } 55
ps:me_easy_printf虽然支持%c的输出,但是并没有定义char类型的变量,这是因为char类型变量在参数传递的过程中会进行类型提升,提升为int,相当于char类型变量都被类型转换成int型变量了。
类似的
(unsigned/signed)char、short ---> (unsigned/signed)int
float ---> double
4.va_list、va_start、va_arg、va_end的一种实现方式
这四个东西(宏和类型别名)的实现和具体环境有关,并非一成不变,下面内容仅供参考,在实际使用中不要自己编写va_start、va_arg、va_end,不要自己声明类型别名va_list,请使用官方提供版本。
(1)va_list
1 typedef char * va_list
va_list 定义的变量用于存储变量的指针
(2)va_start
1 va_start(parg,first_arg) (parg=(va_list)&first_arg+sizeof(first_arg))
(3)va_arg
1 #define _SIZEOFINT(n) ((sizeof(n)+sizeof(int)-1)&~(sizeof(int)-1))//按sizeof(int)的整数倍不减对齐
_SIZEOFINT(n)的目的是,求得一整数k,使
k%sizeof(int)==0 ----------1
k>=sizeof(n) ----------2
k是满足1、2的最小整数
这里以8位举例_SIZEOFINT的实现方法,假设sizeof(int)为4,虽然int是32(16)位,但原理一样:
假如我们要把m向4的整数倍对齐,那么只需让m的二进制后两位为0即可,即可满足能被4(sizeof(int))整除的条件,方法是m&11111100,而~sizeof(int)-1即为11111100。但是不满足条件2(除非n刚好能被4整除),因为把1变为0的话,数一定是减小的,所以要把(m+(4-1))&11111100才行,而4-1就是sizeof(int)-1,并且m就是sizeof(n)。综上,为
((sizeof(n)+sizeof(int)-1)&~(sizeof(int)-1))
1 #define va_arg(parg,t) (parg=(va_list)&parg+SIZEOFINT(t)); 2 //取得第一个可变参数的指针,并存储在parg中
上面的_SIZEOFINT 主要是用于需要内存对齐的系统,倘若系统不需要内存对其,则
1 #define _SIZEOFINT(n) sizeof(n)
1 #define va_arg(parg,t) (*(t*)((parg+=SIZEOFINT(t))-SIZEOFINT(t))) 2 //parg+=SIZEOFINT(t)使得parg指向下一个可变参数 3 //取得下一个可变参数的指针,并返回这个指针上的值
(4)va_end
1 #define va_end(parg) (parg=(va_list)0) 2 //让parg指向NULL,防止对指针的错误使用
上面谈的是参数数目和类型都不确定的函数,在C语言里,还有一种参数数目确定但是类型不确定的函数
比如文件操作函数fwrite的第一个参数,貌似不需要具体的类型,先看一下fwrite的声明:
1 size_t fwrite(const void* buffer, size_t size, size_t count, FILE* stream);
eg9:
1 //下面函数用于对两个操作数进行+操作, 2 //如果mod为1,则进行整形+操作, 3 //如果mod为2则进行字符串操作,出错返回ERROR,否则返回OK 4 int add(int mod,void *opd1,void *opd2,void*result) 5 { 6 char *popd1,popd2,presult; 7 8 if(1==mod) 9 { 10 * (int *)result=*(int *)opd1+*(int *)opd2; 11 return OK; 12 } 13 else if(2==mod) 14 { 15 popd1=(char *)opd1; 16 popd2=(char*)opd2; 17 presult=(char *)result; 18 19 while(*presult++=*opd1++); 20 presult--; 21 while(*presult++=*opd2++); 22 *presult=’\0’; 23 return OK; 24 } 25 else 26 return ERROR; 27 } 28
本人萌新,错误难免,请叔叔们指教。