C和Java中函数的可变参数列表

用MyEclipse编写我的实验系统时,遇到不知怎样确定参数个数的问题,由于这段时间用matlab的缘故,希望Java里也能找到使参数个数可变的方法,搜索了一下,找到一篇不错的文章,与大家分享一下。

原文地址:http://blog.csdn.net/daheiantian/article/details/6196946


       所谓“可变参数列表”就是指函数的形参的数目和类型是不确定的。printf函数就是一个可变参数的函数,第一个参数是格式化字符串,后面可以跟任意数目的参数。而我们平时使用的函数,其参数的数目和类型都是固定的,一旦声明,无法改变。

1. 先看一个问题

        计算一系列值的平均值,注意:这些值不是保存在数组中的,而是在参数中显示的传递。

常见的实现方式可能如下:

[c++]  view plain copy print ?
  1. /* 
  2. 参数: 
  3.     n_values    可变参数的个数 
  4.     v1, v2, v3, v4, v5     待计算平均数的参数 
  5. 返回值: 
  6.     返回n_values个参数的平均值     
  7. */  
  8. float average( int n_values, int v1, int v2, int v3, int v4, int v5 ){  
  9.     float sum =v1;  
  10.     if( n_values>=2 ){  
  11.         sum += v2;  
  12.     }else if( n_values>=3 ){  
  13.         sum += v3;  
  14.     }else if( n_values>=4 ){  
  15.         sum += v4;  
  16.     }else if( n_values>=5 ){  
  17.         sum += v5;  
  18.     }  
  19.     return sum/n_values;   
  20. }  

但是这种实现方式的缺点很明显:只能计算5个数的平均数,调用时加上n_values一共能且只能传递6个参数。

2. 在C中实现可变参数列表

        C语言中使用头文件stdarg.h来实现可变参数。stdarg.h属于标准库,其声明了一个类型va_list和三个宏(va_start、va_arg、va_end)。

具体的使用步骤如下:

1. 在函数声明中使用 省略号; 
2. 在函数体中穿件一个va_list类型的变量; 
3. 用宏va_start将该变量初始化为一个参数列表; 
4. 用宏va_arg访问这个参数列表; 
5. 用宏va_end完成清理工作;

         下面分别介绍它们的使用方法:

        2.1 在声明中使用省略号(就是3个句点)。  
              也就是说函数的声明要写成类似下面的形式:void(int n_values, …)。和普通函数一样,书写该函数的原型时,要和函数的定义保持一致,即也要使用省略号。

        2.2 声明一个va_list类型的变量。   
              声明方式为:va_list arg;  可以将这里的变量名称arg替换为其它名称。

        2.3 用宏va_start将该变量初始化为一个参数列表。作用是将调用时传递的参数列表复制到va_list变量中。  
              va_start需要两个参数:va_list类型变量的名称,省略号前最后一个命名参数。

        2.4 用宏va_arg访问这个参数列表。只能从前往后依次访问,即第一次调用返回参数列表的第一项,第二次调用返回第二项。  
              va_arg需要两个参数:va_list类型变量的名称,下一个变量的类型。

        2.5 用宏ar_end完成清理工作。例如:释放动态分配的用于存放可变参数的内存等。  
              va_end需要一个参数:va_list类型变量的名称。在调用va_end以后,只有通过va_start重新对列表进行初始化以后,才能继续使用变量arg。

3.用 可变参数列表 解决上面的均值问题

代码如下

[cpp]  view plain copy print ?
  1. /* 
  2. 参数: 
  3.     n_values    可变参数的个数 
  4.     ...     可变参数,需要导入头文件stdarg.h  
  5. 返回值: 
  6.     返回n_values个参数的平均值     
  7. */  
  8. float average2( int n_values, ... ){/* 在函数声明中使用 省略号 */  
  9.     va_list arg;    /* 声明va_list类型的变量 */   
  10.     int count;  
  11.     float sum = 0;  
  12.       
  13.     /* 用宏va_start将该va_list类型变量初始化为一个参数列表 */   
  14.     va_start( arg, n_values );  
  15.       
  16.     int temp;   
  17.     for( count=0; count<n_values; count++ ){  
  18.         /* 用宏va_arg依次访问这个参数列表*/   
  19.         temp = va_arg( arg, int );  
  20.         sum += temp;  
  21.     }   
  22.     /* 用宏va_end完成清理工作 */   
  23.     va_end( arg );  
  24.       
  25.     return sum/n_values;  
  26. }  

4.注意事项:

        1. 在省略号之前必须有一个命名参数。 
            因为如果连一个命名参数都没有,那么无法使用va_start。其实在C的内部实现中,是通过在省略号之前、并且紧挨着省略号的该变量来确定 可变参数的开始位置,所以必须要有至少一个命名参数。

        2. 在省略号之后不能再有命名参数。 
            因为可变参数的长度可以变化,如果在省略号之后还有命名参数,那么编译器无法确定应该把这个命名参数当做可变参数,还是应该当做命名参数,会引起歧义。

        3. 无法直接获得可变参数列表中的参数的数量和类型。 
            如果需要获得参数的数量和类型,就必须使用其他标志。例如:在printf函数中,通过“格式化字符串”中的“格式化字符”(如%d)既指定了可变参数的数量,也指定了可变参数的类型。

        4. 不能在va_arg中指定错误的类型。 
            例如:上面的程序中,可变参数部分如果含有一个浮点数2.0,那么其结果是未定义的。 
            原因分析:值在计算机中都是以0和1来存储的,一个值的类型无法简单的通过它被存储的位模式来判断,而要看它被解释成为什么类型。例如:位模式0100 0000 0100 1000 1111 0101 1100 0011被解释成单精度浮点数float值为3.14,被解释成整数值为1 078 523 331。所以如果指定了错误的类型,那么结果可能会相差千里。

        5. 注意参数传递时的“缺省类型提升”。char、short、float类型的值将缺省提升为 int 和 double型。所以如果需要char、short和float类型时,在va_arg中需要使用int和double。原因与上一条注意事项(“不能在va_arg中指定错误的类型”)相同。

        6. 通过va_arg依次访问参数列表时,不能进行回退访问。 
            如果需要进行回退访问,可以使用宏va_copy,将va_list变量arg1拷贝成arg2,通过访问arg2来实现访问已经在arg1中已经访问过的参数。 
            例子代码如下:在下面这段代码中,虽然arg已经不能再访问前面两项,但是可以通过arg2中重新获取这两项。

[c++]  view plain copy print ?
  1. va_list arg;  
  2. va_list arg2;  
  3. va_start(ap, n_values);  
  4. va_copy(arg2, arg);  
  5. va_arg(arg, int);  
  6. va_arg(arg, int);  

5. 练习一下:

         题目:编写一个max_list的函数,检查任意数目的整形参数并返回它们中的最大值。参数列表以一个负值结尾,用来表示列表的结束。

        实现代码如下

[c++]  view plain copy print ?
  1. #include   
  2. #include   
  3. #include    /* 可变长度参数需要 包含的头文件 */  
  4.   
  5. /* 
  6. 说明: 
  7. 本题目中没有表示长度的第一个参数,但是使用stdarg需要在列表前有一个参数。 
  8.     解决方法:将列表的第一个元素 作为使用stdarg需要的第一个参数。 
  9.               而且,由于列表以负数结尾,即如果第一个元素为负数时,表示列表中没有元素;  
  10. */  
  11.   
  12. int max_list( int first, ... );  
  13.   
  14. int main(){  
  15.     /* 测试max_list函数 */   
  16.     int max = max_list(2, 4, 5, 6, 7, 1, -1);  
  17.     printf("%d   /n", max);  
  18.       
  19.     max = max_list(-1);  
  20.     printf("%d   /n", max);  
  21.       
  22.     max = max_list(2, -10);  
  23.     printf("%d   /n", max);  
  24.       
  25.     getchar();  
  26.     return EXIT_SUCCESS;  
  27. }   
  28. int max_list( int first, ... ){  
  29.     va_list arg;  
  30.     int max; /* 用来保存结果 */   
  31.     /* first为负数时,说明列表为空,返回0 */  
  32.     if( first<0 ){  
  33.         return 0;  
  34.     }  
  35.       
  36.     /* 用va_start初始化arg, 第一个参数为va_list的名字,第二个为省略号前的最后一个变量 */  
  37.     va_start( arg, first );  
  38.       
  39.     /* 寻找最大数 */  
  40.     max = first;  
  41.     int temp = va_arg( arg, int );   
  42.     while( temp>=0 ){  
  43.         if( temp>max ){  
  44.             max = temp;  
  45.         }  
  46.         temp = va_arg(arg, int);  
  47.     }   
  48.       
  49.     /* 结束时要调用va_end */  
  50.     va_end(arg);  
  51.       
  52.     return max;  
  53. }  

6. java中的可变参数列表

6.1  格式

        Java中的可变参数列表使用起来比较简单。函数声明格式如下:

[java]  view plain copy print ?
  1. methodName([argumentList], dataType...argumentName)  

        argumentList:普通参数,可选; 
        dataType:数据类型或者类。调用该函数时,参数自动转换成dataType类型的数组; 
        ... :Java的操作符,表示可以接收0到多个参数,注意必须是3个点; 
        argumentName——参数名。           注意,可变参数必须在最后。

6.2   注意事项

         1. 可变参数只能作为函数的最后一个参数,但其前面可以有也可以没有任何其他参数。即与C语言相比,可变参数之后一定不能有其他命名参数,但java中可变参数之前 有或者没有 其他命名参数都可以,但在C语言中可变参数之前必须至少有一个命名参数。 
             由于可变参数必须是最后一个参数,所以一个函数最多只能有一个可变参数。如果有两个,那么前面的那一个就不是最后一个参数,出现错误。

         2. Java的可变参数,实质上是一个数组。所以可以获得可变参数的长度和类型,并且可以执行回退访问。

6.3   使用举例

        下面是一个求和的函数。

[java]  view plain copy print ?
  1. public class TestVariableArguments {  
  2.     public static void main(String[] args) {  
  3.         int temp;  
  4.         temp = add(12345);  
  5.         System.out.println(temp);  
  6.     }  
  7.     public static int add( int ... args ){  
  8.         int sum = 0;  
  9.         for(int i=0; i<args.length; i++){  
  10.             sum +=args[i];  
  11.         }  
  12.         return sum;  
  13.     }  
  14. }  

参考资料:《C和指针》,《C primer plus》

你可能感兴趣的:(java,C语言,可变参数)