为什么两种输入字符串的方法都有效?

时间:2014-09-09 18:36:00

标签: c++ string arrays

假设我有代码:

1a上。

char str1[50];
scanf("%s",&str1);

这很好。

1b中。

char str1[50];
scanf("%s",str1);

这也很好。

但是,以下代码无法从用户那里获取输入。

1c上。

char str1[50];
scanf("%c",str1[0]);

我知道&需要从用户那里获取输入并将其存储在一个地址中,但第一种情况会发生什么?

4 个答案:

答案 0 :(得分:3)

读取字符串的正确方法是:

scanf("%s", &str1[0]);    // Correct
scanf("%s", str1);        // Also correct

第一个明确正确 - 您想要传递第一个char的地址。事实证明,第二个是等价的。将数组传递给函数时,它会静默“衰减”到指针。编译器将array之类的参数转换为&array[0]

scanf("%c",str1[0]);      // Incorrect

传递第一个字符而不是第一个字符的地址是错误的。这将无法编译,或者在运行程序时很可能会崩溃。字符与地址非常不同。

scanf("%s", &str1);       // Undefined behavior

这是错误的,但如果我被允许做出这样的区分,那就不是“错”了。

传递&str1是一个常见错误。原因确实很微妙。 &str[0]是字符串第一个字符的地址。 &str是整个50个字符数组的地址。这里的细微之处在于它们实际上是内存中的相同地址 - 数组从它的第一个字符开始。

那为什么这是错的?

答案是打字。

  • &str[0]的类型是char * - 指向字符的指针。当您使用%s时,这是预期的类型。

  • &str的类型是char (*)[50] - 指向50个字符数组的指针。这 char *相同。有关进一步说明,请参阅:How come an array's address is equal to its value in C?

&str&str[0]相等,但类型不同。从迂腐的角度来看,这种类型不匹配是一种错误。它会导致未定义的行为。

我知道你在想什么。 “好吧,聪明的家伙 - 如果这是一个错误,那为什么它会起作用,对吧?”嗯,C是一种有趣的语言。它真的很信任。过分信任,真的。如果你告诉它跳下悬崖,它会从悬崖上跳下来。它并不是为了验证你做的是绝对正确和明智的事情。

有很多时候你可以要求它做一些在技术上无效的事情。编译器不需要诊断和防止未定义的行为。在效率的名义下,允许他们做任何他们想做的事情:崩溃,或打印错误信息......或者,通常看似实际工作。这是其中一个案例。 &str&str[0]的值相同,因为scanf()是varargs函数,并非所有编译器都会捕获类型不匹配。

士气是:每次出错都不要依赖编译器告诉你。如果你陷入困境并调用未定义的行为,你可能会暂时放弃它。您的代码可能似乎有效。但是你坐在定时炸弹上。有一天它会在你的脸上爆炸。

答案 1 :(得分:1)

让我们一个接一个地把它全部拿走:

  

我知道&需要从用户那里获取输入并将其存储在一个地址中,但第一种情况会发生什么?

实际上,那是错的。您无法将地址存储在地址中,因为地址不可修改 你可能想说的是,你知道C是严格按值传递的,因此让函数修改一个对象,你需要传递一个指向它的指针(使用&来获取)。

现在,让我们看看你的代码:

char str1[50];
scanf("%s",&str1);

这是Undefined Behavior,因为scanf需要指向字符类型的指针(char* unsigned char* signed char*const - 限定版本) 。
传递char(*)[50]类型的值是不行的。为什么它仍然工作
因为the address of an array and its first element are guaranteed to be identical,你的实现只有一个数据指针类型,你运气好(以后会咬你)。

如果忽略等待发生的缓冲区溢出,那么这个是好的。始终通过适当的限制:

char str1[50];
scanf("%s",str1);

应该是:

char str1[50];
scanf("%49s",str1);

您的下一个示例完全不同,将输入流中的单个char读入*str1

char str1[50];
scanf("%c",str1[0]);

这可能不是你想要的。

附加说明:请务必检查分配的字段数,即scanf的返回值。

答案 2 :(得分:0)

当声明诸如char str1[50]的数组时,数组的内存在堆栈上分配,如下所示:

str1
+----+----+----+----+----+----+----+
| 0  | 1  |   ........   | 48 | 49 |
+----+----+----+----+----+----+----+

现在,如果您获取数组的元素,它们是:

str1[0]
+----+----+----+----+----+----+----+
| 0  | 1  |   ........   | 48 | 49 |
+----+----+----+----+----+----+----+

     str1[1]
+----+----+----+----+----+----+----+
| 0  | 1  |   ........   | 48 | 49 |
+----+----+----+----+----+----+----+

对于这样的数组,恰好str1的地址是相同的 作为str1[0]的地址。

相反,使用scanf的正确方法是:

scanf("%s", str1);

scanf("%s", &str1[0]);

适用于

scanf("%s", &str1);

因为&str1&str1[0]是相同的。

如果使用动态分配的数组,它们将无法工作。

char* str2 = malloc(50);

假设malloc返回的值为AAA

str2的内存布局如下:

str2
+-----+
| AAA |
+-----+

地址AAA处的内存如下所示:

+----+----+----+----+----+----+----+
| 0  | 1  |   ........   | 48 | 49 |
+----+----+----+----+----+----+----+

数组的元素位于AAA开始。

str2[0]
+----+----+----+----+----+----+----+
| 0  | 1  |   ........   | 48 | 49 |
+----+----+----+----+----+----+----+

     str2[1]
+----+----+----+----+----+----+----+
| 0  | 1  |   ........   | 48 | 49 |
+----+----+----+----+----+----+----+

对于这种情况,&str2&str2[0]不同。如果您使用:

scanf("%s", &str2);

您将遇到未定义的行为。

答案 3 :(得分:0)

在我看来,真正的问题是C / C ++将数组名称视为引用而不是值,并结合一些C / C ++编译器将处理对数组名称的显式引用这一事实,例如& str ,与str相同。例如,使用以下代码,某些32位编译器(Visual C / C ++ 4.0)将为sizeof str1输出32,为& str1的大小输出32,而其他(Visual Studio 2005或更高版本)将为sizeof str1和4输出32对于sizeof& str1

#include <stdio.h>
main()
{
char str1[32];
    printf("%d %d\n", sizeof(str1), sizeof(&str1));
    return (0);
}