char a[50][50] 将a 声明为char 的50 元素数组的50 元素数组。这意味着每个 a[i] 是一个由 50 个元素组成的 char 数组。它将像这样在内存中排列:
+---+
a: | | a[0][0]
+---+
| | a[0][1]
+---+
| | a[0][2]
+---+
...
+---+
| | a[0][49]
+---+
| | a[1][0]
+---+
| | a[1][1]
+---+
...
+---+
| | a[1][49]
+---+
| | a[2][0]
+---+
...
此代码在a 中存储最多50 个字符串,每个最多49 个字符(IOW,每个a[i] 可以存储一个49 个字符的字符串)。在 C 中,string 是包含 0 值终止符的字符值序列。例如,字符串"hello",表示为序列{'h', 'e', 'l', 'l', 'o', 0}。结尾的 0 标记了字符串的结尾。带有%s 说明符的puts 和printf 等字符串处理函数和输出函数需要该0 终止符才能正确处理字符串。
字符串存储在字符类型的数组中,char(用于 ASCII、UTF-8 或 EBCDIC 字符集)或 wchar_t 用于“宽”字符串(需要超过 8 位左右的字符集编码)。 N 个字符的字符串需要一个至少 N+1 个元素宽的数组来说明 0 终止符。
除非它是sizeof 或一元& 运算符的操作数,或者是用于初始化字符类型数组的字符串字面量,否则是“N 元素数组”类型的表达式的T”将被转换(“衰减”)为“指向T的指针”类型的表达式,表达式的值将是数组第一个元素的地址。
当你打电话时
gets( a[i] );
表达式a[i]从“char的50元素数组”类型转换为“指向char的指针”,表达式的值是数组第一个元素的地址(@987654345 @)1。 gets 将从标准输入读取字符并将它们存储到从该地址开始的数组中。请注意,gets 不再是标准 C 库的一部分——它在 2011 版标准中被删除,因为它不安全。 C 不需要对数组访问进行任何类型的边界检查 - 如果您输入的字符多于目标缓冲区的大小(在本例中为 50),那么这些额外的字符将在最后一个元素之后立即写入内存数组,这可能会导致各种混乱。缓冲区溢出是一种流行的恶意软件利用。您应该将 gets 调用替换为
fgets( a[i], 50, stdin );
它将从标准输入将最多 49 个字符读入a[i]。请注意,任何多余的字符都会留在输入流中。
另外,fflush 的行为没有为输入流定义2 - 没有好的、安全、便携的方法可以清除多余的输入,除非使用读取它getchar 或 fgetc。
- 这就是当您将
a 从 char [50][50] 更改为 char [50] 时收到错误消息的原因 - 在这种情况下,a[i] 的类型为 char,而不是 char *,并且值为a[i] 不是地址。
- Microsoft 的 Visual Studio C 编译器是一个明显的例外 - 它会从输入流中清除多余的输入。但是,这是特定于 MSVC 的,不能跨不同的编译器移植。就“flush”语义而言,该操作也有点荒谬。