C语言的本质(18)——函数的可变参数
一般而言,在设计函数时会遇到许多数学和逻辑操作,是需要一些可变功能。例如,计算数字串的总和、字符串的联接或其它操作过程。
实现一个函数,要求在函数中计算传入的所有参数之和,并输出到屏幕上。这个函数实现起来并不困难,问题在于这个函数的参数个数是不确定的:假设这个函数的名字是sum_n,那么程序员既可以调用sum_n(1, 2)来计算两个数的和,又可以调用sum_n(2, 3, 4)来计算三个数的和,还可以调用sum_n(1, 5, 8, 9)来计算四个数的和。
要实现这个函数,显然不能为不同数量的参数实现不同的函数——这些函数完成的功能完全相同,只是提供的参数不同而已,都实现一遍太浪费时间了。这时就需要创建参数个数不确定的函数来解决这个问题。
在C语言中允许定义一个具有不确定个数参数的函数,这种情形被称为可变参数,也叫不定参数。带有可变参数的函数的声明方式如下:
返回值类型函数名(形式参数列表, ...)
与固定参数的函数相比,可变参数的函数在声明时只要在形参列表的最后提供额外的三个“.”即可。可变参数的函数仍然可以有个数确定的固定参数,固定参数之后则是个数可变的可选参数。
下面就是带有一个固定参数和可选参数的函数声明:
int func_a(int x, …)
下面则是一个带有两个固定参数和可选参数的函数声明:
int func_b(char a, double b, …)
完成了可变参数函数的声明,下面来看看如何在对应的函数中得到传递进来的实际参数——肯定不能靠省略号“…”来访问可选参数。
要处理可变参数,需要用C到标准库的va_list类型和va_start、va_arg、va_end宏,这些定义在stdarg.h头文件中。
首先需要使用va_list定义一个变量,这个变量将被用来存储指向不同可变参数的指针。有关指针的概念将在“指针”一章中详述,这里大家只要了解如何使用即可。下面的语句定义了一个名为argPtr的可变参数指针。
va_list argPtr;
刚刚定义的argPtr没有任何意义,因为还没有进行初始化。宏va_start用来初始化argPtr,得到的是第一个可变参数的地址。
va_start(argPtr, <最后一个固定参数>);
例如对于下面的函数声明
int func_b(char a, double b, …)
应当使用
va_start(argPtr, b);
对argPtr进行初始化。初始化后,argPtr指向第一个可变参数(注意不是指向最后一个固定参数)。
宏va_arg可以返回argPtr当前指向的可变参数的值,同时修改argPtr,使其指向下一个可变参数。调用va_arg时还要指定当前参数的类型。
下面的语句将取得argPtr指向的可变参数(类型为double)放到变量val中,并将argPtr指向下一个可变参数的位置:
double val = va_arg(argPtr, double);
最后需要调用va_end使argPtr无效:
va_end(argPtr);
注意:宏va_start和宏va_end需要成对出现。
回到开头sum_n的例子上。要计算不确定个数的参数之和,函数sum_n有下面两种实现思路:
1. 将要相加的所有数直接放在参数中,最后使用一个特殊值来标记参数列表的结束,这个值被称为结束符。例如选择-1作为结束符,可以像下面这样调用函数sum_n:
sum_n(1, 2, 3, 5, 10, 28, 4, -1);
这种方法的问题在于要相加的数中不能有被选为结束符的那个数(例如-1),否则在函数sum_n遍历参数列表时,遇到第一个-1就认为参数列表结束了。
2. 首先传递一个数标明这次调用时一共有几个数需要相加,然后才是所有要想加的数。这样的好处是无需使用特殊的结束符。例如在计算1+2+4时,可以像下面这样调用
sum_n:
sum_n(3 /* 一共三个数 */, 1, 2, 4);
这里选择第二种方式来实现函数sum_n。
任意个数的参数相加的实现:
#include <stdio.h>
#include <stdarg.h>
/* 至少应该有一个数来相加,first 代表第一个要相加的数 */
void sum_n(int count, int first, ...)
{
inti;
intsum;
va_listargPtr;
/*检查 count 是否合理 */
if(count< 1)
{
printf("参数个数不合理!\n");
return;
}
/*初始化 sum */
sum= first;
/*初始化 argPtr */
va_start(argPtr,first);
/*由于第一个数已经在 sum 中了,所以 i 从 1 开始计数 */
for(i= 1; i < count; ++i)
{
intval = va_arg(argPtr, int);
sum+= val;
}
va_end(argPtr);
printf("%d个数的和为 %d。\n", count, sum);
}
intmain()
{
sum_n(1,1);
sum_n(2, 3, 4);
sum_n(6,200, 1, 2, 3, 4 ,5);
sum_n(0,1, 2, 3);
}
使用可变参数时需要注意:
使用可变参数的函数必须至少有一个固定参数,定义可变参数的函数时,固定参数必须位于可选参数之前;开发者需要自己确定可选参数的类型;开发者需要自己确定可选参数的数量(例如将可选参数的数量当作一个固定参数传到函数中)。
C语言规定至少要定义一个有名字的参数,因为va_start宏要用到参数列表中最后一个有名字的参数,从它的地址开始找可变参数的位置。实现者应该在文档中说明参数列表必须以NULL结尾,如果调用者不遵守这个约定,实现者是没有办法避免错误的。
另外va_arg(argp, type)宏中不支持的类型有:
- char、signed char、unsigned char
 - short、unsigned short
 - signed short、short int、signed short int、unsigned short int
 - float
 
va_arg宏的第2个参数不能被指定为char、short或者float类型。
因为char和short类型的参数会被转换为int类型,而float类型的参数会被转换为double类型 ……
例如,这样写肯定是错误的:
c = va_arg(ap,char);
因为我们无法传递一个char类型参数,如果传递了,它将会被自动转化为int类型。所以应该写成:
c = va_arg(ap,int);
C语言的本质(18)——函数的可变参数的更多相关文章
- php匿名函数和可变参数函数
		
php匿名函数和可变参数函数 简介 直接上代码了: <?php $test1 = function($value) { echo $value; }; $test1('HelloWorld'); ...
 - 【Java学习笔记】函数的可变参数
		
package p2; public class ParamterDemo { public static void main(String[] args) { int sum1 = add(4,5) ...
 - lua --- 函数的可变参数
		
主要掌握: 1>虚变量 --- 一个下划线 2>lua将函数的可变参数放在一个叫 arg 的表中,除了参数以外,arg表中还有一个域n表示参数的个数. do function fun(x, ...
 - Python学习之路:函数传递可变参数与不可变参数
		
函数传参的方法: 太基础了,8说了 直接上重点 一.可变参数的传递 可变参数有:列表.集合.字典 直接上代码: a = [1, 2] def fun(a): print('传入函数时a的值为:', a ...
 - 函数、可变参数、keyword-only参数、实参解构
		
函数的数学定义:y=f(x) ,y是x的函数,x是自变量.y=f(x0, x1, ..., xn) python中的函数: 由函数名称.参数列表.和若干语句组成的语句块构成,完成一定的功能,是组织代码 ...
 - js函数的可变参数
		
//对于js的可变参数的清空,在定义函数式不需要写上参数, 在函数内部使用argument对象可以 直接获取参数个数等信息 //在调用函数式可以传递任意个数的参数 function text(){ v ...
 - Scala函数使用可变参数
		
scala同java一样,在定义函数的时候支持接收可变长参数列表,即最后一个参数的可以被重复.示例代码如下: 结果: 在此代码中我们定义函数printInfo接收变长参数列表,其最后一个参数names ...
 - Python 函数(可变参数)
		
在python函数中,可以定义可变参数,顾名思义,可变参数就是,传入的参数是可变的例如,给定一组数字a,b,c... 请计算a2 + b2 + c2 + …… 要定义出这个函数,我们必须确定输入的参 ...
 - python 函数传递可变参数的用法
		
可变参数 在Python函数中,还可以定义可变参数.顾名思义,可变参数就是传入的参数个数是可变的,可以是1个.2个到任意个,还可以是0个. 我们以数学题为例子,给定一组数字a,b,c……,请计算a2 ...
 
随机推荐
- Eclipse Clojure 开发插件
			
参考:http://doc.ccw-ide.org/documentation.html#install-as-plugin 安装Eclipse Clojure插件 这里安装的插件是Countercl ...
 - Android  DatePickerDialog 只选择年月
			
//对EditText注册OnTouch事件etSscxNssbDate.setOnTouchListener(selectDateTouchListener); //选择日期 private OnT ...
 - Javascript 文件的同步加载与异步加载
			
HTML 4.01 的script属性 charset: 可选.指定src引入代码的字符集,大多数浏览器忽略该值.defer: boolean, 可选.延迟脚本执行,相当于将script标签放入页面b ...
 - HTTP -> Asp.net (第一篇)
			
当用户在浏览器输入一个URL地址后,浏览器会发送一个请求到服务器.这时候在服务器上第一个负责处理请求的是IIS.然后IIS再根据请求的URL扩展名将请求分发给不同的ISAPI处理. 流程如下: 1.I ...
 - Linux系统编程(20)——信号基本概念
			
信号及信号来源 信号是在软件层次上对中断机制的一种模拟,在原理上,一个进程收到一个信号与处理器收到一个中断请求可以说是一样的.信号是异步的,一个进程不必通过任何操作来等待信号的到达,事实上,进程也不知 ...
 - 剑指offer-面试题.二叉树的镜像
			
题目:请完成一个函数,输入一个二叉树,该函数输出它的镜像. 二叉树节点定义如下: strcut BinaryTreeNode { int val; strcut BinaryTreeNode* m_ ...
 - java  读取固定目录下的文件(和上篇差点儿相同)
			
package gao.org; import java.io.FileNotFoundException; import java.io.IOException; import java.io.Fi ...
 - 常用SQL Server分页方式
			
假设有表ARTICLE,字段ID.YEAR...(其他省略),数据53210条(客户真实数据,量不大),分页查询每页30条,查询第1500页(即第45001-45030条数据),字段ID聚集索引,YE ...
 - JavaScript sort()方法比较器
			
当我们想把一个由数字组成的数组进行简单的排序时,可能会想到sort()方法: var arr = [2 , 3, -1, -107, -14, 1]; console.log(arr.sort()) ...
 - Android SQLite的使用1(非原创)
			
1.继承SQLiteOpenHelper :public class MyOpenHelper extends SQLiteOpenHelper {} 2.重写下面3个方法 package com.e ...