这里有一个经典的代码示例,似乎可以工作,但原因是错误的。
让我们回顾一下关于printf 和scanf 的一些事情。格式说明符%d 用于int 类型的值。你可以像这样读取一个整数:
int i;
scanf("%d", &i);
你可以像这样打印出来:
printf("%d\n", i);
为什么有人使用& 而有人不使用?好吧,C 使用所谓的“按值传递”。如果我们写了
scanf("%d", i); /* WRONG */
我们会将i 的值传递给 scanf。但是我们不想将i 的(旧)值传递给scanf,我们希望scanf 读取一个新值,并将其存储到i。换句话说,我们希望scanf 实际上将i 的新值传回给我们。为此,我们改为传递scanf 一个指向 变量i 的指针,我们希望它在其中存储刚刚读取的整数。这就是& 所做的——它生成一个指向i 的指针。
另一方面,当我们调用printf 时,传递参数的常规方式工作得很好。我们确实想将i 的值传递给printf,以便它可以打印出来。如果我们打电话
printf("%d\n", &i); /* WRONG */
这是行不通的,因为printf 需要一个int,而这里我们错误地把一个指向int 的指针交给了它。
所以现在我们知道对于带有%d 的整数,printf 需要一个int,而scanf 需要一个指向int 的指针。
让我们谈谈角色。 %c 格式用于字符。我们可以用scanf读取一个字符:
char c;
scanf("%c", &c);
我们可以用printf打印它:
printf("%c\n", c);
同样,模式完全相同。 scanf需要一个指针,这样才能填入值,所以我们传递&c。但是printf 只需要这个值,所以我们传递普通的c。
现在我们来看看字符串。 C 中的字符串是一个字符数组。此外,C 中的字符串总是由一个特殊的空字符 '\0' 终止,它标志着字符串的结束。因此,如果我们想声明一个可以包含最多 9 个字符的字符串的变量,我们可以这样写
char s[10];
这给了我们 9 个字符的空间,加上终止的 '\0'。
但是数组在 C 中是特殊的:每当你将一个数组传递给一个函数,或者当你做任何需要数组“值”的事情时,你得到的(编译器自动为你生成的)是一个指向数组第一个元素的指针。
这意味着要读取带有scanf 和%s 的字符串,我们可以调用:
scanf("%s", s);
“但是& 在哪里?”,你问。 “我以为你打电话给scanf时总是需要&!”
嗯,不完全是。在调用scanf 时,您总是需要一个指针。事实上,当你打电话给scanf("%s", s)时,就像你写了一样
scanf("%s", &s[0]);
当您将%s 与scanf 一起使用时,它需要一个指向多个字符中第一个字符的指针,即指向字符数组开头的指针,它应该开始写入它读取的字符串。 (它如何知道数组有多大?如果用户键入的字符串太长而无法放入数组怎么办?我们稍后会谈到这些点。)
当然,您也可以使用%s 打印字符串,它看起来像这样:
printf("%s\n", s);
再一次,就像你写的一样
printf("%s\n", &s[0]);
当您将%s 与printf 一起使用时,它需要一个指向应该开始打印的几个字符中的第一个字符的指针,直到找到终止'\0' 字符。
所以%s 与printf 和scanf 是特殊的,因为字符串是特殊的(因为数组是特殊的)。使用%d 和%c 以及几乎所有其他格式说明符,当您调用scanf 时通常需要&,而当您调用printf 时通常不需要&。但是对于%s,您通常不希望& 用于printf 或scanf。
(如果我们仔细考虑一下,这个例外并不是scanf 和%s 不需要&。记住,规则是真的,scanf 总是需要指针.scanf 和%s 不需要& 的唯一原因是,当你传递一个数组时,你会自动获得指向数组第一个元素的指针。所以printf 和@987654404 确实例外@: printf 和 %s 确实 期望一个指针,而 printf 和 %s 被设计为期望一个指针的原因是没有办法不给它一个:它 必须接受一个指针,因为对于字符串,这就是你最后总是给它的。)
所以%s 的规则是scanf 需要一个指向多个字符中第一个的指针,而printf 也需要一个指向多个字符中第一个的指针。
所以现在,在所有这些背景之外,我们可以查看您的代码。你基本上写了
char c;
scanf("%s", &c);
起初这似乎有点,有点,几乎是正确的。 scanf 和%s 想要一个指向字符的指针,而你给了它&c,它是一个指向字符的指针。但是%s 真的想要一个指针指向几个字符中的第一个。但是你给了它一个指向单个字符的指针。因此,当用户键入字符串时,键入的第一个字符将存储在c 中,但其余字符以及终止的'\0' 将被写入变量c 右侧的某个未分配内存中.他们将覆盖(“clobber”)可能用于其他用途的内存。这是一个严重的问题,但可能不会马上变得明显。
最后,您尝试使用printf 再次打印出来。你第一次尝试
printf("%s\n", c); /* WRONG */
但这根本不起作用。原因是%s 和printf 需要一个指向char 的指针,但你给了它一个普通的char。假设c 包含字母'A'。这最终会要求printf 转到地址65 并开始打印字符,直到找到终止的'\0'。为什么地址为 65?因为 65 是 A 的 ASCII 码。但是在内存中可能没有从地址 65 开始的正确的、以 null 结尾的字符串;事实上,您的程序很有可能根本没有从地址 65 读取的权限。
那你试过了
printf("%s\n", &c); /* ALSO WRONG */
这似乎起作用了。它“有效”是因为 if scanf 成功地将完整的字符串存储到 c 中,并且未分配的内存位于它的右侧,并且如果以某种方式破坏该内存并没有导致(太多)其他问题,那么当你将指针&c 传递给printf 时,printf 可以找到这些字符,组成一个字符串,然后打印出来。
所以它“有效”,但正如我所说,出于错误的原因:在此过程中,它会踩到它不“拥有”的所有内存,并且迟早会导致其他东西无法正常工作.
您应该如何扫描和打印字符串?正如我们之前看到的,一种方法是这样的:
char s[10];
scanf("%s", s);
printf("%s\n", s);
现在当scanf 获得指向数组s 的第一个元素的指针时,它有10 个字符可供使用。
我们确实不得不担心用户输入超过 9 个字符的可能性。但是有一个解决方法:我们可以告诉scanf 一个字符串允许读取多长时间,允许将多少个字符写入我们交给它的数组:
scanf("%9s", s);
那里的9 告诉scanf 不允许从用户那里读取超过9 个字符。由于 9 小于 10,因此终止 '\0' 字符仍有空间。
关于scanf,还有很多话要说。正如 chqrlie 在评论中指出的那样,检查它的返回值很重要,以确保它成功地转换了你想要的尽可能多的值。它有一些关于空格的奇怪规则。除非您知道自己在做什么,否则您不能将对scanf 的调用与对其他输入读取函数(如getchar 或fgets)的调用混合在一起——您会得到奇怪的结果。最后,scanf 是如此的挑剔,并且(最终)如此缺乏真正有用的功能,以至于它根本不值得使用。但这些是另一天的话题,因为这个答案已经是tl;dr。