scanf(或)printf中的格式说明符错误

icomxhvb  于 2022-12-03  发布在  其他
关注(0)|答案(2)|浏览(152)

关于printf函数,我从几篇参考文献和实验中了解到以下几点。

  • 当我们试图打印一个整数值时,格式说明符用于浮点(或)双精度,反之亦然,行为是不可预测的
  • 但是,可以使用%c打印整数值的字符等效值。也可以使用%d打印字符的ASCII值(整数表示)。

类似地,如果格式说明符和传递给scanf的参数不匹配,scanf的行为是什么?标准定义了吗?

yrwegjxp

yrwegjxp1#

变量参数(与省略号...相匹配的变量)是 * default-promitted *,这意味着所有较短的整型类型都被提升为int(或无符号类型)。整型和字符(I believe)之间没有区别。%d%cprintf中的区别仅仅是值的 * 格式 *。
scanf则完全不同,你传递的所有参数都是 pointers,指针之间没有default-pro motion,而且你传递的格式说明符必须与指针对象的类型完全匹配。
在这两种情况下,如果您的格式说明符与提供的参数不匹配(例如,将int *传递给printf中的%p),则结果是 *undefined behavior *,这比“不可预测”要糟糕得多--这意味着您的程序只是格式错误。

56lgkhnf

56lgkhnf2#

printfscanf很容易让人混淆,因为它们是非常特殊的函数。它们都接受 * 可变数量的参数 *。这意味着匹配参数类型的规则,以及可能发生的自动转换,与其他函数不同。
对于大多数函数,编译器确切地知道需要什么类型的参数。例如,如果调用

#include <math.h>

int i = 144;
printf("%f\n", sqrt(i));

sqrt函数需要一个double类型的参数,但编译器知道这一点,所以它可以插入一个自动转换,就像您编写

printf("%f\n", sqrt((double)i));

(脚注:编译器从<math.h>中找到的函数原型中知道sqrt的预期参数类型。)
但是printf接受任意类型的参数,只要格式说明符匹配,你可以传递任何参数:

int i1 = 12, i2 = 34;
float f1 = 56.78, f2 = 9.10;
printf("%d %d %f %f\n", i1, i2, f1, f2);
printf("%f %f %d %d\n", f1, f2, i1, i2);

但这是关键:* 只要格式说明符与 * 匹配。在调用参数数目可变的函数(如printf)时,编译器不会尝试(甚至不允许尝试)将每个参数转换为相应格式说明符所期望的类型。因此,如果存在严重的不匹配,比如试图使用%f打印整数,它将不起作用,事实上疯狂的事情也会发生。
但是,为了让事情更有趣,当你调用一个参数个数可变的函数时,比如printf,会自动执行另一组转换。它们被称为 default argument promotions。基本上,charshort类型的任何东西都会被自动转换(“提升”)为int,并且float类型的任何内容都会自动转换为double。您可以使用%d打印charshort,也可以使用%f打印floatdouble。由于char总是提升为int,这意味着%c实际上将接收int,这意味着您可以传递一个常规的int,并使用%c打印它。
但是很容易出错,特别是当我们调用其他函数,比如sqrt时,我们已经习惯了编译器为我们正确地转换所有的东西。所以,一个好的编译器会偷看格式字符串,并用它来预测你应该传递什么类型的参数,如果你传递了错误的东西,编译器会发出警告。如果你的编译器没有发出这样的警告,最好弄清楚如何启用它们,如果没有的话,也许可以使用一个更好的编译器。
scanf也接受可变数量的参数,这些参数应该与格式字符串上的说明符相匹配。但是,对于scanf,* 所有 * 传递的参数都是指针。你要求scanf用它读到的值来填充的变量。这意味着 * 没有 * 自动转换是可能的,并且必须将所有参数作为正确类型的指针进行传递。下表列出了其中的一些类型:
| 说明符|相应的变元|
| - -|- -|
| x1米32英寸|指向char的指针|
| x1米34英寸|指向short的指针|
| x1米36英寸1x|指向int的指针|
| x1米38英寸|指向long的指针|
| x1米40英寸|指向float的指针|
| %lf|指向double的指针|
| %s个|指向char的指针[注]|
所以没有从charshortint的自动转换,就像printf一样,也没有从floatdouble的自动转换,如果你阅读的是float,你必须使用%f,如果你读的是double,你必须使用%d
不过,一个好的编译器会再次警告您任何不匹配的情况,找出并注意这些警告是一个非常好的主意!
当你调用scanf时,有一个“转换”会自动发生,但它与scanf无关。如果你使用%s读取一个字符串,你应该传递一个pointer-to-char类型的参数,尽管它必须是一个指向一些连续字符的指针。因为scanf可能不会读取一个字符它可能会读取一个字符串,所以很常见的做法是给scanf传递一个字符数组,这没关系,因为在C语言的表达式中使用数组时,比如给函数传递数组(如scanf),使用的值是指向数组第一个元素的指针,它工作正常,并且是scanf所期望的。

char *p = malloc(10);
scanf("%9s", p);

char a[1-];
scanf("%9s", a);

(请注意,在这种情况下,您 * 不 * 需要在传递给scanf的变量上使用&。)

相关问题