鉴于此处给出的大多数答案在某种程度上存在缺陷:
例如,接受的答案指出a[i][10] 访问行中的第十个字符。这是完全错误的。由于数组是零索引的,这实际上会尝试访问连续的 第十一个 字符,这很可能超出范围。
相同的答案显示了如何将二维数组划分为行和列。这也是错误的。数组,无论是 2D 还是 1D,都是内存中的一个连续块。只有一大排字符。你的数组a[100][10] 的实际布局看起来像这样。我刚刚注意到答案提到了这一点,但仍然:
|/a[0][0] ===============================\\===============>a[99][9]\|
|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|
连续只有 100*10 个字符。访问a[1][0] 与访问a[0][10] 或实际上是a[10] 相同。如果i 为99,则会出现使用a[i][10] 的越界风险,因为如您所见,数组中的最后一个字符位于a[99][9]。超出该点的所有内存都是禁区。
给出的示例,以行和列格式,输入为 a、b 和 c,然后看起来不像这样:
_ _ _ _ _ _ _ _ _ a
_ _ _ _ _ _ _ _ _ b
_ _ _ _ _ _ _ _ _ c
但是,它看起来像这样:
0 1 2 3 4 5 6 7 8 9
_ _ _ _ _ _ _ _ _ _
a _ _ _ _ _ _ _ _ _
b _ _ _ _ _ _ _ _ _
c _ _ _ _ _ _ _ _ _
@TomFenech 的答案要好得多,在这里使用fgets 是有道理的。但是,点 i 并交叉 T:fgets 不会读取以 nul 结尾的字符。该函数将最多读取 n-1 个字符,其中 n 是指定的最大长度,如文档所述:
fgets函数从stream指向的流中最多将n指定的字符数少1个字符读入s指向的数组中。在换行符(保留)之后或文件结尾之后不会读取其他字符。
但是,fgets 当且仅当遇到换行符或 EOF 时才会停止读取。遇到 NUL 字符后它不会停止。请记住这一点,但就您而言,不必担心。
但是,当使用fgets 时,您会发现大量调用fflush(stdin); 的sn-ps 来清理输入缓冲区。但是,这会调用未定义的行为,并且您最好先读取缓冲区,直到它首先为空。 Tom Fench 忽略了这一点,所以为了完整起见,我想我也会在这里提到它。除此之外,我认为他的回答是准确的。
因此,考虑到这一点,这是我的建议:
取消固定大小的缓冲区。 a 将包含多少个字符串取决于用户输入。这意味着数组本身应该是可变长度之一。值得庆幸的是,C(自 C99 起)正是您需要的东西:可变长度数组 - 或简称 VLA 在这里更可取。以下是我作为快速测试/概念证明所写的内容:
#include <stdio.h>
#include <stdlib.h>
int main ( void )
{
int i, n;
puts("How many words do you wish to enter?");
scanf(" %d", &n);//get size of input array
while((i=getchar()) != EOF && i != '\n');//"flush" stdin
char a[n][10];
printf("Please enter %d words now\n", n);
for (i=0;i<n;++i)
fgets(a[i], 9, stdin);//get 9 char input, not 10 (10th char is nul terminator)
for (i=0;i<n;++i)
printf("Line %d: %s\n", i+1, a[i]);//print each input line
return 0;//return
}
您可以使用 gcc 在 *NIX 系统上编译它:
$ gcc -std=c99 your_code.c -o progName
$ ./progName
然后运行它。它按预期工作。
请注意,每个条目的输入缓冲区只有 10 个字符长。这意味着像:"successful" 这样的词是不行的(它有 10 个字符长,但字符串也需要 NUL 终止字符)。如果我是你,我会选择为实际输入使用更大的缓冲区。
由于我们使用的是 VLA,因此默认情况下我们不再使用 1000 字节的堆栈空间(a[100][10] 使用了那么多)。如果 n 的值为 10,我们可以将缓冲区增大 100 个字符,但最终使用的内存不会比现在多。所以也许考虑使用:
char a[n][24];
for (i=0;i<n;++i)
fgets(a[i], 24, stdin);
理想情况下,您也应该在循环中的每个 fgets 调用之后清理 stdin 缓冲区:
int j;//for clearing the buffer
for (i=0;i<n;++i)
{
fgets(a[i], 24, stdin);
if (strlen(a[i]) >= 23)//buffer overflow averted, clear stdin
while ((j = getchar()) != EOF && j != '\n');//clear buffer
}
如果不这样做,并且将缓冲区设置为 10,您可能会遇到如下情况:
Please enter 2 words now
successful?
Line 1: successfu
Line 2: l?
完整的代码,现在看起来像这样:
#include <stdio.h>
#include <stdlib.h>
#include <string.h> //add this
int main ( void )
{
int i, j, n;//extra temp var
puts("How many words do you wish to enter?");
scanf(" %d", &n);
while((j=getchar()) != EOF && j != '\n');
char a[n][10];
printf("Please enter %d words now\n", n);
for (i=0;i<n;++i)
{
fgets(a[i], 10, stdin);
if (strlen(a[i] >= 9)//<-- added
while ((j=getchar()) != EOF && j != '\n');//added
}
for (i=0;i<n;++i)
printf("Line %d: %s\n", i+1, a[i]);
return 0;//return
}
不过,程序的编译和运行方式完全相同。
如果你想容纳更大的缓冲区,如果用户只想传递 2 个单词作为输入,你也可以使用 VLA:
#include <stdio.h>
#include <stdlib.h>
#include <string.h> //add this
//max buffer total 1000 chars is what you are using with a[100][10]
#define MAX_TOTAL_BUFFER 1000
//don't allow more than 80 chars per entry
#define MAX_SINGLE_BUFFER 80
//added for safety
#define MIN_SINGLE_BUFFER 10
int main ( void )
{
int i, j, n. buf_len;
puts("How many words do you wish to enter?");
scanf(" %d", &n);
buf_len = MAX_TOTAL_BUFFER/n; // int/int yields int 1000/3 == 333
if (buf_len > MAX_SINGLE_BUFFER)
buf_len = MAX_SINGLE_BUFFER;
else if (buf_len < MIN_SINGLE_BUFFER) //mind you, risk of stack overflow here
buf_len = MIN_SINGLE_BUFFER;
while((j=getchar()) != EOF && j != '\n');
char a[n][buf_len];
printf("Please enter %d words (max %d chars long)\n", n, buf_len-1);
for (i=0;i<n;++i)
{
fgets(a[i], buf_len, stdin);
if (strlen(a[i] >= buf_len)//<-- added
while ((j=getchar()) != EOF && j != '\n');//added
}
for (i=0;i<n;++i)
printf("Line %d: %s\n", i+1, a[i]);
return 0;//return
}
玩弄缓冲区的最大值和最小值,看看在堆栈最终溢出之前你能推多远。在从用户那里获得n 的值后,还要检查它的值。如果用户通过0,则该程序无法正确处理。再次提示用户输入正数、使用默认值或退出。
如果 n 为负数,则使用其绝对值(或乘以 -1),再次提示或退出……所有这些都有助于在交互式 CLI C 程序中进行很好的练习。分离逻辑并编写函数将证明对将这个 sn-p 转换为实际有用的代码很有用。