假设我有代码:
1a上。
char str1[50];
scanf("%s",&str1);
这很好。
1b中。
char str1[50];
scanf("%s",str1);
这也很好。
但是,以下代码无法从用户那里获取输入。
1c上。
char str1[50];
scanf("%c",str1[0]);
我知道&需要从用户那里获取输入并将其存储在一个地址中,但第一种情况会发生什么?
答案 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);
}