c语言中printf函数的定义是:
int printf(const char * _Format, ...);
scanf
和许多类似的功能也是如此。
为什么有_Format
强制性参数?
格式字符串是必须的
具体来说,要读取其他变量参数,请使用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
,但这足以说明fmt
如何使用va_start
。
一次,这实际上并不需要。回到C时,您可以具有相当于:int f(...);
的功能。
但是,在第一个C标准化工作期间,这被取消了上述宏(va_start
,va_arg
,va_end
),它需要至少一个命名参数。较旧的宏对呼叫大会提出了许多要求:
- 参数总是以相同的方式传递,无论类型或数字如何。
- 总是很容易找到通过的参数。
随着常规的c呼叫约定(所有参数均在堆栈上传递,参数从右向左推动)是事实。您基本上只是看着堆栈的顶部,向后移动返回地址,这是第一个参数。
随着其他呼叫惯例,事情并不是那么简单。例如,仅从左至右推动参数意味着第一个参数(在printf
的情况下,格式字符串)被埋在堆栈下的一些任意距离,并在其之后使用任意数量的其他参数。
他们想出的方式是将以前的(命名)参数传递给 va_start
(而va_start是通常使用该参数地址的宏)。如果您从右向左推动,这将为您提供一个地址,任何需要的距离都需要堆栈,则va_arg
可以回路 up 堆栈以检索其他变量参数。
这显然被视为可接受的妥协,尤其是因为采用变量参数的函数几乎总是以至少一个命名的参数。
,因为它不想猜测要打印什么
这是必不可少的,因为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
是当没有参数传递时,绝对没有用。
一般而言,具有未知数参数的函数依赖于va_start
,va_arg
和va_end
来处理函数参数列表中未明确的参数。
va_start
需要使用的最后一个命名参数。因此,一个具有未知数参数的函数必须至少具有一个命名参数。
printf
指定格式规范的参数/参数是最佳选择,作为必需的参数/参数。
没有格式描述,printf不会理解要打印的内容。对于C,一切都只是字节,因此printf不知道传递了什么样的数据,因此不知道如何表示。
当您是新来的C时,您可能还没有意识到这是多么真实,尤其是如果您学到了一种语言,即Print()了解它所看到的数据类型。