有很多同学对我们经常使用的函数printf和scanf都不以为然,其实这两个函数在c语言当中还是比较典型的。一般我们编程的时候,函数中形式参数的数目通常是确定的,在调用时要依次给出与形式参数对应的实际参数。但在某些情况下我们希望函数的参数个数可以根据需要确定,因此c语言引入可变参数函数。典型的可变参数函数的例子有printf()、scanf()。
下面我们就说一下这些函数的实现。
这些不定参数函数在c语言中是通过堆栈的方式实现的,这样看来理论上好像参数的个数是没有限制的实际上由于内存或者栈的大小的限制,实际上参数的个数是有上限的,只是这个上限我们一般情况下不会达到。如果达到了也会出现错误。
在c语言中,不定参函数的具体实现主要通过c语言定义的几个函数宏来完成,具体内部空间如何分配,如何调用均在其中,这也为我们自己完成一些不定餐函数提供了便利条件。下面介绍下这些函数宏。
VA_LIST 是在C语言中解决变参问题的一组宏,所在头文件:#include <stdarg.h>
INTSIZEOF 宏,获取类型占用的空间长度,小占用长度为int的整数倍:
#define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )
VA_START宏,获取可变参数列表的第一个参数的地址(ap是类型为va_list的指针,v是可变参数左边的参数):
#define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) )
VA_ARG宏,获取可变参数的当前参数,返回指定类型并将指针指向下一参数(t参数描述了当前参数的类型):
#define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
VA_END宏,清空va_list可变参数列表:
#define va_end(ap) ( ap = (va_list)0 )
具体用法:
(1)首先在函数里定义一具VA_LIST型的变量,这个变量是指向参数的指针;
(2)然后用VA_START宏初始化刚定义的VA_LIST变量;
(3)然后用VA_ARG返回可变的参数,VA_ARG的第二个参数是你要返回的参数的类型(如果函数有多个可变参数的,依次调用VA_ARG获取各个参数);
(4)后用VA_END宏结束可变参数的获取。
Ok下面举个例子来说明下:
void arg_test(int i, ...);
int main(int argc,char *argv[])
{
int int_size = _INTSIZEOF(int);
printf("int_size=%d\n", int_size);
arg_test(0, 4);
//arg_cnt(4,1,2,3,4);
return 0;
}
void arg_test(int i, ...)
{
int j=0;
va_list arg_ptr;
va_start(arg_ptr, i);
printf("&i = %p\n", &i);//打印参数i在堆栈中的地址
printf("arg_ptr = %p\n", arg_ptr);//打印va_start之后arg_ptr地址
/*这时arg_ptr应该比参数i的地址高sizeof(int)个字节,即指向下一个参数的地址*/
j=*((int *)arg_ptr);
printf("%d %d\n", i, j);
j=va_arg(arg_ptr, int);
printf("arg_ptr = %p\n", arg_ptr);//打印va_arg后arg_ptr的地址
/*这时arg_ptr应该比参数i的地址高sizeof(int)个字节,即指向下一个参数的地址,如果已经是后一个参数,arg_ptr会为NULL*/
va_end(arg_ptr);
printf("%d %d\n", i, j);
}
说明:
int int_size = _INTSIZEOF(int);得到int类型所占字节数
va_start(arg_ptr, i); 得到第一个可变参数地址
根据定义(va_list)&v得到起始参数的地址, 再加上_INTSIZEOF(v) ,就是其实参数下一个参数的地址,即第一个可变参数地址。
j=va_arg(arg_ptr, int); 得到第一个可变参数的值,并且arg_ptr指针上移一个_INTSIZEOF(int),即指向下一个可变参数的地址。
va_end(arg_ptr);置空arg_ptr,即arg_ptr=(void *)0;
在这里还要说一下,不定参数在c语言中的函数原型写法参数部分是“...”三个点。
后附上一个我们自己写的简单的printf的代码:
#include <stdarg.h>
void my_printf(const char* fmt, ... )
{
va_list ap;
va_start(ap,fmt); /* 用后一个具有参数的类型的参数去初始化ap */
for (;*fmt;++fmt)
{
/* 如果不是控制字符 */
if (*fmt!='%')
{
putchar(*fmt); /* 直接输出 */
continue;
}
/* 如果是控制字符,查看下一字符 */
++fmt;
if ('\0'==*fmt) /* 如果是结束符 */
{
assert(0); /* 这是一个错误 */
break;
}
switch (*fmt)
{
case '%': /* 连续2个'%'输出1个'%' */
putchar('%');
break;
case 'd': /* 按照int输出 */
{
/* 下一个参数是int,取出 */
int i = va_arg(ap,int);
printf("%d",i);
}
break;
case 'c': /* 按照字符输出 */
{
/** 但是,下一个参数是char吗*/
/* 可以这样取出吗? */
char c = va_arg(ap,char);
printf("%c",c);
}
break;
}
}
}
va_end(ap); /* 释放ap—— 必须! 见相关链接*/
}
看完后,你是不是自己也想试一下实现一个不定参函数呢。
Copyright © 2004-2024 华清远见教育科技集团 版权所有
京ICP备16055225号-5,京公海网安备11010802025203号