为什么每次都要在C中指定数据types?

正如你可以从下面的代码片断看到的,我已经声明了一个charvariables和一个intvariables。 当代码被编译时,它必须识别variablesstri的数据types。

为什么我需要通过指定%s%dscanf来扫描我的variables是否是string或整型variables? 编译器不够成熟,无法识别当我声明我的variables?

 #include <stdio.h> int main () { char str [80]; int i; printf ("Enter your family name: "); scanf ("%s",str); printf ("Enter your age: "); scanf ("%d",&i); return 0; } 

因为对于像scanfprintf这样的variables参数函数来说,没有可移植的方法来知道variables参数的types,甚至不需要传递多less个参数。

请参阅C常见问题解答: 如何发现函数实际调用的参数数量?


这就是为什么必须至less有一个固定的参数来确定variables参数的数量和types。 这个参数(标准称为parmN ,参见C11( ISO / IEC 9899: 201x)§7.16 variables参数 )起着这个特殊的作用,并将被传递给macrosva_start 。 换句话说,你不能在标准C中使用这样的原型:

 void foo(...); 

编译器之所以不能提供必要的信息很简单,因为编译器不在这里介入。 函数的原型没有指定types,因为这些函数具有可变types。 所以实际的数据types不是在编译时确定的,而是在运行时确定的。 然后该函数从堆栈中接收一个参数。 这些值没有任何关联的types信息,所以唯一的办法是,函数知道如何解释数据,通过使用调用者提供的信息,即格式string。

函数本身不知道传入哪些数据types,也不知道传递的参数的数量,所以printf无法自行决定。

在C ++中,你可以使用运算符重载,但这是一个完全不同的机制。 因为这里编译器根据数据types和可用的重载函数select合适的函数。

为了说明这一点,编译时的printf如下所示:

  push value1 ... push valueN push format_string call _printf 

printf的原型是这样的:

 int printf ( const char * format, ... ); 

因此,除了格式string中提供的内容之外,没有任何types信息被传送。

编译器可能很聪明,但函数printfscanf是愚蠢的 – 他们不知道每个调用传递的参数是什么types。 这就是为什么你每次都需要传递%s或者%d

printf不是一个内在函数 。 这本身不是C语言的一部分。 编译器所做的就是生成代码来调用printf ,传递任何参数。 现在,因为C没有提供reflection作为在运行时计算types信息的机制,程序员必须明确地提供所需的信息。

第一个参数是格式string 。 如果您打印的是十进制数字,则可能如下所示:

  • "%d" (十进制数)
  • "%5d" (用空格填充到宽度5的十进制数字)
  • "%05d" (用零填充到宽度5的十进制数字)
  • "%+d" (十进制数,总是带符号)
  • "Value: %d\n" (数字前后的一些内容)

例如,请参阅维基百科上的格式占位符以了解string可包含的格式。

此处也可以有多个参数:

"%s - %d" (一个string,然后是一些内容,然后是一个数字)

当我声明我的variables时,编译器是否不够成熟?

没有。

您正在使用几十年前指定的语言。 不要指望C的现代devise美学,因为它不是现代语言。 现代语言在编译,解释或执行方面往往会有less量的效率,以提高可用性或清晰度。 从计算机处理时间昂贵且供应非常有限的时候开始,其devise反映了这一点。

这也是为什么当你真正关心快速,高效或接近金属时,C和C ++仍然是select的语言。

scanf作为原型int scanf ( const char * format, ... ); 说商店根据参数格式给出数据到附加参数指向的位置。

它与编译器无关,它全部是关于为scanf定义的语法的。为了让scanf知道要为input的数据保留的大小,需要参数格式。

GCC(可能还有其他C编译器)至less在某些情况下跟踪参数types。 但是这种语言不是这样devise的。

printf函数是一个接受可变参数的普通函数。 variables参数需要某种运行时types的标识scheme,但在C语言中,值不包含任何运行时types信息。 (当然,C程序员可以使用结构或位操作技巧来创build运行时typesscheme,但是这些方法并没有被整合到语言中。)

当我们开发这样的function:

 void foo(int a, int b, ...); 

我们可以在第二个参数之后传递任意数量的附加参数,我们可以通过函数传递机制之外的某种协议来确定它们的types和types。

例如,如果我们像这样调用这个函数:

 foo(1, 2, 3.0); foo(1, 2, "abc"); 

被叫方无法区分这些情况。 在parameter passing区域只有一些位,我们不知道它们是代表一个指向字符数据的指针还是一个浮点数。

传达这类信息的可能性很多。 例如在POSIX中,函数的exec系列使用具有所有相同types的variables参数char * ,并且使用空指针来指示列表的结尾:

 #include <stdarg.h> void my_exec(char *progname, ...) { va_list variable_args; va_start (variable_args, progname); for (;;) { char *arg = va_arg(variable_args, char *); if (arg == 0) break; /* process arg */ } va_end(variable_args); /*...*/ } 

如果调用者忘记传递空指针终止符,则行为将是未定义的,因为该函数在消耗了所有参数之后将继续调用va_arg 。 我们的my_exec函数必须像这样调用:

 my_exec("foo", "bar", "xyzzy", (char *) 0); 

00是必需的,因为没有上下文将其解释为空指针常量:编译器不知道该参数的预期types是指针types。 而且(void *) 0是不正确的,因为它只是作为void *types而不是char *传递的,尽pipe二者在二进制级别几乎是可以兼容的,所以它在实践中将起作用。 这种types的exec函数常见的错误是:

 my_exec("foo", "bar", "xyzzy", NULL); 

编译器的NULL在没有任何(void *) NULL情况下被定义为0

另一个可能的scheme是要求调用者传递一个表示有多less个参数的数字。 当然,这个数字可能是不正确的。

printf的情况下,格式string描述参数列表。 该函数parsing它并相应地提取参数。

正如一开始提到的,一些编译器,特别是GNU C编译器,可以在编译时parsing格式化string,并根据参数的数量和types执行静态types检查。

但是,请注意格式string可能不是一个文字,并可能在运行时计算,这是不受这种types的检查scheme。 虚拟的例子:

 char *fmt_string = message_lookup(current_language, message_code); /* no type checking from gcc in this case: fmt_string could have four conversion specifiers, or ones not matching the types of arg1, arg2, arg3, without generating any diagnostic. */ snprintf(buffer, sizeof buffer, fmt_string, arg1, arg2, arg3); 

这是因为这是告诉函数的唯一方式(如printf scanf )你传递哪种types的值。 例如-

 int main() { int i=22; printf("%c",i); return 0; } 

此代码将打印字符不是整数22,因为您已经告诉printf函数将该variables视为char。

printfscanf是I / O函数,它们是以接收控制string和参数列表的方式进行devise和定义的。

函数不知道传递给它的参数的types,编译器也不能将这些信息传递给它。

因为在printf中你没有指定数据types,所以你需要指定数据格式。 这在任何语言中都是一个重要的区别,在C语言中是非常重要的。

当用%s扫描string时,不是说“为我的stringvariablesparsingstringinput”。 你不能说在C中,因为C没有stringtypes。 C对stringvariables最接近的是一个固定大小的字符数组,它恰好包含一个表示string的字符,string的末尾用空字符表示。 所以你真正说的是“这里是一个数组来保存string,我保证足够大的stringinput,我希望你parsing。

原始? 当然。 C是在40年前发明的,当时一台典型的机器至多有64K的RAM。 在这样的环境下,保存RAM的优先级高于复杂的string操作。

尽pipe如此, %s扫描器仍然存在于更高级的编程环境中,其中有string数据types。 因为这是关于扫描,而不是打字。