zoukankan      html  css  js  c++  java
  • C语言的可变参数函数

    本文主要介绍了C语言的可变参数函数的基本使用方法。

    一、引入

    在讲解之前,让我们回到刚学习C语言的时候,看我们曾经写过的这样的一段C语言代码:

    #include <stdio.h>
    
    int main(void)
    {
        printf("Hello World!
    ");
        printf("%s
    ", "Hello World!");
        printf("%s %s
    ", "Hello", "World!");
        return 0;
    }
    

    显然,这段代码会输出三行“Hello World!”。大多数人入门C语言,也是从这样的一个程序开始的。但是,请比较一下三行语句的printf函数,看看它们有什么区别。

    仔细观察,我们发现,这三行语句中,printf函数分别有1、2、3个实参。而我们自己写过的大多数的函数,通常都只有固定的参数。在C语言不提供函数重载的情况下(实际上重载了也没用),如何实现这样的功能呢?

    二、可变参数函数的声明

    通常情况下,函数传参的过程是从右向左读取参数列表,并将其压入栈中的过程。在函数内可以从栈顶往下读取参数。如果我们将不同数量的参数同时压入栈中,再根据其数量从上往下依次读取,不就可以解决这个问题了吗?

    在C语言中,我们可以在参数列表的最后且不唯一的一个参数,使用“...”来表示可变的参数。例如printf的函数原型:

    int printf(const char *restrict format, ...);
    

    对于函数而言,它是怎么获取参数的个数呢?在解答这个问题之前,我们再看看printf函数。printf函数是怎么知道我们有多少模式串提供给它呢?答案是printf的第一个参数。在format参数中,我们使用占位符来标记参数。我们使用了多少个占位符,意味着我们将会给printf函数提供多少参数。

    就像printf函数一样,在我们自己书写函数的时候,可以要求用户将参数可变的参数数量的个数传入,例如下面的函数中的arg_cnt参数,可以被规定为可变参数的个数。

    void foo(int arg_cnt, ...);
    

    三、可变参数列表的使用

    我们希望,在函数体中,能像操作一般的变量一样,操纵我们的参数。C语言给我们提供了这样的语法。

    首先,需要在代码包含stdarg.h这个头文件。这个头文件提供了va_list类型,用于表示可变参数的列表。然后提供了四个宏a_startva_argva_endva_copy用于操作它。

    首先,我们设计这样的一个函数,并定义一个可变参数列表:

    #include <stdarg.h>
    #include <limits.h>	// 被INT_MIN宏所需要
    int find_max_int(int arg_cnt, ...)	// 寻找最大的整数
    {
    	va_list ap;
    	int max = INT_MIN;
    	// ...
    	return max;
    }
    

    va_start宏接受两个参数,原型为void va_start(va_list ap, parmN);,第一个参数是一个va_list;第二个参数为函数参数列表中,可变参数的前一个参数名,用于确定可变参数列表的位置。va_end只有一个参数,类型也为va_listva_arg有两个参数,第一个参数为va_list,第二个参数为要读取的变量的类型。而va_copy的作用,是将第二个va_list参数赋值给第一个va_list参数。

    但是,C11标准文档指出:

    The parameter parmN is the identifier of the rightmost parameter in the variable parameter list in the function definition (the one just before the , ...). If the parameter parmN is declared with the register storage class, with a function or array type, or with a type that is not compatible with the type that results after application of the default argument promotions, the behavior is undefined.

    即如果va_start的第二个参数是一个寄存器变量、函数、数组,或者是“不兼容应用默认参数提升后生成的类型”(机器翻译,建议读一下原文),则行为是未定义的。

    根据上面的叙述,我们就可以完成这个函数了:

    int find_max_int(int arg_cnt, ...)
    {
    	va_list ap;
    	int max = INT_MIN;
    	va_start(ap, arg_cnt);
    	while (arg_cnt--)
    	{
    		int now = va_arg(ap, int); // va_list中,每个参数只可以被读取一次
    		if (max < now)
    		{
    			max = now;
    		}
    	}
    	va_end(ap);
    	return max;
    }
    

    习题

    1. 自己实现一个printf函数。
    2. 了解C++的std::initializer_list。
  • 相关阅读:
    二分图最大匹配的K&#246;nig定理及其证明
    HDOJ 2389 Rain on your Parade
    HDOJ 1083 Courses
    HDOJ 2063 过山车
    POJ 1469 COURSES
    UESTC 1817 Complete Building the Houses
    POJ 3464 ACM Computer Factory
    POJ 1459 Power Network
    HDOJ 1532 Drainage Ditches
    HDU 1017 A Mathematical Curiosity
  • 原文地址:https://www.cnblogs.com/mrfangd/p/Variable-Arguments-C.html
Copyright © 2011-2022 走看看