-
xzsdn_08



- 注册时间 01-04-2009
- 江苏徐州
- 发帖总数 641
|
- 16.1 为什么调用 printf() 前, 必须要用 #include <stdio.h>?
- 16.2 为什么 %f 可以在 printf() 参数中, 同时表示 float 和 double?他们难道不是不同类型吗?
- 16.3 为什么当 n 为 long int, printf("%d", n); 编译时没有匹配警告?我以为 ANSI 函数原型可以防止这样的类型不匹配。
- 16.4 怎样写一个有可变参数的函数?
- 16.5 怎样写类似 printf() 的函数, 再把参数转传给 printf() 去完成大部分工作?
- 16.6 怎样写类似 scanf() 的函数, 再把参数转传给 scanf() 去完成大部分工作?
- 16.7 怎样知道实际上有多少个参数传入函数?
- 16.8 为什么编译器不让我定义一个没有固定参数项的可变参数函数?
- 16.9 我有个接受 float 的可变参函数, 为什么 va_arg(argp, float) 不工作?
- 16.10 va_arg() 不能得到类型为函数指针的参数。
- 16.11 怎样实现一个可变参数函数, 它把参数再传给另一个可变参数函数?
- 16.12 怎样调用一个参数在执行是才建立的函数?
16.1 为什么调用 printf() 前, 必须要用 #include <stdio.h>? 为了把 printf() 的正确原型说明引入作用域。
对于用可变参数的函数, 编译器可能用不同的调用次序。例如, 如果可变参数的调用比固定参数的调用效率低。所以在调用可变参数的函数前, 它的原型说明必须在作用域内, 编译器由此知道要用不定长调用机制。在原型说明中用省略号 ``..." 来表示可变参数。
参考资料: [ISO, Sec. 6.3.2.2, Sec. 7.1.7]; [Rationale, Sec. 3.3.2.2, Sec. 4.1.6]; [H&S, Sec. 9.2.4 pp. 268-9, Sec. 9.6 pp. 275-6]。
16.2 为什么 %f 可以在 printf() 参数中, 同时表示 float 和 double?他们难道不是不同类型吗? ``参数默认晋级" 规则适用于在可变参数中的可变动部分: char 和 short int 晋级到 int, float 晋级到 double。同样的晋级也适用于在作用域中没有原型说明的函数调用, 即所谓的 ``旧风格" 函数调用, 参见问题 11.4。所以 printf 的 %f 格式总是得到 double。类似的, %c 总是得到 int, %hd 也是。参见问题 12.7, 12.13。
参考资料: [ISO, Sec. 6.3.2.2]; [H&S, Sec. 6.3.5 p. 177, Sec. 9.4 pp. 272-3]。
16.3 为什么当 n 为 long int, printf("%d", n); 编译时没有匹配警告?我以为 ANSI 函数原型可以防止这样的类型不匹配。 当一个函数用可变参数时, 它的原型说明没有也不能提供可变参数的数目和类型。所以通常的参数匹配保护不适用于可变参数中的可变部分。编译器不能执行内含的转换或警告不匹配问题16.1。
参见问题 5.2, 11.4, 12.7 和 15.2。
脚注
... 编译器不能执行内含的转换或警告不匹配问题16.1 译者注: 现代的编译器 (例如 gcc), 如果打开编译警告参数, 编译器对标准中的可变参数函数 (printf, scanf ... 等) 会进行匹配测试。象问题中的源代码, 用 ``gcc -Wall" 进行编译, 会给出这样的警告: ``warning: int format, long int arg (arg 2)"
16.4 怎样写一个有可变参数的函数? 用 <stdarg.h> 提供的辅助设施。
下面是一个把任意个字符串连接起来的函数, 结果存在 malloc 的内存中:
#include <stdlib.h> /* 说明 malloc, NULL, size_t */
#include <stdarg.h> /* 说明 va_ 相关类型和函数 */
#include <string.h> /* 说明 strcat 等 */
char *vstrcat(const char *first, ...)
{
size_t len;
char *retbuf;
va_list argp;
char *p;
if(first == NULL)
return NULL;
len = strlen(first);
va_start(argp, first);
while((p = va_arg(argp, char *)) != NULL)
len += strlen(p);
va_end(argp);
retbuf = malloc(len + 1); /* +1 包含终止符 \0 */
if(retbuf == NULL)
return NULL; /* 出错 */
(void)strcpy(retbuf, first);
va_start(argp, first); /* 重新开始扫描 */
while((p = va_arg(argp, char *)) != NULL)
(void)strcat(retbuf, p);
va_end(argp);
return retbuf;
}
调用如下:
char *str = vstrcat("Hello, ", "world!", (char *)NULL);
注意最后一个参数的类型重置; 参见问题 5.2, 15.3。注意调用者要释放返回的存储空间, 那是用 malloc 分配的。
参考资料: [K&R2, Sec. 7.3 p. 155, Sec. B7 p. 254]; [ISO, Sec. 7.8]; [Rationale, Sec. 4.8]; [H&S, Sec. 11.4 pp. 296-9]; [CT&P, Sec. A.3 pp. 139-141]; [PCS, Sec. 11 pp. 184-5, Sec. 13 p. 242]。
16.5 怎样写类似 printf() 的函数, 再把参数转传给 printf() 去完成大部分工作? 用 vprintf(), vfprintf() 或 vsprintf()。
下面是一个 error() 函数, 它列印一个出错信息, 在信息前加入字符串 ``error: " 和在信息后加入换行符:
#include <stdio.h>
#include <stdarg.h>
void error(const char *fmt, ...)
{
va_list argp;
fprintf(stderr, "error: ");
va_start(argp, fmt);
vfprintf(stderr, fmt, argp);
va_end(argp);
fprintf(stderr, "\n");
}
参考资料: [K&R2, Sec. 8.3 p. 174, Sec. B1.2 p. 245]; [ISO, Secs. 7.9.6.7,7.9.6.8,7.9.6.9]; [H&S, Sec. 15.12 pp. 379-80]; [PCS, Sec. 11 pp. 186-7]。
16.6 怎样写类似 scanf() 的函数, 再把参数转传给 scanf() 去完成大部分工作? C99 支持 vscanf(), vfscanf() 和 vsscanf(), C99 以前的标准不支持。
参考资料: [C9X, Secs. 7.3.6.12-14]。
16.7 怎样知道实际上有多少个参数传入函数? 这一段信息不可移植。 一些旧系统提供一个非标准函数 nargs()。然而它的可信度值得怀疑, 因为它的典型返回值是参数的字节长度, 而不是参数的个数。结构、整数和浮点类型的值一般需要几个字节的长度。
任何接收可变参数的函数都应该可以从传入的参数本身来得到参数的数目。类 printf 函数从格式字符串中的格式说明符来确定参数个数, 就象 %d 这样的格式说明符。所以如果格式字符串和参数数目不符时, 此类函数会出错的很厉害。
还有一个常用的技巧, 如果所有的参数是同一个类型, 在参数列最后加一个标记值。 通常用 0、-1 或适当类型转换的空指针, 参见问题 5.2 和 15.4 例子中 exec1() 和 vstrcat() 的用法。
最后, 如果类型是可预见的, 你可以加一个参数数目的参数。当然调用者通常是很不喜欢这种做法的。
参考资料: [PCS, Sec. 11 pp. 167-8]。
16.8 为什么编译器不让我定义一个没有固定参数项的可变参数函数? 标准 C 要求用可变参数的函数至少有一个固定参数项, 这样你才可以使用 va_start()。所以编译器不会接受下面定义的函数:
int f(...)
{
...
}
参见问题 15.9。
参考资料: [ISO, Sec. 6.5.4, Sec. 6.5.4.3, Sec. 7.8.1.1]; [H&S, Sec. 9.2 p. 263]。
16.9 我有个接受 float 的可变参函数, 为什么 va_arg(argp, float) 不工作? ``参数默认晋级" 规则适用于在可变参数中的可变动部分: 参数类型为 float 的总是晋级 (扩展) 到 double, char 和 short int 晋级到 int。所以 va_arg(arpg, float) 是错误的用法。应该总是用 va_arg(arpg, double)。 同理, 要用 va_arg(argp, int) 来取得原来类型是 char, short 或 int 的参数。基于相同理由, 传给 va_start() 的最后一个 ``固定" 参数项的类型不会被晋级。参见问题 11.4 和 15.2。
参考资料: [ISO, Sec. 6.3.2.2]; [Rationale, Sec. 4.8.1.2]; [H&S, Sec. 11.4 p. 297]。
16.10 va_arg() 不能得到类型为函数指针的参数。 宏 va_arg() 所用的类型重写不能很好地操作于象函数指针这类过度复杂的类型。但是如果你用 typedef 定义一个函数指针类型, 那就一切正常了。参见问题 1.7。
参考资料: [ISO, Sec. 7.8.1.2]; [Rationale, Sec. 4.8.1.2]。
16.11 怎样实现一个可变参数函数, 它把参数再传给另一个可变参数函数? 通常来说, 你做不到。理想情况下, 你应该提供另一个版本的函数, 这个函数接受 va_list 指针类型的参数。类似于 vfprintf(), 参见问题 15.5。如果所有的参数必须完整的传给另一个函数, 或者你不能重写另一个函数为一个接受 va_list 指针类型参数的函数, 这并没有一个可移植的解决方法。也许可以通过求助于机器的汇编语言来实现。参见问题 15.12。
16.12 怎样调用一个参数在执行是才建立的函数? 这没有一个保证工作或可移植的方法。如果你好奇, 可以问本文的编辑 (Steve Summit), 他有一些古怪的点子, 也许你可以试试 ……
也许你可以试着传一个无值型指针 (void *) 数组, 而不是一个参数序列。被调用函数遍历这个数组, 就象 main() 遍历 argv 一样。当然这一切都建立在你能控制所有的调用函数上。参见问题 19.35。
|
|