【问题标题】:scanf problem when adding multiple strings添加多个字符串时的scanf问题
【发布时间】:2021-12-04 09:56:10
【问题描述】:

所以我有一个代码,它可以输入任意数量的任意长度的行。每行通常包含一个或多个由空格分隔的单词。我必须输出那个字符串。

所以这是我的代码。我是 C 的新手,不要对我的 哑代码;)

#include <stdio.h>
#include <stdlib.h>

int main () {
    int i = 1;
    char **list = malloc(1000 * sizeof(char*));
    while(!feof(stdin)){
        list[i] = malloc(1000 * sizeof(char));
        printf("Enter string with num %d: ", i);
        scanf("%99[^\n]", list[i]);
        i++;
    }
    printf("\n");
    for (int j = 1; j < i; j++)
        printf("Your strings by num %d: %s\n", j, list[j]);

    return 0;
}

但我的 scanf 有问题,我无法在空格后继续输入。 我认为我的问题是

scanf("%99[^\n]", list[i]);

除此之外,我不能使用gets()、getchar() 系列以及规范%c 和%m。

【问题讨论】:

标签: c scanf


【解决方案1】:

使用%99[^\n] 扫描直到遇到\n。所以第一行读起来很顺利。但是由于\n没有被扫描,它仍然在stdin的未读部分。下一次扫描时,又遇到 '\n' 并且实际上扫描了一个空字符串。所以使用%99[^\n]%*c %*c 部分意味着你忽略了一个字符。

【讨论】:

  • 在问题中,OP 声明他们不允许使用 %c 格式说明符。这可能是对他们家庭作业的限制。但是,我仍然支持答案,因为约束没有意义,除非 OP 预计使用 %s 而不是 %[set]
  • 从技术上讲,'*' 是“赋值抑制字符”,当它与 转换说明符 配对时,会导致 scanf() 读取并丢弃匹配说明符的字符。
【解决方案2】:

你已经做了一件肯定会导致你的逻辑出现问题的第一件事。您将 i 混合为从 1 开始(用于计数),但应该从 0 开始进行索引。在 C 中,所有数组和存储都是从零开始的。第一个元素是元素0。当您开始将基于 1 的计数器与基于 0 的索引混合使用时,您的逻辑会变得复杂,并且必须考虑变量用作计数器或索引的每个实例中的变化——不惜一切代价尝试避免这种情况。

您将其用作索引,而不是将 int i = 1; 初始化为 int i = 0;。当您需要作为计数器输出时,只需使用i + 1。在您的情况下,这意味着您正在分配 1000 指针,但您从不使用第一个指针。 (你可以这样做——但这只是草率)始终从0 索引并通过添加到索引来根据需要调整输出编号。 (也不仅仅是使用i 来跟踪分配的指针数量,而是一个更具描述性的变量名称,例如n_pointersnptrs

正如评论中提到的,由于您从键盘获取用户输入,因此在开始时预分配 1000 个指针没有任何好处 - 没有提高程序效率。在用户键入"cat" 的时间里,您可以重新分配一个新指针并为存储分配 1000 次,而不会引入任何延迟。

这里更好的方法是简单地声明一个固定缓冲区(字符数组)以用于所有输入,然后在您验证接收到好的输入之后,只需 realloc() 一个新指针并为输入分配存储空间。这样你就不会过度分配指针。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MAXC 1000       /* if you need a constant, #define one (or more) */

int main () {
    
    size_t nptrs = 0;       /* counter for no. pointers allocated */
    char **list = NULL,     /* list and fixed buffer for read */
         buf[MAXC];
    ...

由于您显然无法使用scanf()(如 cmets 中所述),因此请花一个小时阅读手册页 - 稍后它将为您节省 100 小时的痛苦。 scanf() 对新的 C 程序员来说充满了陷阱,比如知道哪些转换说明符会丢弃前导空格,哪些不会,以及......还有 50 个类似的陷阱。

在这里,您想用scanf() 读取空格分隔的单词,因此您将使用%[...] 转换说明符来读取所有字符,不包括'\n' 字符。 (课程,%[...]%c%n 是不丢弃前导空格的转换)。因此,在您的情况下,要丢弃前导空格,您需要在 " %[^\n]" 之前包含一个空格。

您还需要知道所消耗的字符数,以便您可以检查您的 999 field-width 限制,以了解您刚刚尝试阅读的行是否太长而无法容纳。您可以使用伪转换%n 来获取消耗的字符数,直到它出现在您的格式字符串中。

由于您的读取末尾不包含 '\n' 字符,如果您的缓冲区已填充 999 字符(加上 nul 终止字符),则该行中的其他字符仍然未读,您需要阅读和丢弃,以便您的下一次阅读将从下一行开始。

(您通常会使用getchar() 进行循环,直到找到'\n',但由于您无法使用它,您将再次使用scanf(),但不会丢弃前导空格)

当您循环收集用户输入时(尤其是需要特定输入(如整数值)时),您将在输入例程周围使用连续循环来要求用户提供所需的输入类型以及任何输入在特定的数值范围内。当您收到并验证提供了所需的输入时,您只需 break 您的读取循环。否则,您只需再次循环,直到获得所需的内容。验证任何用户输入的关键是检查返回(如果需要,检查值是否在限制范围内)。

如果行太长,将读取到固定缓冲区、验证和丢弃字符的方法,您可以这样做:

    ...
    puts ("input\n");
    while (1) { /* loop continually until manual EOF */
        int nchar, rtn;     /* no. of chars consumed, scanf return */
        
        printf ("Enter string with num %zu: ", nptrs + 1);  /* prompt */
        rtn = scanf (" %999[^\n]%n", buf, &nchar);          /* save return */
        if (rtn == EOF) {                                   /* check EOF */
            puts ("EOF\n\noutput\n");
            break;
        }
        if (nchar == 999) { /* check line-too-long */
            fputs ("errro: line exceeds MAXC chars.\n", stderr);
            while (nchar == 999) {  /* loop discarding remainder of line */
                if (scanf ("%999[^\n]%n", buf, &nchar) != 1) {
                    goto getnextline;   /* skip over allocation/storage */
                }
            }
        }
        ...

现在为每个输入重新分配一个额外的指针和存储,并从固定缓冲区复制到分配的存储,您可以这样做:

        ...
        /* always realloc using temp pointer or risk memory leak */
        void *tmp = realloc (list, (nptrs + 1) * sizeof *list);
        if (!tmp) {
            perror ("realloc-list");
            break;
        }
        list = tmp;         /* assign realloc'ed block of ptrs to list */
        
        list[nptrs] = malloc (nchar + 1);           /* allocate for string */
        memcpy (list[nptrs], buf, nchar + 1);       /* don's scan for \n */
        
        nptrs += 1;         /* increment pointer count */
        getnextline:;
    }
    ...

剩下的就是输出你的行(并将你的索引移动到计数器以匹配你的输入提示)。由于您要结束程序,请不要忘记free() 每行的存储空间,然后在最后释放指针。是的,当程序退出时会发生这种情况,但您不会总是在main() 中分配内存,并且未能释放您在函数中分配的内存将在您的程序中造成内存泄漏。所以早点养成好习惯。始终跟踪您的内存分配和free() 不再需要的内存,例如

    ...
    for (size_t j = 0; j < nptrs; j++) {
        printf("Your strings by num %zu: %s\n", j + 1, list[j]);
        free (list[j]);     /* free storage for string */
    }
    free (list);            /* don't forget to free pointers */
}

就是这样。这将获取所需的所有输入,消除对可以输入的行数的任何限制(直到虚拟内存的限制),输出所有存储的行并在退出之前释放所有分配的内存。

示例使用/输出*

$  ./bin/scanflines
input

Enter string with num 1: string with   1
Enter string with num 2:       string with   2
Enter string with num 3:      string with   3
Enter string with num 4:     string with   4
Enter string with num 5:    string with   5
Enter string with num 6:   string with   6
Enter string with num 7:  string with   7
Enter string with num 8: string with   8
Enter string with num 9: EOF

output

Your strings by num 1: string with   1
Your strings by num 2: string with   2
Your strings by num 3: string with   3
Your strings by num 4: string with   4
Your strings by num 5: string with   5
Your strings by num 6: string with   6
Your strings by num 7: string with   7
Your strings by num 8: string with   8

内存使用/错误检查

在您编写的任何动态分配内存的代码中,对于分配的任何内存块,您都有 2 个职责:(1)始终保留指向起始地址的指针内存块,因此 (2) 当不再需要它时可以释放

您必须使用内存错误检查程序来确保您不会尝试访问内存或写入超出/超出分配块的边界,尝试读取或基于未初始化的值进行条件跳转,最后, 以确认您已释放所有已分配的内存。

对于 Linux,valgrind 是正常的选择。每个平台都有类似的内存检查器。它们都易于使用,只需通过它运行您的程序即可。

$ valgrind ./bin/scanflines
==7141== Memcheck, a memory error detector
==7141== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==7141== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==7141== Command: ./bin/scanflines
==7141==
input

Enter string with num 1: string with   1
Enter string with num 2:       string with   2
Enter string with num 3:      string with   3
Enter string with num 4:     string with   4
Enter string with num 5:    string with   5
Enter string with num 6:   string with   6
Enter string with num 7:  string with   7
Enter string with num 8: string with   8
Enter string with num 9: EOF

output

Your strings by num 1: string with   1
Your strings by num 2: string with   2
Your strings by num 3: string with   3
Your strings by num 4: string with   4
Your strings by num 5: string with   5
Your strings by num 6: string with   6
Your strings by num 7: string with   7
Your strings by num 8: string with   8
==7141==
==7141== HEAP SUMMARY:
==7141==     in use at exit: 0 bytes in 0 blocks
==7141==   total heap usage: 18 allocs, 18 frees, 2,492 bytes allocated
==7141==
==7141== All heap blocks were freed -- no leaks are possible
==7141==
==7141== For counts of detected and suppressed errors, rerun with: -v
==7141== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

始终确认您已释放已分配的所有内存并且没有内存错误。

这里有很多要消化的东西,所以花点时间确保你理解为什么每一行都是这样写的,以及它在做什么。如果您有任何问题,请在下方发表评论。

最后,要知道在这种情况下,将fgets() 转换为buffer 会比使用scanf() 更好和推荐的方法。但由于看起来这是禁区,可以使用scanf() 完成,并且是一个很好的学习练习——但如果没有学习练习,fgets() 是更好的选择。

完整计划

为了方便:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MAXC 1000       /* if you need a constant, #define one (or more) */

int main () {
    
    size_t nptrs = 0;       /* counter for no. pointers allocated */
    char **list = NULL,     /* list and fixed buffer for read */
         buf[MAXC];
    
    puts ("input\n");
    while (1) { /* loop continually until manual EOF */
        int nchar, rtn;     /* no. of chars consumed, scanf return */
        
        printf ("Enter string with num %zu: ", nptrs + 1);  /* prompt */
        rtn = scanf (" %999[^\n]%n", buf, &nchar);          /* save return */
        if (rtn == EOF) {                                   /* check EOF */
            puts ("EOF\n\noutput\n");
            break;
        }
        if (nchar == 999) { /* check line-too-long */
            fputs ("errro: line exceeds MAXC chars.\n", stderr);
            while (nchar == 999) {  /* loop discarding remainder of line */
                if (scanf ("%999[^\n]%n", buf, &nchar) != 1) {
                    goto getnextline;   /* skip over allocation/storage */
                }
            }
        }
        /* always realloc using temp pointer or risk memory leak */
        void *tmp = realloc (list, (nptrs + 1) * sizeof *list);
        if (!tmp) {
            perror ("realloc-list");
            break;
        }
        list = tmp;         /* assign realloc'ed block of ptrs to list */
        
        list[nptrs] = malloc (nchar + 1);           /* allocate for string */
        memcpy (list[nptrs], buf, nchar + 1);       /* don's scan for \n */
        
        nptrs += 1;         /* increment pointer count */
        getnextline:;
    }
    
    for (size_t j = 0; j < nptrs; j++) {
        printf("Your strings by num %zu: %s\n", j + 1, list[j]);
        free (list[j]);     /* free storage for string */
    }
    free (list);            /* don't forget to free pointers */
}

【讨论】:

  • 谁否决了这个答案,请正直解释为什么?答案没有一个不正确的部分。
猜你喜欢
  • 1970-01-01
  • 2017-03-10
  • 2017-02-11
  • 1970-01-01
  • 2013-01-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-04-07
相关资源
最近更新 更多