C之诡谲(指针,数组,类型的识别,参数可变的函数)
时间:2006-07-07 17:02:10 来源:本站搜集整理 作者:Eric
四.参数可变的函数 C语言中有一种很奇怪的参数“…”,它主要用在引数(argument)个数不定的函数中,最 常见的就是printf函数。 printf(“Enjoy yourself everyday!\n”); printf(“The value is %d!\n”, value); …… 你想过它是怎么实现的吗? 1. printf为什么叫printf? 不管是看什么,我总是一个喜欢刨根问底的人,对事物的源有一种特殊的癖好,一段典故 ,一个成语,一句行话,我最喜欢的就是找到它的来历,和当时的意境,一个外文翻译过 来的术语,最低要求我会尽力去找到它原本的外文术语。特别是一个字的命名来历,我一 向是非常在意的,中国有句古话:“名不正,则言不顺。”printf中的f就是format的意思 ,即按格式打印【注13】。 注13:其实还有很多函数,很多变量,很多命名在各种语言中都是非常讲究的,你如果细 心观察追溯,一定有很多乐趣和满足,比如哈希表为什么叫hashtable而不叫hashlist?在 C++的SGI STL实现中有一个专门用于递增的函数iota(不是itoa),为什么叫这个奇怪的 名字,你想过吗?看文章我不喜欢意犹未尽,己所不欲,勿施于人,所以我把这两个答案 告诉你: (1)table与list做为表讲的区别: table: -------|--------------------|------- item1 | kadkglasgaldfgl | jkdsfh -------|--------------------|------- item2 | kjdszhahlka | xcvz -------|--------------------|------- list: **** *** ******* ***** That's the difference! 如果你还是不明白,可以去看一下hash是如何实现的! (2)The name iota is taken from the programming language APL. 而APL语言主要是做数学计算的,在数学中有很多公式会借用希腊字母, 希腊字母表中有这样一个字母,大写为Ι,小写为ι, 它的英文拼写正好是iota,这个字母在θ(theta)和κ(kappa)之间! 你可以看看http://www.wikipedia.org/wiki/APL_programming_language 下面有一段是这样的: APL is renowned for using a set of non-ASCII symbols that are an extension of traditional arithmetic and algebraic notation. These cryptic symbols, some hav e joked, make it possible to construct an entire air traffic control system in two lines of code. Because of its condensed nature and non-standard character s, APL has sometimes been termed a "write-only language", and reading an APL p rogram can feel like decoding an alien tongue. Because of the unusual characte r-set, many programmers used special APL keyboards in the production of APL co de. Nowadays there are various ways to write APL code using only ASCII charact ers. 在C++中有函数重载(overload)可以用来区别不同函数参数的调用,但它还是不能表示任 意数量的函数参数。 在标准C语言中定义了一个头文件<stdarg.h>专门用来对付可变参数列表,它包含了一组宏 ,和一个va_list的typedef声明。一个典型实现如下【注14】: typedef char* va_list; #define va_start(list) list = (char*)&va_alist #define va_end(list) #define va_arg(list, mode)\ ((mode*) (list += sizeof(mode)))[-1] 注14:你可以查看C99标准7.15节获得详细而权威的说明。也可以参考Andrew Konig的《C 陷阱与缺陷》的附录A。 ANSI C还提供了vprintf函数,它和对应的printf函数行为方式上完全相同,只不过用va_ list替换了格式字符串后的参数序列。至于它是如何实现的,你在认真读完《The C Prog ramming Language》后,我相信你一定可以do it yourself! 使用这些工具,我们就可以实现自己的可变参数函数,比如实现一个系统化的错误处理函 数error。它和printf函数的使用差不多。只不过将stream重新定向到stderr。在这里我借 鉴了《C陷阱与缺陷》的附录A的例子。 实现如下: #include <stdio.h> #include <stdarg.h> void error(char* format, …) { va_list ap; va_start(ap, format); fprintf(stderr, “error: “); vfprintf(stderr, format, ap); va_end(ap); fprintf(stderr, “\n”); exit(1); } 你还可以自己实现printf: #include <stdarg.h> int printf(char* format, …) { va_list ap; va_start(ap, format); int n = vprintf(format, ap); va_end(ap); return n; } 我还专门找到了VC7.1的头文件<stdarg.h>看了一下,发现各个宏的具体实现还是有区别的 ,跟很多预处理(preprocessor)相关。其中va_list就不一定是char*的别名。 typedef struct { char *a0; /* pointer to first homed integer argument */ int offset; /* byte offset of next parameter */ } va_list; 其它的定义类似。 经常在Windows进行系统编程的人一定知道函数调用有好几种不同的形式,比如__stdcall ,__pascal,__cdecl。在Windows下_stdcall,__pascal是一样的,所以我只说一下__st dcall和__cdecl的区别。 (1)__stdcall表示调用端负责被调用函数引数的压栈和出栈。函数参数个数一定的函数 都是这种调用形式。 例如:int fun(char c, double d),我们在main函数中调用它,这个函数就只管本身函数 体的运行,参数怎么来的,怎么去的,它一概不管。自然有main负责。不过,不同的编译 器的实现可能将参数从右向左压栈,也可能从左向右压栈,这个顺序我们是不能加于利用 的【注15】。 注15:你可以在Herb Sutter的《More Exceptional C++》中的条款20:An Unmanaged Po inter Problem, Part 1:Parameter Evaluation找到相关的细节论述。 (2)__cdecl表示被调用函数自身负责函数引数的压栈和出栈。参数参数可变的函数采用 的是这种调用形式。 为什么这种函数要采用不同于前面的调用形式呢?那是因为__stdcall调用形式对它没有作 用,调用端根本就无法知道被调用函数的引数个数,它怎么可能正确工作?所以这种调用 方式是必须的,不过由于参数参数可变的函数本身不多,所以用的地方比较少。 对于这两种方式,你可以编制一些简单的程序,如何反汇编,在汇编代码下面你就可以看 到实际的区别,很好理解的! 重载函数有很多匹配(match)规则调用。参数为“…”的函数是匹配最低的,这一点在A ndrei Alexandrescu的惊才绝艳之作《Modern C++ Design》中就有用到,参看Page34-35 ,2.7“编译期间侦测可转换性和继承性”。 |
下一篇:Beej's 网络编程指南
文章评论
共有 1 位网友发表了评论 此处只显示部分留言 点击查看完整评论页面