C 语言允许定义参数数量可变的函数,这称为可变参数函数(variadic function)。这种函数需要固定数量的强制参数(mandatory argument),后面是数量可变的可选参数(optional argument)

这种函数必须至少有一个强制参数。可选参数的类型可以变化。可选参数的数量由强制参数的值决定,或由用来定义可选参数列表的特殊值决定。

平时我们写C语言函数时,一般是固定参数的,但是像打印输出格式化内容时,其参数个数就不确定了,类似如下:

printf("This is a sample, %s, %d, %u", "variable-argument", -20, 50);

对printf函数来说,需要格式化的内容是怎样的形式,完全由程序员决定,这就导致参数个数不定(从第二个参数开始,第一个参数是固定的字符串类型),因此printf的函数原型只能采用可变参数的形式。下面是C语言中printf原型:

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

a_start、va_arg、va_end和va_list

对于每一个强制参数来说,函数头部都会显示一个适当的参数,像普通函数声明一样。参数列表的格式是强制性参数在前,后面跟着一个逗号和省略号(...),这个省略号代表可选参数。

可变参数函数要获取可选参数时,必须通过一个类型为 va_list 的对象,它包含了参数信息。这种类型的对象也称为参数指针(argument pointer),它包含了栈中至少一个参数的位置可以使用这个参数指针从一个可选参数移动到下一个可选参数,由此,函数就可以获取所有的可选参数。va_list 类型被定义在头文件 stdarg.h 中。va在这里是variable-argument(可变参数)的意思。

当编写支持参数数量可变的函数时,必须用 va_list 类型定义参数指针,以获取可选参数。在下面的讨论中,va_list 对象被命名为 argptr。可以用 4 个宏来处理该参数指针,这些宏都定义在头文件 stdarg.h 中:

void va_start(va_list argptr, lastparam);

va_start 使用第一个可选参数的位置来初始化 argptr 参数指针。该宏的第二个参数必须是该函数最后一个有名称参数的名称。必须先调用该宏,才可以开始使用可选参数。

type va_arg(va_list argptr, type);

展开宏 va_arg 会得到当前 argptr 所引用的可选参数,也会将 argptr 移动到列表中的下一个参数。宏 va_arg 的第二个参数是刚刚被读入的参数的类型。

void va_end(va_list argptr);

当不再需要使用参数指针时,必须调用宏 va_end。如果想使用宏 va_start 或者宏 va_copy 来重新初始化一个之前用过的参数指针,也必须先调用宏 va_end。

void va_copy(va_list dest, va_list src);

宏 va_copy 使用当前的 src 值来初始化参数指针 dest。然后就可以使用 dest 中的备份获取可选参数列表,从 src 所引用的位置开始。

typedef int *va_list[1];#define va_start(ap, parmN)    (void)(*(ap) = __va_start(parmN))
#define va_arg(ap, type)      __va_arg(*(ap), type)
#define va_end(ap)          ((void)(*(ap) = 0))

它的实现原理利用了内存的压栈技术,将参数压入(push)栈内,使用时,再逐个从栈里pop出来。需要注意的是,压栈的顺序是从最右边参数开始的,再向左逐个压入,根据栈的原理,在取参数时,就从第一个可变参数开始了。

在VS2017-X86平台下的源码:

typedef char* va_list;
#define _ADDRESSOF(v) (&(v))
#define _INTSIZEOF(n)          ((sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1))#define __crt_va_start_a(ap, v) ((void)(ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v)))
#define __crt_va_arg(ap, t)     (*(t*)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)))
#define __crt_va_end(ap)        ((void)(ap = (va_list)0))#define va_start __crt_va_start
#define va_arg   __crt_va_arg
#define va_end   __crt_va_end
#define va_copy(destination, source) ((destination) = (source))

使用方法

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

注意事项

  1. 定义的函数必须至少有一个固定参数;这么做的目的很明显,就是要定位变参的首地址,也就是固定参数地址+sizeof(固定参数类型)啦;
  2. 固定参数和可变参数之间可以没有任何关系;虽然经典的printf函数中,第一个固定参数里(格式化字符串)包含了后续变参的类型,但是这仅仅是函数功能的需要,语法上没有任何要求,下面的例子就可以证明这一点;
#include <stdarg.h>
#include <stdio.h>void variable_argument(int fix_argument1, int fix_argument2, ...)
{va_list ptr;va_start(ptr, fix_argument2);int first = va_arg(ptr, int);int second = va_arg(ptr, int);int third = va_arg(ptr, int);char* four = va_arg(ptr, char*);float five = va_arg(ptr, double);va_end(ptr);printf("first is %d, second is %d, third is %d, four is %s, five is %f\n", first, second, third, four, five);return;
}int main(int argc, char** argv[])
{variable_argument(1, 2, 5, 2, -5, "Hello", 3.14159);return 0;
}
// 函数add() 计算可选参数之和
// 参数:第一个强制参数指定了可选参数的数量,可选参数为double类型
// 返回值:和值,double类型
double add(int n, ...)
{int i = 0;double sum = 0.0;va_list argptr;va_start(argptr, n);             // 初始化argptrfor (i = 0; i < n; ++i)       // 对每个可选参数,读取类型为double的参数,sum += va_arg(argptr, double); // 然后累加到sum中va_end(argptr);return sum;
}//double s = add(5, 1.0, 2.0, 3.0, 4.0, 5.0);
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>int average(int n, ...)
{va_list arg;int i = 0;int sum = 0;va_start (arg, n);for (i=0; i<n; i++){sum += va_arg(arg, int);}return sum/n;va_end (arg);
}int main()
{int ret1 = average (5, 4, 5, 6, 7, 8);int ret2 = average (3, 4, 5, 6);printf ("ret1 = %d, ret2 = %d\n", ret1, ret2);system ("pause");return 0;
}

参考文章:

C语言中可变参数的使用方法

C语言可变参数列表知识总结

【干货】C语言可变参数

C语言可变参数函数(printf/scanf)相关推荐

  1. C语言可变参数函数_初探

    一.什么是可变参数函数 C语言允许定义参数数量可变的函数,这称为可变参数函数(variadic function).这种函数需要固定数量的强制参数,后面是数量可变的可选参数. 其中,强制参数必须至少一 ...

  2. c语言怎样获得函数内参数的值_C语言可变参数函数的实现原理

    在本人的<C语言可变参数函数的实现方法>一文中,介绍了如何建立自己的可变参数函数. 下面继续介绍可变参数函数的实现原理. 在汇编语言程序设计中,详细介绍了子程序的实现思想: (1)子程序只 ...

  3. 关于C语言可变参数函数的一些研究和总结

    可变参数函数是指函数参数的个数.类型等是不固定的,需要在用户调用过程中,根据实际传入的参数来确定其类型.个数等信息.例如:可变参数函数printf可谓是在C开发过程中使用最多的标准输出库函数之一,因此 ...

  4. C语言可变参数函数的使用及相关函数介绍

    By qianghaohao(Xqiang) 在C语言中当一个函数参数无法列举出来,或者参数个数  不确定,这时我们将函数声明为可变参数的形式,根据需  要传适当个数的参数.举例如下: int fun ...

  5. 编写可变参数函数 c语言,C语言可变参数函数的编写

    1. 引言 C语言我们接触的第一个库函数是 printf("hello,world!");其参数个数为1个. 然后,我们会接触到诸如: printf("a=%d,b=%s ...

  6. C语言 可变参数函数 tcy

    可变参数函数 1.1.格式:int add(int n, ...) 1.2.参数:n:强制参数(必须至少一个)...:可选参数类型可变化 2.stdarg.h : void va_start( va_ ...

  7. 揭密X86架构C可变参数函数实现原理

    前两天公司论坛有同事在问C语言可变参数函数中的va_start,va_arg 和 va_end 这些接口怎么实现的?我毫不犹豫翻开箱底,将多年前前(算算有十年了)写的文章「亲密接触C可变参数函数」发给 ...

  8. 解析可变参数函数的实现原理(printf,scanf)

    From: http://hi.baidu.com/huifeng00/blog/item/085e8bd198f46ed3a8ec9a0b.html 学习C的语言的时候,肯定接触到标准输出和标准输入 ...

  9. 从printf谈可变参数函数的实现

    作者:戎亚新 摘要:一直以来都觉得printf似乎是c语言库中功能最强大的函数之一,不仅因为它能格式化输出,更在于它的参数个数没有限制,要几个就给几个,来者不拒.printf这种对参数个数和参数类型的 ...

最新文章

  1. Redis持久化 - RDB和AOF
  2. 使用selenium+phantomJS实现网页爬取
  3. 苏宁高时效、高并发秒杀业务中台的设计与实现
  4. TCP/IP的二层负载
  5. YII2 rule exist unique
  6. 计算机博士论文答谢,这篇博士论文《致谢》刷屏,句句扎心
  7. 上海技术计算机学校学费多少,上海web前端学校学费一般是多少
  8. 并查集路径压缩和按rank合并代码实现
  9. EMNLP 2022 和 COLING 2022,投哪个会议比较好?
  10. WPS怎么转成PPT文件
  11. 完全卸载exchange2010
  12. 嵌入式 Linux 入门 环境篇(一、开发板初体验)
  13. python将png转为jpg,Python OpenCV读取png图像转成jpg图像存储的方法
  14. 乐理:十二平均律与大小调
  15. 网工解惑:何为二层交换机,它与三层交换机的区别在哪里?
  16. LTE学习笔记 ——SRB、DRB
  17. 【概率论】- (2)假设检验
  18. javaSwing ATM
  19. Backup App's data without rooting the phone
  20. ajax连接服务器获取后台数据

热门文章

  1. C#PrintDocument打印尺寸调整
  2. 多目标规划(数学建模)
  3. vue-cli报错:getaddrinfo enotfound locahost
  4. 使用Swift和SceneKit开发一片圣诞树林
  5. 模拟路由器使用距离向量算法更新路由表
  6. vmware安装centos7以及安装图形化操作界面
  7. IXP425 U-boot移植
  8. gecco爬虫初见与综述
  9. Eclipse常用快捷键(持续更新)
  10. pythonopencv检测行人_【图像处理】使用OpenCV实现人脸和行人检测