概述
在C语言中,函数参数的传递方式有值传递和地址传递,值传递是把实参的一个专用的,临时的复制值给被调用函数中相应形参被调用函数使用,修改这个传来的复制值,不会影响实参的值。地址传递则是把变量(实参)的地址传给给被调用函数,被调用函数通过这个地址找到该变量的存放位置,直接对该地址中存放的变量的内容进行存取操作.因此,在被调用函数中可以修改实参的值.这也是函数参数址传的优点.无论是值传还是址传,都要求实参的数目及类型与形参要完全一致.在一般的程序设计语言中,函数参数的数目及类型是不可变的.即函数被设计之后,只能接收已固定个数和固定类型的实参.这样在编译时,函数形参的存储空间便于确定.但是在C语言中,不但参数的类型可变,参数的个数也是可变的.也就是说,在形参表中可以不明确指定传递参数的个数和类型,一个常见的库函数Printf() 就是如此.这种函数称之为可变长参数函数(变参函数).可变长参数函数的参数数目和类型虽然是可变,但其设计原理与固定参数函数的设计原理是一致的,必须有办法告诉变参函数没有指定的参数的个数和类型。在被调用函数中可以修改实参的值.这也是函数参数址传的优点.无论是值传还是址传,都要求实参的数目及类型与形参要完全一致.在一般的程序设计语言中,函数参数的数目及类型是不可变的.即函数被设计之后,只能接收已固定个数和固定类型的实参.这样在编译时,函数形参的存储空间便于确定.但是在C语言中,不但参数的类型可变,参数的个数也是可变的.也就是说,在形参表中可以不明确指定传递参数的个数和类型,一个常见的库函数Printf() 就是如此.这种函数称之为可变长参数函数(变参函数).
在标准文件stdarg.h中包含带参数的宏定义:
#define _ADDRESSOF(v) ( &reinterpret_cast<const char &>(v) )
<pre name="code" class="cpp" style="line-height: 19px; text-align: justify;">#define _INTSIZEOF(n)
( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )
#define _crt_va_start(ap,v)
( ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v) )
#define _crt_va_arg(ap,t)
( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
#define _crt_va_end(ap)
( ap = (va_list)0 )
注释:
1. _INTSIZEOF(n)整个做的事情就是将n的长度化为int长度的整数倍。比如n为5,二进制就是101b,int长度为4,二进制为100b,那么n化为int长度的整数倍就应该为8。~(sizeof(int) - 1) )就应该为~(4-1)=~(00000011b)=11111100b,这样任何数& ~(sizeof(int) - 1) )后最后两位肯定为0,就肯定是4的整数倍了。
2.可变长参数函数用规定格式定义为“类型函数名(firstfix,…,lastfix,…)”.firstfix,…,lastfix表示函数参数列表中的第一个和最后一个固定参数,该参数列表中至少要有一个固定参数,其作用是为了给变参函数确定列表中参数的个数和参数的类型.3.指针类型va_list用来说明一个变量ap(argument pointer——可变参数指针),此变量将依次引用可变参数列表中用省略号“…”代替的每一个参数.即指向将要操作的变参.4.宏va_start (ap,lastfix)是为了初始化变参指针ap,以指向可变参数列表中未命名的第一个参数,即指向lastfix后的第一个变参.它必须在指针使用之前调用一次该宏,参数列表中至少有一个未命名的可变参数.从宏定义可知其正确性.5.宏va_arg (ap,type)调用,将ap指向下一个可变参数,而ap的类型由type确定,type数据类型不使用float类型.调用后将新的变参可指向一个工作变参,如iap=va_start (ap,int)调用.6.宏va_end (ap)从stdarg.h中看出定义为空,即未定义.其功能完成清除变量ap的作用,表明程序以后不再使用,若该指针变量需再使用,必须重新调用宏va_start 以启动该变量.
7. 关于va_arg,
举个例子帮助理解:
int y=va_arg(x,int);宏展开成( *(int *)((x += _INTSIZEOF(int)) - _INTSIZEOF(int)) )此时x指向下一个参数(x = x + _INTSIZEOF(int))然后x再减去_INTSIZEOF(int)得到x未改变前的地址,再将x所指向的int类型的值赋给y
使用可变参数的下步骤:首先在函数里定义一个 va_list 型的变量,这里是 arg_ptr,这个变量是指向参数的指针.然后用 va_start 宏初始化变量 arg_ptr,这个宏的第二个参数是第一个可变参数的前一个参数,是一个固定的参数.然后用 va_arg 返回可变的参数,并赋值给整数 j. va_arg的第二个 参数是你要返回的参数 的类型,这里是 int 型.最后用 va_end 宏结束可变参数的获取.然后你就可以在函数里使用第二个参数了.如果函数有多个可变参数的,依次调用 va_arg 获取各个参数
定义_INTSIZEOF(n)主要是为了某些需要内存的对齐的系统.C语言的函数是从右向左压入堆栈的,图(1)是函数的参数在堆栈中的分布位置.我们看到va_list被定义成char*,有一些平台或操作系统定义为void*.再看va_start的定义,定义为&v+_INTSIZEOF(v),而&v是固定参数在堆栈的地址,所以我们运行va_start(ap, v)以后,ap指向第一个可变参数在堆栈的地址,如图: 高地址|-----------------------------| |函数返回地址 | |-----------------------------| |. | |-----------------------------| |第n个参数(第一个可变参数) | |-----------------------------|<--va_start后ap指向 |第n-1个参数(最后一个固定参数)| 低地址|-----------------------------|<-- &v 图( 1 ) 然后,我们用va_arg()取得类型t的可变参数值,以上例为int型为例,我们看一下va_arg取int型的返回值: j= ( *(int*)((ap += _INTSIZEOF(int))-_INTSIZEOF(int)) ); 首先ap+=sizeof(int),已经指向下一个参数的地址了.然后返回ap-sizeof(int)的int*指针,这正是第一个可变参数在堆栈里的地址(图2).然后用*取得这个地址的内容(参数值)赋给j. 高地址|-----------------------------| |函数返回地址 | |-----------------------------| |. | |-----------------------------|<--va_arg后ap指向 |第n个参数(第一个可变参数) | |-----------------------------|<--va_start后ap指向 |第n-1个参数(最后一个固定参数)| 低地址|-----------------------------|<-- &v 图( 2 ) 最后要说的是va_end宏的意思,x86平台定义为ap=(char*)0;使ap不再指向堆栈,而是跟NULL一样.有些直接定义为((void*)0),这样编译器不会为va_end产生代码,例如gcc在linux的x86平台就是这样定义的.在这里大家要注意一个问题:由于参数的地址用于va_start宏,所以参数不能声明为寄存器变量或作为函数或数组类型.关于va_start, va_arg, va_end的描述就是这些了,我们要注意的是不同的操作系统和硬件平台的定义有些不同,但原理却是相似的.
示例代码如下:
1 #include <stdarg.h>
2 #include <stdarg.h>
3 #include <stdio.h>
4 #include <iostream>
5 using namespace std;
6
int sum( int, ... );
7
8
int main( void ) {
9
10
11
12
int answer = sum( 4, 4, 3, 2, 1 );
13
14
printf( "The answer is %dn", answer );
15
16
17
18
return( 0 );
19
20
}
21
22
23
24
int sum( int num, ... ) {
25
26
int answer = 0;
27
28
va_list argptr;
29
30
31
32
va_start( argptr, num );
33
34
for( ; num > 0; num-- )
35
{
36
int test = va_arg( argptr, int );
37
answer += test;
38
printf("**********************%d !n", test);
39
}
40
41
va_end( argptr );
42
43
return( answer );
44
45
}
输出:
spring@spring-plum:~/XiaoPing$ ./argtest
**********************4 !
**********************3 !
**********************2 !
**********************1 !
The answer is 10
最后
以上就是活力乌冬面为你收集整理的我理解的C语言变参函数(va_arg,va_start,va_end)的全部内容,希望文章能够帮你解决我理解的C语言变参函数(va_arg,va_start,va_end)所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
发表评论 取消回复