肯尼斯·里科《C和指针》第7章 函数(3)可变参数列表

7.6 可变参数列表

在函数的原型中,列出了函数期望接受的参数,但原型只能显示固定数目的参数。是否可以让一个函数在不同的时候接受不同数目的参数呢?答案是肯定的,但存在一些限制。考虑一个计算一系列值的平均值的函数。如果这些值存储于数组中,这个任务就太简单了,所以为了让问题变得更有趣一些,我们假定它们并不存储于数组中。

程序7.9a试图完成这个任务。

/*
** 计算指定数目的值的平均值(差的方案)。
*/
float
average( int n_values, int v1, int v2, int v3, int v4, int v5 )
{
         float sum = v1;
        if( n_values >= 2 )
           sum += v2;
        if( n_values >= 3 )
           sum += v3;
        if( n_values >= 4 )
            sum += v4;
        if( n_values >= 5 )
            sum += v5;
        return sum / n_values;
}

这个函数存在几个问题。首先,它不对参数的数量进行测试,无法检测到参数过多这种情况。不过这个问题很好解决,简单加上测试就是了。其次,函数无法处理5个以上的值。要解决这个问题,只有在已经很臃肿的代码中再增加一些类似的代码。

但是,当试图用下面这种形式调用这个函数时,还存在一个更为严重的问题:

avgl = average(3,x,y,z);

这里只有4个参数,但函数具有6个形参。标准是这样定义这种情况的:这种行为的后果是未定义的。这样,第1个参数可能会与n_values对应,也可能与形参v2对应。当然可以测试一下自己的编译器是如何处理这种情况的,但这个程序显然是不可移植的。我们需要的是一种机制,它能够以一种良好定义的方法访问数量未定的参数列表。

7.6.1 stdarg宏

可变参数列表是通过宏来实现的,这些宏定义于stdarg.h头文件,它是标准库的一部分。这个头文件声明了一个类型va_list和3个宏——va_start、va_arg和va_end。可以声明一个类型为va_list的变量,与这几个宏配合使用,访问参数的值。

程序7.9b使用这3个宏正确地完成了程序7.9a试图完成的任务。注意参数列表中的省略号:它提示此处可能传递数量和类型未确定的参数。在编写这个函数的原型时,也要使用相同的记法。

/*
** 计算指定数量的值的平均值。
*/
#include 
float
average( int n_values, ... )
{
           va_list   var_arg;
           int    count;
           float sum = 0;
           /*
           ** 准备访问可变参数。
           */
           va_start( var_arg, n_values );
           /*
           ** 添加取自可变参数列表的值。
           */
           for( count = 0; count < n_values; count += 1 ){
                 sum += va_arg( var_arg, int );
           }
           /*
           ** 完成处理可变参数。
           */
           va_end( var_arg );
           return sum / n_values;
}

函数声明了一个名叫var_arg的变量,用于访问参数列表的未确定部分。这个变量通过调用va_start来初始化。它的第1个参数是va_list变量的名字,第2个参数是省略号前最后一个有名字的参数。初始化过程把var_arg变量设置为指向可变参数部分的第1个参数。

为了访问参数,需要使用va_arg,这个宏接受两个参数:va_list变量和参数列表中下一个参数的类型。在这个例子中,所有的可变参数都是整型。在有些函数中,可能要通过前面获得的数据来判断下一个参数的类型。va_arg返回这个参数的值,并使var_arg指向下一个可变参数。

最后,当访问完最后一个可变参数之后,需要调用va_end。

7.6.2 可变参数的限制

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

你可能同时注意到参数列表中至少要有一个命名参数。如果连一个命名参数也没有,也就无法使用va_start。这个参数提供了一种方法,用于查找参数列表的可变部分。

对于这些宏,存在如下两个基本的限制。这两个限制导致的一个直接结果是,一个值的类型无法简单地通过检查它的位模式来判断。

1.这些宏无法判断实际存在的参数的数量。

2.这些宏无法判断每个参数的类型。

要绕开这两个限制,就必须使用命名参数。在程序7.9b中,命名参数指定了实际传递的参数数量,不过它们的类型被假定为整型。printf函数中的命名参数是格式字符串,它不仅指定了参数的数量,还指定了参数的类型。

如果在va_arg中指定了错误的类型,则结果是不可预测的。这个错误很容易发生,因为va_arg无法正确识别作用于可变参数之上的缺省参数类型提升。char、short和float类型的值实际上将作为int或double类型的值传递给函数。所以在va_arg中使用后面这些类型时应该小心。

这个知识点也从来没有见过,当时学的是argc和argv,不知道有什么区别和联系没有(因为这个我也没有弄懂)。。。

你可能感兴趣的:(c语言,学习,笔记,其他)