可变参数函数实现的步骤如下:

  • 1.在函数中创建一个va_list类型变量

  • 2.使用va_start对其进行初始化

  • 3.使用va_arg访问参数值

  • 4.使用va_end完成清理工作

接下来我们来实现一个变长参数函数来对给定的一组整数进行求和。程序清单如下:


#include <stdio.h>
/*要使用变长参数的宏,需要包含下面的头文件*/
#include <stdarg.h>
/*
* getSum:用于计算一组整数的和
* num:整数的数量
*
* */
int getSum(int num, ...)
{
va_list ap;//定义参数列表变量
int sum = 0;
int loop = 0;
va_start(ap, num);
/*遍历参数值*/
for (; loop < num ; loop++)
{
/*取出并加上下一个参数值*/
sum += va_arg(ap, int);
}
va_end(ap);
return sum;
}
int main(int argc, char *argv[])
{
int sum = 0;
sum = getSum(5, 1, 2, 3, 4, 5);
printf("%d\n", sum);
return 0;
}

  上面的小程序接受变长参数,第一个参数表明将要计算和的整数个数,后面的参数是要计算的值。
编译运行可得结果:15。

总结

通过前面的分析和示例,我们来做一些总结

    • 变长参数实现的基本原理
      对于x86来说,函数参数入栈顺序为从右往左,因此,在知道第一个参数地址之后,我们能够通过地址偏移获取其他参数,虽然x86-64在实现上略有不同,但`对于开发者使用来说,实现变长参数函数没有32位和64位的区别。

    • 变长参数实现注意事项
      1.…前的参数可以有1个或多个,但前一个必须是确定类型。
      2.传入参数会可能会出现类型提升。
      3.va_arg的type类型不能是char,short int,float等类型,否则取值不正确,原因为第2点。
      4.va_arg不能往回取参数,但可以使用va_copy拷贝va_list,以备后用。
      5.变长参数类型注意做好检查,例如可以采用printf的占位符方式等等。
      6.即便printf有类型检查,但也要注意参数匹配,例如,将int类型匹配%s打印,将会出现严重问题。
      7.当传入参数个数少于使用的个数时,可能会出现严重问题,当传入参数大于使用的个数时,多出的参数不会被处理使用。
      8.注意字节对齐问题。

变长参数实现

经过前面的理解分析,我们知道,正是由于参数从右往左入栈(但是要注意的是,对于x86-64,它的参数不是完全从右往左入栈,且参数可能不在一个连续的区域中,它的变长参数实现也更为复杂,我们这里不展开)可以实现变长参数。当然了,这一切,C已经有现成可用的一些东西来帮我们实现变长参数。
它主要通过一个类型(va_list)和三个宏(va_start、va_arg、va_end)来实现

va_list :存储参数的类型信息,32位和64位实现不一样。
void va_start ( va_list ap, paramN );
参数:
ap: 可变参数列表地址 
paramN: 确定的参数
功能:初始化可变参数列表,会把paraN之后的参数放入ap中 type va_arg ( va_list ap, type );
功能:返回下一个参数的值。 void va_end ( va_list ap );
功能:完成清理工作。
转载自:
https://mp.weixin.qq.com/s/CbXT6G0CHzp0BG0gS-hprw
#include <stdio.h>
#include <stdarg.h>
#include <math.h> double sample_stddev(int count, ...)
{
/* Compute the mean with args1. */
double sum = 0;
va_list args1;
va_start(args1, count);
va_list args2;
va_copy(args2, args1); /* copy va_list object */
for (int i = 0; i < count; ++i)
{
double num = va_arg(args1, double);
sum += num;
}
va_end(args1);
double mean = sum / count; /* Compute standard deviation with args2 and mean. */
double sum_sq_diff = 0;
for (int i = 0; i < count; ++i)
{
double num = va_arg(args2, double);
sum_sq_diff += (num - mean) * (num - mean);
}
va_end(args2); return sqrt(sum_sq_diff / count);
} int main(void)
{
printf("%f\n", sample_stddev(4, 25.0, 25.0, 25.0, 25.0)); return 0;
}

 这个是va_copy的函数用法,

 

c语言的可变参数实例的更多相关文章

  1. C语言的可变参数在Linux(Ubuntu)与Windows下注意点

    基本上C语言的可变参数原理在不同平台和不同编译器下基本类似(通过函数入栈,从右向左,从高位到低位地址),不过部分实现会有所不同:在使用中需要注意的是: va_list 为char 类型指针,部分调用如 ...

  2. C语言中可变参数的函数(三个点,“...”)

    C语言中可变参数的函数(三个点,“...”) 本文主要介绍va_start和va_end的使用及原理. 在以前的一篇帖子Format MessageBox 详解中曾使用到va_start和va_end ...

  3. [11 Go语言基础-可变参数函数]

    [11 Go语言基础-可变参数函数] 可变参数函数 什么是可变参数函数 可变参数函数是一种参数个数可变的函数. 语法 如果函数最后一个参数被记作 ...T ,这时函数可以接受任意个 T 类型参数作为最 ...

  4. C语言中可变参数的使用

    在C语言程序编写中我们使用最多的函数一定包括printf以及很多类似的变形体.这个函数包含在C库函数中,定义为 int printf( const char* format, ...); 除了一个格式 ...

  5. C语言中可变参数函数实现原理

    C函数调用的栈结构 可变参数函数的实现与函数调用的栈结构密切相关,正常情况下C的函数参数入栈规则为__stdcall, 它是从右到左的,即函数中的最右边的参数最先入栈.例如,对于函数: void fu ...

  6. C语言中可变参数的用法

    原文地址: http://blog.csdn.net/wooin/archive/2006/04/29/697106.aspx   我们在C语言编程中会遇到一些参数个数可变的函数,例如printf() ...

  7. C语言函数可变参数列表

    C语言允许使用可变参数列表,我们常用的printf函数即为可变参数函数,C标准库提供了stdarg.h为我们提供了这方面支持:该头文件提供了一些类型和宏来支持可变参数列表,包括类型va_list,宏v ...

  8. C语言中可变参数的原理——printf()函数

    函数原型: int printf(const char *format[,argument]...) 返 回 值: 成功则返回实际输出的字符数,失败返回-1. 函数说明: 使用过C语言的人所再熟悉不过 ...

  9. C语言的可变参数

    可变参数给编程带来了很大的方便,在享受它带来的方便的同时,很有必要了解一下其实现方式,在了解编程语言的同时,也可以扩展编程的思路. 可变参数需要用到3个宏函数和一个类型,他们都定义在<stdar ...

随机推荐

  1. SQlL Server ----- 通过年月进行查询

    websit  中的代码. 不修改 对控件进行修改,展示年月 WdatePicker({ dateFmt: 'yyyy-MM', isShowToday: false, isShowClear: fa ...

  2. Redis中3种特殊的数据类型(BitMap、Geo和HyperLogLog)

    前言 Reids 在 Web 应用的开发中使用非常广泛,几乎所有的后端技术都会有涉及到 Redis 的使用.Redis 种除了常见的字符串 String.字典 Hash.列表 List.集合 Set. ...

  3. Lock Free (无锁并发)

    CAS( compare and swap) 原子操作,保证了如果需要更新的地址没有被其他进程(线程)改动过,那么它可以安全的写入.而这也是我们对于某个数据或者数据结构加锁要保护的内容,保证读写的一致 ...

  4. 虚拟机CentOS创建/使用快照

    快照 1.什么是快照 说的直白一点,就是创建一个备份.当执行了不可逆的错误操作后,可以通过快照用来恢复系统 2.创建快照的三种模式 挂载状态下创建快照 开机状态下创建快照 关机状态下创建快照 3.如何 ...

  5. Go语言【数据结构】数组

    数组 简介 数组是具有相同唯一类型的一组已编号且长度固定的数据项序列,这种类型可以是任意的原始类型例如整形.字符串或者自定义类型.数组形式 numbers[0], numbers[1] ..., nu ...

  6. 小米9安装charles证书

    一.打开你 mac 中对应的 charles 二.点击右上角的help按钮,打开帮助弹窗 三.点击帮助弹窗中的SSL Proxying,选择save charles root certificatio ...

  7. JAVA-AbstractQueuedSynchronizer-AQS

    import lombok.extern.slf4j.Slf4j; import java.util.concurrent.CountDownLatch; import java.util.concu ...

  8. redis 中文显示的问题解决方法

    在redis 中存储中文,读取会出现乱码(其实不是乱码,只是不是我们存的中文显示) 1 redis> set test "我们" 2 OK 3 redis> get t ...

  9. Python进阶----粘包,解决粘包(旗舰版)

    Python进阶----粘包,解决粘包(旗舰版) 一丶粘包 只有TCP有粘包现象,UDP永远不会粘包 什么是粘包     存在于客户端接收数据时,不能一次性收取全部缓冲区中的数据.当下一次再有数据来时 ...

  10. Java 之 MyBatis(一)入门

    一.Mybatis 框架概述 (1)mybatis 是一个优秀的基于 java 的持久层框架,它内部封装了 jdbc,使开发者只需要关注 sql 语句本身,而不需要花费精力去处理加载驱动.创建连接.创 ...