浅谈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                                      
View Code

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                 
View Code

 

本人萌新,错误难免,请叔叔们指教。

posted @ 2015-11-17 20:38  _Bin  阅读(269)  评论(0编辑  收藏  举报