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

  • 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. 彻底理解Runnable和Thread的区别

    昨天去面试,面试官问了一个问题:Runnable和Thread有什么区别,因为针对这个问题以前有背过,并且网上大多数都是这些结论,所以脱口而出: 1.Thread有单继承的问题: 2.Runnable ...

  2. 长乐国庆集训Day2

    T1 连珠风暴 题目 [题目描述] 给定M种颜色的珠子,每种颜色珠子的个数均不限,将这些珠子做成长度为N的项链. 问能做成多少种不重复的项链.两条项链相同,当且仅当两条项链通过旋转或是翻转后能重合在一 ...

  3. html 打开新页面

    设置 target 页面 这样会点击一次就产生一个页面 页面 填任意名称,多个点击只产生于一个页面

  4. golang微服务框架go-micro 入门笔记2.2 micro工具之微应用利器micro web

    micro web micro 功能非常强大,本文将详细阐述micro web 命令行的功能 阅读本文前你可能需要进行如下知识储备 golang分布式微服务框架go-micro 入门笔记1:搭建go- ...

  5. 运行一个docker镜像并开机启动

    记录,我用的liunx机是centos7.x 安装 安装Docker包$ sudo yum install docker-engine 启动Docker守护进程$ sudo service docke ...

  6. ElementUI table中el-table-column怎么设置百分比显示。

    看文档找到一种方法,是把 width 换成 min-width ,就支持百分比显示啦 !

  7. ES5和ES6的继承

    ES5继承 构造函数.原型和实例的关系:每一个构造函数都有一个原型对象,每一个原型对象都有一个指向构造函数的指针,而每一个实例都包含一个指向原型对象的内部指针, 原型链实现继承 基本思想:利用原型让一 ...

  8. kubeadm部署高可用K8S集群(v1.14.2)

    1. 简介 测试环境Kubernetes 1.14.2版本高可用搭建文档,搭建方式为kubeadm 2. 服务器版本和架构信息 系统版本:CentOS Linux release 7.6.1810 ( ...

  9. 理解 Cookie,Session,Token 并结合 Redis 的使用

    Http 协议是一个无状态协议, 客户端每次发出请求, 请求之间是没有任何关系的.但是当多个浏览器同时访问同一服务时,服务器怎么区分来访者哪个是哪个呢? cookie.session.token 就是 ...

  10. 【等待事件】等待事件系列(5.1)--Enqueue(队列等待)

    [等待事件]等待事件系列(5.1)--Enqueue(队列等待)   1  BLOG文档结构图   2  前言部分   2.1  导读和注意事项 各位技术爱好者,看完本文后,你可以掌握如下的技能,也可 ...