【问题标题】:How to scan a user defined number of strings in C?如何在C中扫描用户定义的字符串数量?
【发布时间】:2014-03-27 11:06:58
【问题描述】:

如果用户输入5 作为第一个输入,那么程序应该继续扫描 5 个字符串。

我试过了:

int n;
scanf("%d",&n);
char a[100][10];

for (int i = 0; i < n; i++) {
    scanf("%s", &a[i][10]);
}

但这只会扫描一个单词然后退出。

【问题讨论】:

  • 更改为scanf("%s", a[i]);
  • a[i][10] 越界,char a[100][10]; 表示a[i] 的最高索引为9。鉴于您的scanf 格式指定了一个字符串,您要传递&amp;a[i]
  • 在这个问题中,字符串长度最大值可以是 10,n 是字符串的数量??我的意思是创建一个矩阵,以便 a[0][10] 是第一个字符串,与其他字符串类似,直到 n。如果我错了,请纠正我??
  • @VarunBehl: 如果你想让第一个字符串从a[0][10]开始,这和写a[1][0]是一样的,那么只需用int i = 1开始循环,然后在i &lt;=n运行它.请记住,虽然前 10 个字节(a[0][0]a[0][9] 在这种情况下永远不会被使用,并且只是浪费堆栈空间

标签: c input scanf


【解决方案1】:

鉴于此处给出的大多数答案在某种程度上存在缺陷:
例如,接受的答案指出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 转换为实际有用的代码很有用。

【讨论】:

  • 如果fgets 最多比参数少一个字符,为什么指定 9 而不是 10?
  • @TomFenech:nips,你说得对。对此感到抱歉,将编辑...这只是我的偏执促使我写了这些废话
  • 也许我的答案毕竟没有错。我正在编辑我的措辞以使其更清晰。
  • @TomFenech:就像我说的,我编辑了我的答案,并提到你的答案是准确的。但是,我在这里留下我的答案,因为scanf(" %d ", &amp;n); 不保证stdin 缓冲区将为空,并且因为我确实建议使用 VLA,所以实际的字符串缓冲区可以更大,因为 10 个字符缓冲区真的非常小
  • @TomFenech:你也一样......奇怪的是,OP 仍然接受包含完全错误信息的答案并选择忽略那些实际上包含更多相关信息的答案......
【解决方案2】:

scanf 在这种情况下很危险,因为可以输入比数组长度更长的字符串。我会推荐使用fgets:

#include <stdio.h>

int main()
{
  int n;
  scanf("%d ",&n); // note the added space
  char a[100][10];
  for (int i = 0; i < n; i++) {
      fgets(a[i], 10, stdin);
  }
  return 0;
}

fgets 的中间参数指定要读取的最大字符数。 fgets 将读取max - 1 字符并在字符串末尾添加\0(空字节)。使用这种方法可以保护您免受缓冲区溢出的影响,并确保您最终得到一个有效的字符串。

scanf 中添加的空格是为了让数字后面的换行符也被吞掉。

【讨论】:

  • @VarunBehl 没问题,很乐意提供帮助。
【解决方案3】:

&amp;a[i][10] 计算为a 中第 i 个数组的第 11 个元素的地址 - 但是由于您的数组只有十个元素,这最终会超出范围。

您需要第 i 个数组的地址。您可以通过a[i] 获得。

【讨论】:

  • &amp;a[i][10] 超出范围,但如果不是,它将评估为数组的第 第十一个 元素
  • 不完全是,a[i] 没有第十一个元素。您还没有提到当i 为 99 时,OP 的代码 (&amp;a[i][10]) 正在越界访问内存...
【解决方案4】:
char a[100][10];
_ _ _ _ _ _ _ _ _ _ 
_ _ _ _ _ _ _ _ _ _
_ _ _ _ _ _ _ _ _ _
         |
         |
    100 rOWS and 10 columns
         |
         |
_ _ _ _ _ _ _ _ _ _
_ _ _ _ _ _ _ _ _ _
_ _ _ _ _ _ _ _ _ _

当您执行scanf("%s", &amp;a[i][10]); 时,您从标准输入获取输入,只针对每一行的tenth 字符。

例如,如果n=3 和输入是 a、b、c 那么它在逻辑上看起来像这样(内存是连续的,但只是为了理解我正在使用行和列)。

_ _ _ _ _ _ _ _ _ a 
_ _ _ _ _ _ _ _ _ b
_ _ _ _ _ _ _ _ _ c

所以要输入一个完整的字符串,你应该只给出行的基地址。

scanf("%s", &a[i]);

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-01-28
    • 2014-11-22
    • 1970-01-01
    • 2012-10-14
    • 1970-01-01
    • 2014-10-26
    • 1970-01-01
    相关资源
    最近更新 更多