奇特的可变参数列表实现print函数

来源:互联网 发布:iphone抢购软件 编辑:程序博客网 时间:2024/04/30 10:21
<div style="word-wrap: break-word; color: rgb(102, 102, 102); font-family: 宋体, Arial; font-size: 16px; line-height: 26px;"><span style="font-size:18px; font-family: georgia, times, serif; line-height: 20.8px; color: rgb(0, 0, 255);">     熟悉C的人都知道,C语言支持</span><span style="font-size:18px; font-family: georgia, times, serif; line-height: 20.8px; margin: 0px; padding: 0px; border: 0px; border-image-source: initial; border-image-slice: initial; border-image-width: initial; border-image-outset: initial; border-image-repeat: initial; outline: 0px; vertical-align: baseline; color: rgb(255, 0, 0); background-image: initial; background-attachment: initial; background-size: initial; background-origin: initial; background-clip: initial; background-position: initial; background-repeat: initial;">可变参数函数(Variable Argument Functions)</span><span style="font-size:18px; font-family: georgia, times, serif; line-height: 20.8px; color: rgb(0, 0, 255);">,即参数的个数可以是不定个,在函数定义的时候用(...)表示,比如我们常用的printf()\execl函数等;printf函数的原型如下:</span></div><div style="word-wrap: break-word; color: rgb(102, 102, 102); font-family: 宋体, Arial; line-height: 26px;"><span style="font-family: 新宋体; color: rgb(0, 0, 255); word-wrap: break-word;"></span><div class="wp_syntax" style="margin: 0px 0px 1.5em; padding: 0px; border: 1px solid silver; border-image-source: initial; border-image-slice: initial; border-image-width: initial; border-image-outset: initial; border-image-repeat: initial; outline: 0px; vertical-align: baseline; color: rgb(17, 0, 0); overflow-x: auto; overflow-y: hidden; width: 578.156px; font-family: georgia, times, serif; line-height: 20.8px; background-image: initial; background-attachment: initial; background-color: rgb(249, 249, 249); background-size: initial; background-origin: initial; background-clip: initial; background-position: initial; background-repeat: initial;"><table style="outline: 0px; vertical-align: baseline; border-spacing: 0px; width: 578px; margin: 0px !important; padding: 0px !important; border: none !important; border-collapse: collapse !important; background: transparent;"><tbody style="margin: 0px; padding: 0px; border: 0px; outline: 0px; vertical-align: baseline; background: transparent;"><tr style="margin: 0px; padding: 0px; border: 0px; outline: 0px; vertical-align: baseline; background: transparent;"><td class="code" style="margin: 0px; outline: 0px; width: 578px; padding: 0px !important; border: none !important; vertical-align: top !important; background: linear-gradient(transparent 50%, rgba(255, 255, 255, 0.901961) 50%) rgb(238, 238, 238);"><pre class="c" style="outline: 0px; vertical-align: baseline; margin-top: 0px !important; margin-bottom: 0px !important; padding: 0px 4px !important; border: none !important; width: auto !important; float: none !important; clear: none !important; overflow: visible !important; line-height: 16px !important; box-shadow: rgba(0, 0, 0, 0) 0px 0px 0px !important; border-radius: 0px !important; background: transparent !important;"><span style="font-size:18px;"><span style="margin: 0px; padding: 0px; border: 0px; outline: 0px; vertical-align: baseline; color: rgb(153, 51, 51); background: transparent;">int</span> <span style="margin: 0px; padding: 0px; border: 0px; outline: 0px; vertical-align: baseline; color: rgb(0, 0, 102); background: transparent;">printf</span><span style="margin: 0px; padding: 0px; border: 0px; outline: 0px; vertical-align: baseline; color: rgb(0, 153, 0); background: transparent;">(</span><span style="margin: 0px; padding: 0px; border: 0px; outline: 0px; vertical-align: baseline; color: rgb(153, 51, 51); background: transparent;">const</span> <span style="margin: 0px; padding: 0px; border: 0px; outline: 0px; vertical-align: baseline; color: rgb(153, 51, 51); background: transparent;">char</span> <span style="margin: 0px; padding: 0px; border: 0px; outline: 0px; vertical-align: baseline; color: rgb(51, 153, 51); background: transparent;">*</span>format<span style="margin: 0px; padding: 0px; border: 0px; outline: 0px; vertical-align: baseline; color: rgb(51, 153, 51); background: transparent;">,</span> ...<span style="margin: 0px; padding: 0px; border: 0px; outline: 0px; vertical-align: baseline; color: rgb(0, 153, 0); background: transparent;">)</span><span style="margin: 0px; padding: 0px; border: 0px; outline: 0px; vertical-align: baseline; color: rgb(51, 153, 51); background: transparent;">;</span></span>

注意,采用这种形式定义的可变参数函数,至少需要一个普通的形参,比如上面代码中的*format,后面的省略号是函数原型的一部分。

C语言之所以可以支持可变参数函数,一个重要的原因是C调用规范中规定C语言函数调用时,参数是从右向左压入栈的;这样一个函数实现的时候,就无需关心调用他的函数会传递几个参数过来,而只要关心自己用到几个;以printf为例:

printf("%d%s\n",i,s);

printf函数在定义的时候,不知道函数调用的时候会传递几个参数。在实现上,printf函数只需关心第一个参数,即字符串“%d%s\n”,当读到%d的时候,printf知道自己需要第二个参数,这时只需要去栈上寻找即可;当读到%s时,再去栈上网上寻找一个参数即可。简单说,printf不关心栈上到底压了多少参数,只关心自己需要多少。

那么对于一个定义为可变参数的函数,函数定义的时候并没有定义形参原型,怎么使用参数呢?

C语言定义了一系列宏来完成可变参数函数参数的读取和使用:宏va_start、va_arg和va_end;在ANSI C标准下,这些宏定义在stdarg.h中。三个宏的原型如下:

void va_start(va_list ap, last);//取第一个可变参数(如上述printf中的i)的指针给ap,last是函数声明中的最后一个固定参数(比如printf函数原型中的*fromat);type va_arg(va_list ap, type);//返回当前ap指向的可变参数的值,然后ap指向下一个可变参数;type表示当前可变参数的类型(支持的类型位int和double);void va_end(va_list ap);//将ap置为NULL

当一个函数被定义位可变参数函数时,其函数体内首先要定义一个va_list的结构体类型,这里沿用原型中的名字,ap。

va_start使ap指向第一个可选参数。va_arg返回参数列表中的当前参数并使ap指向参数列表中的下一个参数。va_end把ap指针清为NULL。函数体内可以多次遍历这些参数,但是都必须以va_start开始,并以va_end结尾。

<div style="word-wrap: break-word; color: rgb(102, 102, 102); font-family: 宋体, Arial; font-size: 16px; line-height: 26px;"><span style="font-family: 新宋体; word-wrap: break-word;">    现在呢,我们先来看几个宏:va_list、va_start、va_arg及va_end(va的意思应该是variable)</span></div><div style="word-wrap: break-word; color: rgb(102, 102, 102); font-family: 宋体, Arial; font-size: 16px; line-height: 26px;"><span style="font-family: 新宋体; word-wrap: break-word;"></span></div><div style="word-wrap: break-word; color: rgb(102, 102, 102); font-family: 宋体, Arial; font-size: 16px; line-height: 26px;"><span style="font-family: 新宋体; word-wrap: break-word;">1、<span style="color: rgb(0, 0, 0); word-wrap: break-word;">va_list</span></span></div><div style="word-wrap: break-word; color: rgb(102, 102, 102); font-family: 宋体, Arial; font-size: 16px; line-height: 26px;"><span style="font-family: 新宋体; word-wrap: break-word;">    <span style="color: rgb(255, 0, 0); word-wrap: break-word;">va_list表示可变参数列表类型</span>,实际上就是一个char指针</span></div><div style="word-wrap: break-word; color: rgb(102, 102, 102); font-family: 宋体, Arial; font-size: 16px; line-height: 26px;"><span style="font-family: 新宋体; word-wrap: break-word;">2、<span style="color: rgb(0, 0, 0); word-wrap: break-word;">va_start</span></span></div><div style="word-wrap: break-word; color: rgb(102, 102, 102); font-family: 宋体, Arial; font-size: 16px; line-height: 26px;"><span style="font-family: 新宋体; word-wrap: break-word;">    <span style="color: rgb(255, 0, 0); word-wrap: break-word;">va_start</span></span><span style="color: rgb(255, 0, 0); word-wrap: break-word;">用于获取函数参数列表中可变参数的首指针(<span style="font-family: 新宋体; word-wrap: break-word;">获取函数可变参数列表</span>)<br style="word-wrap: break-word;" /></span>  * 输出参数ap(类型为<span style="font-family: 新宋体; word-wrap: break-word;">va_list</span>): 用于保存函数参数列表中可变参数的首指针(即,可变参数列表)<br style="word-wrap: break-word;" />  * 输入参数A: 为函数参数列表中最后一个固定参数</div><div style="word-wrap: break-word; color: rgb(102, 102, 102); font-family: 宋体, Arial; font-size: 16px; line-height: 26px;"><span style="font-family: 新宋体; word-wrap: break-word;">3、<span style="color: rgb(0, 0, 0); word-wrap: break-word;">va_arg</span></span></div><div style="word-wrap: break-word; color: rgb(102, 102, 102); font-family: 宋体, Arial; font-size: 16px; line-height: 26px;"><span style="font-family: 新宋体; word-wrap: break-word;">    <span style="color: rgb(255, 0, 0); word-wrap: break-word;">va_arg</span></span><span style="color: rgb(255, 0, 0); word-wrap: break-word;">用于获取当前ap所指的可变参数并将并将ap指针移向下一可变参数</span><br style="word-wrap: break-word;" />  * 输入参数ap(类型为<span style="font-family: 新宋体; word-wrap: break-word;">va_list</span>): 可变参数列表,指向当前正要处理的可变参数<br style="word-wrap: break-word;" />  * 输入参数T: 正要处理的可变参数的类型<br style="word-wrap: break-word;" />  * 返回值: 当前可变参数的值</div><div style="word-wrap: break-word; color: rgb(102, 102, 102); font-family: 宋体, Arial; font-size: 16px; line-height: 26px;"> </div><div style="word-wrap: break-word; color: rgb(102, 102, 102); font-family: 宋体, Arial; font-size: 16px; line-height: 26px;"><span style="color: rgb(0, 128, 255); word-wrap: break-word;"><span style="font-family: 新宋体; word-wrap: break-word;">    </span>在C/C++中,默认调用方式_cdecl是由调用者管理参数入栈操作,且入栈顺序为从右至左,入栈方向为从高地址到低地址。因此,第1个到第n个参数被放在地址递增的堆栈里。所以,函数参数列表中最后一个固定参数的地址加上第一个可变参数对其的偏移量就是函数的可变参数列表了(<span style="font-family: 新宋体; word-wrap: break-word;">va_start的实现</span>);当前可变参数的地址加上下一可变参数对其的偏移量的就是下一可变参数的地址了(<span style="font-family: 新宋体; word-wrap: break-word;">va_arg的实现</span>)。这里提到的偏移量并不一定等于参数所占的字节数,而是为参数所占的字节数再扩展为机器字长(<span style="font-family: 新宋体; word-wrap: break-word;">acpi_native_int</span>)倍数后所占的字节数(因为入栈操作针对的是一个机器字),这也就是为什么<span style="font-family: 新宋体; word-wrap: break-word;">_bnd那么定义的原因。</span></span></div><div style="word-wrap: break-word; color: rgb(102, 102, 102); font-family: 宋体, Arial; font-size: 16px; line-height: 26px;"> </div><div style="word-wrap: break-word; color: rgb(102, 102, 102); font-family: 宋体, Arial; font-size: 16px; line-height: 26px;">4、va_end</div><div style="word-wrap: break-word; color: rgb(102, 102, 102); font-family: 宋体, Arial; font-size: 16px; line-height: 26px;"><span style="font-family: 新宋体; word-wrap: break-word;">    </span><span style="color: rgb(255, 0, 0); word-wrap: break-word;">va_end用于结束对可变参数的处理。</span>实际上,va_end被定义为空.它只是为实现与va_start配对(实现代码对称和"代码自注释"功能)</div><div style="word-wrap: break-word; color: rgb(102, 102, 102); font-family: 宋体, Arial; font-size: 16px; line-height: 26px;"> </div><div style="word-wrap: break-word; color: rgb(102, 102, 102); font-family: 宋体, Arial; font-size: 16px; line-height: 26px;"><span style="color: rgb(0, 0, 255); word-wrap: break-word;"><span style="font-family: 新宋体; color: rgb(0, 0, 0); word-wrap: break-word;">    </span>对可变参数列表的处理过程一般为:</span></div><div style="word-wrap: break-word; color: rgb(102, 102, 102); font-family: 宋体, Arial; font-size: 16px; line-height: 26px;"><span style="color: rgb(0, 0, 255); word-wrap: break-word;">1、用<span style="font-family: 新宋体; word-wrap: break-word;">va_list定义一个可变参数列表</span></span></div><div style="word-wrap: break-word; color: rgb(102, 102, 102); font-family: 宋体, Arial; font-size: 16px; line-height: 26px;"><span style="font-family: 新宋体; color: rgb(0, 0, 255); word-wrap: break-word;">2、用va_start获取函数可变参数列表</span></div><div style="word-wrap: break-word; color: rgb(102, 102, 102); font-family: 宋体, Arial; font-size: 16px; line-height: 26px;"><span style="font-family: 新宋体; color: rgb(0, 0, 255); word-wrap: break-word;">3、用va_arg循环处理可变参数列表中的各个可变参数</span></div><div style="word-wrap: break-word; color: rgb(102, 102, 102); font-family: 宋体, Arial; font-size: 16px; line-height: 26px;"><span style="font-family: 新宋体; color: rgb(0, 0, 255); word-wrap: break-word;">4、用va_end结束对可变参数列表的处理</span></div>


下面是一个使用可变参数列表实现printf函数的例子:

<span style="font-size:24px;">#include<stdio.h>#include<stdarg.h>void print(const char* format,...){va_list arg;va_start(arg,format);while(*format){switch(*format){case 'c':{char tmp = va_arg(arg,char);putchar(tmp);}break;case 's':{char *str = va_arg(arg,char*);while(*str){putchar(*str);str++;}/*puts(str);*///此处不可以用puts直接输出字符串,因为puts是输出一整行的内容}break;default:putchar(*format);break;}format++;}}int main(){print("s s cc.\n","hello",'b','i','t');system("pause");return 0;}</span>
<span style="font-size:24px;"></span>

0 0
原创粉丝点击