
使用过程如下:
在使用此类型的函数定义时需要熟悉va_start、va_arg和va_end等宏的使用。
在传入的参数类型后边加上即声明了该参数是可变长度的,而我们在取参的时候只需要将其当成数组去处理,这似乎比oc省心了不少。
/
printfc - print formatted
Copyright (c) 1985-1997, Microsoft Corporation All rights reserved
Purpose:
defines printf() - print formatted data
/
#include
#include
#include
#include
#include
#include
#include
/
int printf(format, ) - print formatted data
Purpose:
Prints formatted data on stdout using the format string to
format data and getting as many arguments as called for
Uses temporary buffering to improve efficiency
_output does the real work here
Entry:
char format - format string to control data format/number of arguments
followed by list of arguments, number and type controlled by
format string
Exit:
returns number of characters printed
Exceptions:
/
int __cdecl printf (
const char format,
)
/
stdout ''PRINT'', ''F''ormatted
/
{
va_list arglist;
int buffing;
int retval;
va_start(arglist, format);
_ASSERTE(format != NULL);//断言宏。如果输出格式字符串指针为空,则在DEBUG版下断言,报告错误。
_lock_str2(1, stdout);
buffing = _stbuf(stdout);//stdout:指定输出到屏幕
retval = _output(stdout,format,arglist);
_ftbuf(buffing, stdout);
_unlock_str2(1, stdout);
return(retval);
}
以上为printf()的源代码
1、从含有可选参数函数中获得可选参数,以及 *** 作这些参数
typedef char va_list;
void va_start( va_list arg_ptr, prev_param );
type va_arg( va_list arg_ptr, type );
void va_end( va_list arg_ptr );
假定函数含有一个必选参数和多个可选参数,必选参数声明为普通数据类型,且能通过参数名来获得该变量的值。可选参数通过宏va_start、va_arg和va_end(定义在stdargh或varargsh中)来进行 *** 作,即通过设置指向第一个可选参数指针、返回当前参数、在返回参数后重新设置指针来 *** 作所有的可选参数。
va_start:为获取可变数目参数的函数的参数提供一种便捷手段。设置arg_ptr为指向传给函数参数列表中的第一个可选参数的指针,且该参数必须是va_list类型。prev_param是在参数列表中第一个可选参数前的必选参数。
va_arg:返回由arg_ptr所指向的参数的值,且自增指向下一个参数的地址。type为当前参数的类型,用来计算该参数的长度,确定下一个参数的起始位置。它可以在函数中应用多次,直到得到函数的所有参数为止,但必须在宏va_start后面调用。
va_end:在获取所有的参数后,设置指针arg_ptr为NULL。
下面举例说明:
#include
#include
int average( int first, );
void main( void )
{
/ Call with 3 integers (-1 is used as terminator) /
printf( "Average is: %d\n", average( 2, 3, 4, -1 ) );
/ Call with 4 integers /
printf( "Average is: %d\n", average( 5, 7, 9, 11, -1 ) );
/ Call with just -1 terminator /
printf( "Average is: %d\n", average( -1 ) );
}
int average( int first, )
{
int count = 0, sum = 0, i = first;
va_list marker;
va_start( marker, first ); / Initialize variable arguments /
while( i != -1 )
{
sum += i;
count++;
i = va_arg( marker, int);
}
va_end( marker ); / Reset variable arguments /
return( sum (sum / count) : 0 );
}
返回值为:
Average is: 3
Average is: 8
Average is: 0
综上所述,在printf()函数中,可以只输出一个字符串,也可按照一定的形式输出含有多个可选参数的字符串信息。因此,首先就要通过这些宏来获取所有的可选参数。在上面的源码可以看出printf()中,只使用了宏at_start,将可选参数的首地址赋给了arglist。
2、锁定字符串及输出字符串到屏幕
#define _lock_str2(i,s) _lock_file2(i,s)
void __cdecl _lock_file2(int, void );
#define _unlock_str2(i,s) _unlock_file2(i,s)
void __cdecl _unlock_file2(int, void );
int __cdecl _stbuf(FILE );
void __cdecl _ftbuf(int, FILE );
int __cdecl _output(FILE , const char , va_list);
在output函数中,读取格式字符串中的每一个字符,然后对其进行处理,处理方式根据每一个字符所代表的意义来进行,如:普通字符直接利用函数WRITE_CHAR(ch, &charsout);输出到控制台。
其中的主要部分是对转换说明符(d,c,s,f)的处理,现在将对其中的部分代码进行详细说明,这里只说明最基本的转换说明符,对这些须基本的转换说明符进行修饰的修饰符,程序中单独进行处理。下面是函数output()(outputc)部分源代码:
case ST_TYPE:
//表示当前处理的字符的类型为转换说明符。
switch (ch) {
//下面对参数的获取都是利用宏va_arg( va_list arg_ptr, type );来进行的。
case ''c'': {
//从参数表中获取单个字符,输出到缓冲字符串中,此时,type=int
buffer[0] = (char) get_int_arg(&argptr); / get char to print /
text = buffer;
textlen = 1; / print just a single character /
}
break;
case ''s'': {
//从参数表中获取字符串,输出到缓冲字符串中,此时,type=char
int i;
char p; / temps /
text = get_ptr_arg(&argptr);
}
break;
case ''w'': {
//对宽字符进行处理
} / case ''w'' /
break;
case ''e'':
case ''f'':
case ''g'': {
//对浮点数进行 *** 作
#if !LONGDOUBLE_IS_DOUBLE
/ do the conversion /
if (flags & FL_LONGDOUBLE) {
_cldcvt((LONGDOUBLE)argptr, text, ch, precision, capexp);
va_arg(argptr, LONGDOUBLE);
//对长双精度型进行处理,此时,type=long double
}
else
#endif / !LONGDOUBLE_IS_DOUBLE /
{
//对双精度型进行处理,此时,type=double
_cfltcvt((DOUBLE)argptr, text, ch, precision, capexp);
va_arg(argptr, DOUBLE);
}
break;
//对整型变量处理
case ''d'':
case ''i'':
goto COMMON_INT;
case ''u'':
radix = 10;
goto COMMON_INT;
case ''p'':
goto COMMON_INT;
case ''o'':
注:对于浮点型double和long double,有相应的转换说明符(%f表示双精度型,%lf表示长双精度型),而float却没有。其中的原因是,在K&RC下,float值用于表达式或用作参数前,会自动转换成double类型。而ANSI C一般不会自动把float转换成double。有些程序已假定其中的float参数会被转换成double,为了保护大量这样的程序,所有printf()函数的float参数还是被自动转换成double型。因此,在K&RC或ANSI C下,都无需用特定的转换说明符来显示float型。
综上所述,转换说明符必须与待打印字符的类型。通常,用户有种选择。例如,如要打印一个int类型的值。则只可以使用%d,%x或%o。所有这些说明符都表示要打印一个int类型的值;它们只不过提供了一个数值的几种不同表示。类似一,可以用%f、%g和%e来表示double类型的值。但如果转换说明与类型不匹配,将会出现意想不到的结果。为什么呢?问题就在于C向函数传递信息的方式。
这个失败的根本细节与具体实现相关。它决定了系统中的参数以何方式传递。函数调用如下:
float n1;
double n2;
long n3;
long n4;
printf("%ld,%ld,%ld,%ld",n1,n2,n3,n4);
这个调用告诉计算机,要把变量n1,n2,n3和n4的值交给计算机,它把这些变量放进称作栈(stack)的内存区域中,来完成这一任务。计算机把这些值放进栈中,其根据是变量的类型而不是转换说明符,比如n1,把8个字节放入栈中(float被转换成double),类似地,为n2放了8字节,其后给n3和n4各放了4个字节。接着,控制的对象转移到printf();此函数从栈中读数,不过在这一过程中,它是在转换说明符的指导下,读取数值的。说明符%ld指定printf()应读4个字节(va_arg( va_list arg_ptr, type )中type=long),因此printf()读入栈中的4个字节,作为它的第一个值。但是这只是n1的前半部分,这个值被看成一个long整数。下一个说明符%ld读入4个字节,这正是n1的后半部分,这个值被看成第二个long整数。类似地,第三、第四次又读入n2的前后两部分。因此,尽管我们对n3和n4使用了正确的说明符,printf()仍然会产生错误。
我把你的提问分为3个问题:
1、为什么printf("%s", ap);输出不了?
2、va_start(ap, v)的定义中为什么使用二级指针?
3、va_arg(ap,t) 的定义中为什么用(t ),它的作用是?
在解释之前,先确认一个小问题:
在C语言中,指针这种类型的大小实际上一样的,我的意思是说无论是char a,还是int a,或者是char a,a这个指针变量所占用的内存空间是一样的(都是sizeof(a),究竟是等于4,还是8取决于CPU的位数)
先回答第一个问题:
你应该知道va_list的定义:typedef char va_list;
也就是说ap可以理解为一个char 类型的变量,va_start(ap,c)这个执行之后,ap确实指向了可变参数列表中的第一个参数,注意是ap这个指针指向了第一个参数,而如果你的第一个参数是一个字符串(C语言中也就意味着是一个char的变量),这样的话,ap这个指针就指向了一个char类型的指针变量,指向指针的指针变量是二级指针变量这个我就不用多说了吧,所以printf("%s", ap);是无法输出的,而修改为printf("%s", (char )ap);应该就可以输出了!
然后是第二个问题:
这里先说一下函数调用过程中参数传递的问题:
函数参数是以数据结构:栈的形式存取,从右至左入栈。
首先是参数的内存存放格式:参数存放在内存的堆栈段中,在执行函数的时候,从最后一个开始入栈。因此栈底高地址,栈顶低地址,举个例子如下:
void func(int x, char y, char z);
那么,调用函数的时候,实参 char z 先进栈,然后是 char y,最后是 int x,因此在内存中变量的存放次序是 x->y->z,因此,从理论上说,我们只要探测到任意一个变量的地址,并且知道其他变量的类型,通过指针移位运算,则总可以顺藤摸瓜找到其他的输入变量。
注意,x,y,z这几个变量是存放到堆栈中的,所以我们需要获得的不是y这个变量本身,而是它在堆栈中的地址,而ap这个指针变量就是保存着堆栈中函数入参的地址的,所以在va_start(ap, v)的定义中要使用&v,而不管v变量本身是什么类型的(哪怕v是一个指针变量,甚至是二级指针)&v都表示一个地址,所以可以强制转换为va_list类型(也就是char )。
第三个问题:
要睡觉了,先自己想吧,如果还不明白,就留言追问吧。
va_list ap; //ap是传入的参数的指针,访问传入的可变参数都要通过它进行。
va_start(ap,b); //初始化参数指针,b就传入函数的最后一个强制性参数
temp=va_arg(ap,int); //va_arg()宏通过ap指针从可选参数列表中提取下一个值,并转换为int类型返回到temp变量
va_end(ap); //不再使用可选参数,做一些清理工作。
这种函数需要固定数量的 强制参数(mandatory argument) ,至少有一个;后面是数量可变的 可选参数(optional argument) ,可选参数的数量由强制参数的值决定,或由用来定义可选参数列表的特殊值决定。
对于每一个强制参数来说,函数头部都会显示一个适当的参数,像普通函数声明一样。参数列表的格式是强制性参数在前,后面跟着一个逗号和省略号,这个省略号代表可选参数。
可变参数函数要获取可选参数时,必须通过一个 类型为 va_list 的对象,这种类型的对象也称为 参数指针(argument pointer) 。它包含了栈中至少一个参数的位置。可以使用这个参数指针从一个可选参数移动到下一个可选参数,由此,函数就可以获取所有的可选参数。va_list 类型被定义在头文件 stdargh 中。
当编写支持参数数量可变的函数时,必须用 va_list 类型定义参数指针,以获取可选参数。在下面的讨论中,va_list 对象被命名为 argptr。可以用 4 个宏来处理该参数指针,这些宏都定义在头文件 stdargh 中:
void va_start(va_list argptr, lastparam);
使用第一个可选参数的位置来初始化 argptr 参数指针。该宏的第二个参数必须是该函数最后一个有名称参数的名称。必须先调用该宏,才可以开始使用可选参数。
type va_arg(va_list argptr, type);
展开宏 va_arg 会得到当前 argptr 所引用的可选参数,也会将 argptr 移动到列表中的下一个参数。宏 va_arg 的第二个参数是刚刚被读入的参数的类型。
void va_end(va_list argptr);
当不再需要使用参数指针时,必须调用宏 va_end。如果想使用宏 va_start 或者宏 va_copy 来重新初始化一个之前用过的参数指针,也必须先调用宏 va_end。
void va_copy(va_list dest, va_list src);
宏 va_copy 使用当前的 src 值来初始化参数指针 dest。然后就可以使用 dest 中的备份获取可选参数列表,从 src 所引用的位置开始。
例1:
例2:
参考博客:
>
以上就是关于swift中函数可变参数的使用全部的内容,包括:swift中函数可变参数的使用、C语言库函数如何编写、C语言中可变参数宏的va_start(ap, v)等相关内容解答,如果想了解更多相关内容,可以关注我们,你们的支持是我们更新的动力!
欢迎分享,转载请注明来源:内存溢出
微信扫一扫
支付宝扫一扫
评论列表(0条)