浅析可变参数

来源:互联网 发布:深圳软件协会会长 编辑:程序博客网 时间:2024/06/12 21:13

在函数原型中,列出了函数期望的参数,但原型只能显示固定数目的参数,在求平均数的时候,如果数字有少数的几个,那么我们可以依次给函数传参,可是,如果数字多的话,难道要写出每个形参吗?那么让一个函数在不同时候接受不同数目的参数是否可以呢?答案是ok的,因为C语言很聪明,提供了一系列的可变参数来解决这个问题。

1,stdarg宏
可变参数可变参数列表是通过宏来实现的,这些宏定义在stdarg.h头文件中。这个头文

件声明了一个类型va_list和三个宏va_start,va_arg和va_end。

(1)va_list:声明变量,用于访问参数列表的未确定部分;

(2)va_start:初始化va_list申明的变量,它的第一个参数是va_list变量的名字,第二个参数是省略号前最后一个有名字的参数。通过初始化把va_list变量设置为指向可变参数部分的第一个参数;

(3)va_arg:用于访问参数,它的第一个参数是va_list变量的名字,第二个参数是参数列表中下一个参数的类型。在有些函数中,参数类型都是一样的,但有些函数可能要通过前面获得的数据来判断下一个参数的类型。va_arg返回这个参数的值,并使va_list变量指向下一个可变参数,如果此时已经是最后一个参数,则指向NULL。

(4)va_end:当访问最后一个可变参数之后,用va_end置空va_list变量.

注意:可变参数必须从头到尾按照顺序逐个访问,但也可以在访问几个参数之后半途中止。但是,不能从中间开始访问。此外,由于参数列表中的可变参数部分并没有原型,所以,所有作为可变参数传递给函数的值都将执行缺省参数类型提升。

2.代码实现:

实现可变参数函数:average,求平均值。

#include <stdio.h>#include <stdarg.h>#include <stdlib.h>int average(int n,...){   va_list arg;   int i = 0;   int sum = 0;   va_start(arg,n);  //以固定参数的地址为起点确定可变参数的内存起始地址   for(i=0; i<n; i++)   {      sum += va_arg(arg,int);//得到下一个可变参数的值   }   va_end(arg);   return sum/n;}int main(){   int ret = 0;   ret = average(5,1,2,3,4,5);   printf("%d\n",ret);   return 0;}

实现可变参数函数:Max,求最大值。

#include <stdio.h>#include <stdarg.h>#include <stdlib.h>int Max(int n,...){    va_list arg;    int i = 0;    int max = 0;    va_start(arg,n);//</span><span style="line-height: 26px; font-family: 宋体;"><span style="font-size:18px;">以固定参数的地址为起点确定可变参数的内存起始地址</span></span><span style="font-size:24px;">    for(i=0; i<n; i++)    {        int tmp = 0;        tmp = va_arg(arg,int); //得到下一个可变参数的值        if(tmp > max)            max = tmp;    }    va_end(arg);    return max;}int main(){    int ret = 0;    ret = Max(5,1,2,3,4,5);    printf("%d\n",ret);    system("pause");    return 0;}

3.可变参数在编译器中的处理

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 )

代码的含义:

(1)首先把va_list被定义成char*,这是因为在我们目前所用的PC机上,字符指针类型可以用来存储内存单元地址。而在有的机器上va_list是被定义成void*的

(2)定义_INTSIZEOF(n)主要是为了某些需要内存的对齐的系统.这个宏的目的是为了得到最后一个固定参数的实际内存大小。在我的机器上直接用sizeof运算符来代替,对程序的运行结构也没有影响。(后文将看到我自己的实现)。

(3)va_start的定义为 &v+_INTSIZEOF(v)
,这里&v是最后一个固定参数的起始地址,再加上其实际占用大小后,就得到了第一个可变参数的起始内存地址。所以我们运行va_start(ap,v)以后,ap指向第一个可变参数在的内存地址.

(4)va_arg():有了va_start的良好基础,我们取得了第一个可变参数的地址,在va_arg()里的任务就是根据指定的参数类型取得本参数的值,并且把指针调到下一个参数的起始地址。

(5)va_end宏的解释:ap=(char*)0.

4.实现简单的printf可变参数的函数。

#include <stdio.h>#include <stdarg.h>#include <stdlib.h>int my_printf(char *val,...){    va_list arg;    va_start(arg,val);    while(*val)    {        if(*val == 'c')        putchar(va_arg(arg,char));//输出字符        else if(*val == 's')        puts(va_arg(arg,char*));//输出字符串        val++;    }    va_end(arg);}int main(){    my_printf("s ccc","hello", 'b','i','t');    return 0;}

注意:如果在va_arg中指定了错误类型,其结果不可预测。

原创粉丝点击