C语言函数参数中的三个点(三点 “...”)是干什么用的?(可变参数)<stdarg.h>va_start 宏va_arg 宏va_end 宏

Posted Dontla

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C语言函数参数中的三个点(三点 “...”)是干什么用的?(可变参数)<stdarg.h>va_start 宏va_arg 宏va_end 宏相关的知识,希望对你有一定的参考价值。

参考文章:C 可变参数

参考文章2:C语言中的可变参数函数

文章目录

C 可变参数

有时,您可能会碰到这样的情况,您希望函数带有可变数量的参数,而不是预定义数量的参数。C 语言为这种情况提供了一个解决方案,它允许您定义一个函数,能根据具体的需求接受可变数量的参数。下面的实例演示了这种函数的定义。

int func(int, ... ) 

   .
   .
   .

 
int main()

   func(2, 2, 3);
   func(3, 2, 3, 4);

请注意,函数 func() 最后一个参数写成省略号,即三个点号(…),省略号之前的那个参数是 int,代表了要传递的可变参数的总数。为了使用这个功能,您需要使用 stdarg.h 头文件,该文件提供了实现可变参数功能的函数和宏。具体步骤如下:

  • 定义一个函数,最后一个参数为省略号,省略号前面可以设置自定义参数。
  • 在函数定义中创建一个 va_list 类型变量,该类型是在 stdarg.h 头文件中定义的。
  • 使用 int 参数和 va_start 宏来初始化 va_list 变量为一个参数列表。宏 va_start 是在 stdarg.h 头文件中定义的。
  • 使用 va_arg 宏和 va_list 变量来访问参数列表中的每个项。
  • 使用宏 va_end 来清理赋予 va_list 变量的内存。

现在让我们按照上面的步骤,来编写一个带有可变数量参数的函数,并返回它们的平均值:

实例

#include <stdio.h>
#include <stdarg.h>
 
double average(int num,...)

 
    va_list valist;
    double sum = 0.0;
    int i;
 
    /* 为 num 个参数初始化 valist */
    va_start(valist, num);
 
    /* 访问所有赋给 valist 的参数 */
    for (i = 0; i < num; i++)
    
       sum += va_arg(valist, int);
    
    /* 清理为 valist 保留的内存 */
    va_end(valist);
 
    return sum/num;

 
int main()

   printf("Average of 2, 3, 4, 5 = %f\\n", average(4, 2,3,4,5));
   printf("Average of 5, 10, 15 = %f\\n", average(3, 5,10,15));

当上面的代码被编译和执行时,它会产生下列结果。应该指出的是,函数 average() 被调用两次,每次第一个参数都是表示被传的可变参数的总数。省略号被用来传递可变数量的参数。

Average of 2, 3, 4, 5 = 3.500000
Average of 5, 10, 15 = 10.000000

原理

可变参数函数

在C语言中,有这样的一类函数:函数的参数个数是不确定的,动态变化的。比如我们经常用到的 prinf, sprinf 等等,这与我们平时定义的函数有些不同,它们便是可变参数函数,我们也可以自定义这类函数,这篇文章讲解C语言中的可变参数函数相关的内容。

原理与分析

C语言的参数列表是从右往左被压入堆栈的(函数右边的参数在栈底,左边的在栈顶),假设现在堆栈中有关参数的情况如下:

栈顶-不可变参数1-不可变参数2-....-不可变参数n-可变参数1-可变参数2-......可变参数n-栈低

现在假设我们知道了"可变参数n"的类型,我们还需要知道什么就能得到这个参数?

这个参数的地址!

那如何得到这个地址?

你必须知道前一个的地址和类型!

那怎么知道前一个的类型和地址?……

一直到最前面那个已经知道了类型和地址的不可变参数n是不是就搞定了?!

实现方案

下面看一下ANSI标准的实现。只讲相关的三个宏,用这三个宏就实现了上面的过程。

这三个宏是:

va_start( va_list arg_ptr, prev_param )
va_arg( va_list arg_ptr, type )
va_end( va_list arg_ptr )

既然明白了前面所述过程,就容易理解库里面那些宏或者函数的用途了。

1、 va_start 宏

这个函数用于确定第一个不可变参数的位置。它是如何做到的?就是通过最后一个不可变参数 n 实现的,因此它里面有两个参数,一个是不可变参数 n ,一个是可变参数 1 的地址。因为不可变参数 n 的地址和类型都能够得到,因此只要将这个指针(将它的地址赋给一个相同类型的指针)加 1 就能得到可变参数 1 的指针,通过这个宏, va_list 的指针就指向第一个可变参数。

2、 va_arg 宏

这个函数的作用就是获得当前指向的参数的值。但当前我们只是得到了可变参数 1 的地址,它的类型怎么确定?只能通过前面的不可变参数来传达这个信息,像 printf 里面的格式化字符串,或者你可以认为可变参数列表的参数类型和第几个不可变参数的相同。这种信息的传递是由程序员来设计的。

3、 va_end 宏

因为在 va_start 的实现中可能会有对参数列表的动态内存分配,需要调用 va_end 宏来释放。如果忘记了,很可能会“内存泄露”。

应用举例

举例1:提前已知所有参数类型的简单情况

/*程序功能:这里实验可变参数的函数,以及可变参数的宏的特性.
*可变参数函数void my_sum(int count, ...);
*这个函数的功能是计算多个整数的和。
*其中count是将要求和的整数的数目。
*其它的参数是可变的,其中第一个参数是char*的参数,用于提示。
*后面的参数分别是待求和的整数,一共count个。
*/

#include <stdarg.h>
#include <stdio.h>

void my_sum(int count, ...);

int main(int argc, char *argv[])

        int count = 5;
        printf("compute sum of %d numbers.\\n",count);
        my_sum(count,"the sum of numbers is:", 1, 2, 3, 4, 5);
        return 0;


void my_sum(int count, ...)

        //将要用来存放需要的某个可变参数的指针的信息
        va_list ap;
        char *prompt;
        int sum = 0;

        //开始的初始化,其中ap含有指向可变参数的指针的信息,count是当前函数中最后一个非可变的参数(这样才能定位).
        va_start(ap, count);

        //获取并返回下一个可变参数的值,第一个参数是ap不用说了,第二个参数是要获取的参数的类型。
        //根据文档,如果类型指定错误了,或者没有下一个可变参数了,那么返回的结果是随机的。
        prompt = va_arg(ap, char*);
        printf("%s\\n", prompt);

        int i;
        for(i = 0; i < count; ++i)
        
                sum += va_arg(ap, int);
        

        //使用完可变参数之后要用这个来释放资源
        va_end(ap);
        printf("%d\\n",sum);


运行结果:

compute sum of 5 numbers.
the sum of numbers is:
15

举例2:通过固定参数,来动态确定可变参数类型的复杂情况

#pragma warning(disable : 4996)
#include <stdarg.h>
#include <stdio.h>
void foo(char* fmt, ...)

    va_list ap;
    int d;
    char c;
    char* s;

    va_start(ap, fmt);
    while (*fmt) 
    
        switch (*fmt++)
        
            case 's':
                s = va_arg(ap, char*);
                printf("string %s\\n", s);
                break;
            case 'd':
                d = va_arg(ap, int);
                printf("int %d\\n", d);
                break;
            case 'c':
                c = (char)va_arg(ap, int);
                printf("char %c\\n", c);
                break;
        
    
    va_end(ap);


int main()

    foo("csds", 'b', "of", 50, "you");  //c代表char,s代表string,d代表int;用不同switch+case去解析后面的参数
    return 0;

VS抽风,静态检查报红,忽略它直接编译:

运行结果:

char b
string of
int 50
string you

以上是关于C语言函数参数中的三个点(三点 “...”)是干什么用的?(可变参数)<stdarg.h>va_start 宏va_arg 宏va_end 宏的主要内容,如果未能解决你的问题,请参考以下文章

c语言可变参数是干啥的

C函数后面多加两条下划线是干啥的?

SendMessage函数是干啥用的 它都有哪些参数

解析 c语言编写的根据三点得出圆心和半径的数学含义

c语言中啥是宏定义,它是干啥的?

ES6---扩展运算符和rest‘...’(三点运算符),在数组函数set/map等中的应用