【问题标题】:The usage of an ampersand(&) in printf and format specifiers在 printf 和格式说明符中使用 & 符号
【发布时间】:2018-10-09 00:49:28
【问题描述】:
#include <stdio.h>
#include <stdlib.h>

int main() {
    char  a;
    printf("What? \t");

    scanf("%s", &a);

    printf("U have to %s", a);
    return 0;
}

每当我构建并运行此代码并在%s 中输入一个值时,我都会收到错误消息,并且调试程序会停止工作并关闭。但是当我像这样使用&符号时:

#include <stdio.h>
#include <stdlib.h>

int main() {
    char  a;
    printf("What? \t");

    scanf("%s", &a);

    printf("U have to %s", &a);
    return 0;
}

在printf... 中有效。这是为什么?格式说明符之间也有所不同,例如在scanf 中使用%c 或%d 时,不需要在printf 中输入&amp;(和号)符号。为什么会发生这种情况?它与数据类型有关吗?是哪个格式说明符得出了这个结果?

(对不起,我的英语不好。我不是以英语为母语的人,这是我第一次来这里)。

【问题讨论】:

  • %s 需要一个指向字符串的指针,而不是单个字符。
  • 未定义的行为不需要失败;它也不需要工作。行为未定义,任何事情都可能发生。将指向单个字符的指针传递给 scanf() 和 %s 作为相应的转换规范正在调用未定义的行为。

标签: c


【解决方案1】:

这里有一个经典的代码示例,似乎可以工作,但原因是错误的。

让我们回顾一下关于printf 和scanf 的一些事情。格式说明符%d 用于int 类型的值。你可以像这样读取一个整数:

int i;
scanf("%d", &i);

你可以像这样打印出来:

printf("%d\n", i);

为什么有人使用&amp; 而有人不使用?好吧,C 使用所谓的“按值传递”。如果我们写了

scanf("%d", i);    /* WRONG */

我们会将i 的值传递给 scanf。但是我们不想将i 的(旧)值传递给scanf,我们希望scanf 读取一个新值,并将其存储到i。换句话说,我们希望scanf 实际上将i 的新值传回给我们。为此,我们改为传递scanf 一个指向 变量i 的指针,我们希望它在其中存储刚刚读取的整数。这就是&amp; 所做的——它生成一个指向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需要一个指针,这样才能填入值,所以我们传递&amp;c。但是printf 只需要这个值,所以我们传递普通的c。

现在我们来看看字符串。 C 中的字符串是一个字符数组。此外,C 中的字符串总是由一个特殊的空字符 '\0' 终止,它标志着字符串的结束。因此,如果我们想声明一个可以包含最多 9 个字符的字符串的变量,我们可以这样写

char s[10];

这给了我们 9 个字符的空间,加上终止的 '\0'。

但是数组在 C 中是特殊的:每当你将一个数组传递给一个函数,或者当你做任何需要数组“值”的事情时,你得到的(编译器自动为你生成的)是一个指向数组第一个元素的指针。

这意味着要读取带有scanf 和%s 的字符串,我们可以调用:

scanf("%s", s);

“但是&amp; 在哪里?”,你问。 “我以为你打电话给scanf时总是需要&amp;!”

嗯,不完全是。在调用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 时通常需要&amp;,而当您调用printf 时通常不需要&amp;。但是对于%s,您通常不希望&amp; 用于printf 或scanf。

(如果我们仔细考虑一下,这个例外并不是scanf 和%s 不需要&amp;。记住,规则是真的,scanf 总是需要指针.scanf 和%s 不需要&amp; 的唯一原因是,当你传递一个数组时,你会自动获得指向数组第一个元素的指针。所以printf 和@987654404 确实例外@: printf 和 %s 确实 期望一个指针,而 printf 和 %s 被设计为期望一个指针的原因是没有办法不给它一个:它 必须接受一个指针,因为对于字符串,这就是你最后总是给它的。)

所以%s 的规则是scanf 需要一个指向多个字符中第一个的指针,而printf 也需要一个指向多个字符中第一个的指针。

所以现在,在所有这些背景之外,我们可以查看您的代码。你基本上写了

char c;
scanf("%s", &c);

起初这似乎有点,有点,几乎是正确的。 scanf 和%s 想要一个指向字符的指针,而你给了它&amp;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 中,并且未分配的内存位于它的右侧,并且如果以某种方式破坏该内存并没有导致(太多)其他问题,那么当你将指针&amp;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。

【讨论】:

  • 关于scanf()的相当广泛的课程...但您可能还想向用户介绍返回值以及测试它的必要性。
  • @chqrlie 好点。答案已经太长了,但值得一提。
  • 很好的答案!我认为还值得明确地简要介绍一下 undefined behavior :关于这个答案说“这似乎有效”的地方,我会赶紧补充一点,触发未定义行为的程序可能很好(似乎) 在一个上下文中工作,然后在另一个上下文中不工作即使程序本身完全相同;所以你甚至可以有一个程序在你的调试器和你的 IDE 中“工作”,但是在命令行上运行的行为完全不同;新程序员需要好好学习这一点,并尽早学习
【解决方案2】:

%s 格式说明符需要一个指向字符串的指针。当与scanf 一起使用时,它必须是一个char 数组,其中包含您输入的单词的足够字符加上指示字符串结尾的尾随空字节。在 printf() 中,它必须是一个以 null 结尾的 char 数组。

使用指向char 变量的指针不起作用,因为它没有空间容纳空字节。您在变量外部写入会导致未定义的行为。

char word[100];
scanf("%s", word);
printf("%s\n", word);

您可以使用%c 读取和写入单个字符,而不是多个字符的字符串。

char letter;
scanf("%c", &letter);
printf("%c\n", letter);

【讨论】:

    【解决方案3】:

    在声明中char a; a 是一个字符变量& 扫描一个字符变量使用%c 格式说明符。

    scanf("%s",a);/* %s expects base address of char buffer, not single char */
    scanf(" %c",&a);/* this is correct */
    

    如果您想使用%s 进行扫描,那么您的输入应该是字符缓冲区,例如char buf[10]。例如

    char a[10];
    scanf("%s",a);
    

    当你使用 %c 或 %d 时,你不需要在 printf 中输入 &(&) 符号吗? 不需要将地址 &amp; 提供给 printf() 作为 printf() 工作是打印不扫描。例如

    char input;
    scanf("%c",&input);/* here you need &, As scanf() will store input char into
                          address you provided i.e &input */
    printf("%c",input);/*here no need &input, bcz input char already stored,
                         printf will just print the char*/
    

    好吧,如果你打印地址,你可以使用%p。

    printf("%p",a);/*a is char buffer */
    

    【讨论】:

      猜你喜欢
      • 2012-05-17
      • 2012-05-17
      • 1970-01-01
      • 1970-01-01
      • 2017-11-16
      • 2011-03-11
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多