• 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。
  • 相关阅读:
    Silverligh OOB一直更新
    Silverlight使用通过服务绑定数据的控件
    vue prop不写value的处理逻辑
    .NET Core Http请求(GET、POST、上传文件并携带参数)
    流媒体服务器与web客户端插件的配置与搭建(Darwin Streaming server + Quictime plugin)
    目录
    margin标记可以带一个、二个、三个、四个参数,各有不同的含义。
    Asp.Net中几种相似数据绑定标记符号的解释及用法
    火狐自动换行 有空格
    xml
  • 原文地址:https://www.cnblogs.com/mrfangd/p/Variable-Arguments-C.html
Copyright © 2020-2023  润新知