C语言中printf函数的定义是:
int printf(const char * _Format, ...);
同样适用于scanf
和许多类似的函数,其中管理了可变数量的参数。
为什么有_Format
必填参数?
答案 0 :(得分:7)
格式字符串是必需的,因为C&#39的可变参数宏的工作方式取决于至少存在一个参数,并使用它来查找其他参数。
具体来说,要读取其他变量参数,请使用va_start
(然后重复va_arg
,对于要读取的每个变量参数,使用一次。当你调用va_start
时,你需要传递格式字符串(或者更常见的是,函数的最后一个非变化参数)。
例如,这就像printf
一样,但会打印到stdout
和您选择的另一个文件:
void tee(FILE *f, char const *fmt, ...) {
va_list ap;
va_start(ap, fmt);
vprintf(fmt, ap);
va_end(ap);
va_start(ap, fmt);
vfprintf(f, fmt, ap);
va_end(ap);
}
这使用vprintf
和vfprintf
,因此它不会(直接)使用va_arg
本身,只有va_start
和va_end
,但是&{1}} #39;足以显示fmt
如何使用va_start
。
有一段时间,实际上并不需要这样做。当C闪亮而且新的时候,你可以拥有一个等同于int f(...);
的函数。
然而,在第一次C标准化工作期间,这被取消,有利于上面提到的需要至少一个命名参数的宏(va_start
,va_arg
,va_end
)。较旧的宏对调用约定提出了许多要求:
使用传统的C调用约定(所有参数都在栈上传递,参数从右向左推送)这是真的。你基本上只是查看堆栈的顶部,向后移动到返回地址,并且有第一个参数。
使用其他调用约定,事情并非如此简单。例如,只需从左向右推送参数意味着第一个参数(格式字符串,在printf
的情况下)被埋在堆栈中的任意距离,在其后面有任意数量的其他参数。 / p>
他们提出处理这个问题的方法是将前一个(命名)参数传递给va_start
(而va_start是一个通常使用该参数地址的宏)。如果你从右向左推,那将给你一个地址,无论距离堆栈需要什么距离,然后va_arg
可以向后走堆栈以检索其他变量参数。
这显然被认为是一种可接受的折衷方案,特别是因为采用变量参数的函数几乎总是至少采用一个命名参数。
答案 1 :(得分:4)
因为它不想猜猜要打印什么
答案 2 :(得分:1)
这是强制性的,因为printf
用于来打印数据。想象一下,如果你什么都不打印就会发生什么。 没有。那么,为什么要删除该参数?
关于scanf
的问题是一样的:你需要以某种方式读取数据如果你不知道格式的话,你将如何做?这个数据?
某些功能没有参数,因为他们不需要参数,例如
void Hello(void) { puts("Hello"); }
因此,他们可以在没有参数的情况下生存' 。关于printf
:
int printf(void) { //imaginary function, don't use it!
// WTF? What to print?
// Absolutely nothing! What's the purpose then?
return smth;
}
然后当没有传递参数时,此printf
绝对无用。
答案 3 :(得分:1)
通常,具有未知数量参数的函数依赖于va_start
,va_arg
和va_end
来处理未在函数参数列表中显式的参数。
va_start
需要使用最后一个命名参数。因此,具有未知数量的参数的函数必须至少具有一个命名参数。
对于printf
,指定格式规范的参数/参数是作为必需参数/参数的最佳选择。
答案 4 :(得分:0)
如果没有格式说明,printf将无法理解要打印的内容。对C来说,一切都只是字节,所以printf不知道传递给它的是什么类型的数据,因此不知道如何表示它。
当你刚接触C时,你可能还没有意识到这是多么真实,特别是如果你已经学会了一种print()理解它所看到的数据类型的语言。