调用 fgets() 两次

Calling fgets() twice

提问人:mortenlund 提问时间:11/7/2023 最后编辑:Andrew Henlemortenlund 更新时间:11/8/2023 访问量:1133

问:

这个问题是从 Stack Overflow 迁移过来的,因为它可以在 Code Review Stack Exchange 上回答。18 天前迁移。

我是初学者。两次使用该函数时,我真的很难使用 fgets()。我在带有 VS Code 和 C++ 扩展的 Windows 上。我的测试程序正在工作,但恕我直言,对于如此简单的任务来说,它冗长而复杂。此外,我读过,使用 goto 是一种不好的做法。有没有更简单的解决方案?

#include <iostream>
#include <string.h>
using namespace std;
#define max 5

void killNL(char *str) {            
    int i = 0;                      
    while (*(str+i) != EOF){

            if (*(str+i) == '\n'){
            *(str+i) = '\0';
            *(str+i+1) = EOF;
            }
            i++;
    }
}

int main()
{
    size_t len = 0;
    char string[5] = {0};
    int c = 0;

    begin:
    printf("Enter string: ");
    fgets(string, max, stdin);
    killNL(string);                 //Is used only for the len count when length is 1 less
                                    //than max. It depends on the space that is left for
                                    //'\n' AND '\0' which raises the count to when the 
                                    //(getchar()) != gets activated. Which needs a leftover
                                    //'\n' in the buffer to not go into endless loop.

    len = strlen(string);

    for (int i = 0 ; i <= len ; i++) {
        if (string[i] == '\n'){ printf("\\n \n");}
        else if (string[i] == '\0'){ printf("\\0 \n");}
        else { printf("%c \n", string[i]);}
    }

    askAgain:
    if (len >= max - 1) {
        while (c = (getchar()) != '\n' && c != EOF) {}
    }

    printf("Continue? 'y'/'n' :");
    fgets(string , 3, stdin);

    if (*(string) == 'y'){
        goto begin;
    } else if (*(string) == 'n') {
        goto end;
    } else {
        printf("\nWrong answer!\n");
        goto askAgain;
    }
    
    end:
    printf("END!\n");

    return 0;
} 

我一直在这里搜索,在 Youtube 上,然后在一本书中我找到了 killNL 函数,它最终确实使它起作用了。 这个问题在 Stack Overflow 上有很多变体,与 sscanf 结合使用,与读取文件等结合使用。但不是这个变体,这就是为什么我发布它的原因,尽管它是一个旧主题。

C++ C

评论

7赞 Weather Vane 11/7/2023
这是 C++ 代码,使用类似 C 的函数,可以是 C++。但是在函数中,您正在寻找但这不是应该出现在字符串中的字符:它是文件读取函数返回的标志。killNL()EOF
5赞 Weather Vane 11/7/2023
如果该函数应该删除保留的尾随换行符,请参阅以下简单的单行解决方案: 从 fgets() 输入中删除尾随换行符killNL()fgets()
5赞 Jonathan Leffler 11/7/2023
使用在两个方面是有问题的:(1)它比使用更好,(2)将字符与EOF进行比较是非常可疑的(你应该寻找字符串的结尾,EOS,这是一个空字节,而不是EOF(通常是一个负整数值)。如果基类型是有符号的,则可以在有效字符上停止(在某些代码集中,该字符将是 ÿ — Unicode 中带有 diaeresis 的小拉丁字母 Y)。如果基类型为无符号,则永远不会匹配。在这两种情况下,正如所写的那样,代码崩溃的可能性大于行为的可能性。while (*(str+i) != EOF){str[i]*(str+i)'\0'-1charchar
2赞 vnp 11/7/2023
while (c = (getchar()) != '\n' && c != EOF)肯定是错的。第二个是放错地方的。应该是(你看到区别了吗?(while ((c = getchar()) != '\n' && c != EOF)
5赞 Toby Speight 11/7/2023
如果你真的像你说的那样在一本书中找到了这个功能,请销毁那本书,这样它就不会再伤害任何人了。比较或赋值 是绝对没有意义的,因为 是与任何有效字符不同的值。 (更不用说它用长手拼写索引算术的单调做法,而不是使用更容易理解的运算符)killNL()charEOFint[]

答:

12赞 David C. Rankin 11/7/2023 #1

好吧,对于初学者来说,你真的想决定你是用 C 还是 C++ 编码。你,但没有使用任何 iostream 函数(大概你考虑过使用或类似的东西)。您还可以指定 ,但这在 C 中无效。 规则:仅包含代码中使用的函数的必要标头。#include <iostream>std::coutusing namespace std;

我们假设你想用 C 语言编写代码,给定你选择的输入和输出函数。如果没有,请发表评论,我也很乐意帮助C++。通过代码工作:

  • max 5-- 不要在缓冲区大小上吝啬。虽然你会使用 if 用 C++ 编写,但在声明数组以保存 C 中的输入时,不要吝啬。虽然只会尝试写入字符数(减去为 nul 终止字符提供空间),但除其他外,您使用的目的是一次读取一整行输入,而不会在输入流中留下无关的字符。您可以通过提供足够大小的缓冲区来实现此目的。std::stringfgets()size1fgets()
  • 在接受输入时,如果输入失败流错误或手动(由用户按下(或在 Windows 上)生成),或者收到成功的输入,或者满足您的退出条件之一(例如,用户单独按下字符串表示他们已经完成了输入),您通常会连续循环,然后中断读取循环EOFCtrl + dCtrl + zEnter
  • 在 C 语言中,要从字符串末尾开始修剪 (例如 ),然后您只需调用有效地覆盖 用 nul 终止字符(这只是 ASCII'\n'fgets()fgets (string, MAXC, stdin)string[strcspn (string, "\n")] = 0;'\n''\0'0)
  • 在需要变量的范围内声明变量,以及
  • 使用得当绝对没有错。这是在单个表达式中打破嵌套循环的唯一方法,等等。goto

以此为背景,您可以简化和清理代码,如下所示。许多选择都取决于您,但这是一种通用的方法

  • 读取输入,
  • 检查输入是否有效,
  • 检查用户是否生成了 ,EOF
  • 检查用户是否完成了输入,
  • 检查输入是否与所需的字符串匹配,最后
  • 如果输入不是需要的,则再次循环。

(粗略解释一下你想做什么)

代码(带有附加注释)可以是:

// #include <iostream>  /* only include needed headers for functions used */
#include <stdio.h>      /* or <cstdio> for modern C++ */
#include <string.h>     /* or <cstring> for modern C++ */

// using namespace std; /* using nothing from std:: namespace */

#define MAXC 1024       /* don't SKIMP on buffer size, UPPERCASE defines */
#define ANS "guess"

int main()
{
    char string[MAXC] = {0};    /* declare string */
    
    /* read-loop continually, break on condition of your choice */
    while (1) {
        fputs ("\nEnter string: ", stdout); /* no conversion, fputs is fine */
        
        /* validate EVERY input based on return of read function */
        if (!fgets (string, MAXC, stdin)) {
            puts ("(user generated manual EOF)");
            return 0;
        }
        
        /* trim '\n' from string */
        string[strcspn (string, "\n")] = 0;
        
        /* exit if empty-string (user just pressed [Enter], or any condition) */
        if (*string == '\0') {
            puts ("(all done)");
            break;
        }
        
        /* check if correct answer ANS given */
        if (strcmp (string, ANS) == 0) {
            puts ("(correct!)");
            break;
        }
        
        puts ("(answer didn't match)");   /* otherwise, loop again */
    }
    
    puts ("(That's All Folks!)");
}

示例使用/输出

$ ./bin/read-loop

Enter string: my dog has fleas
(answer didn't match)

Enter string: why?
(answer didn't match)

Enter string: what if I guess?
(answer didn't match)

Enter string: guess
(correct!)
(That's All Folks!)

或使用以下命令取消:Ctrl + d

$ ./bin/read-loop

Enter string: (user generated manual EOF)

或者使用用户的退出条件,只需在提示符下单独按下,表明他们已经完成了猜测:Enter

$ ./bin/read-loop

Enter string: next we will just press Enter when prompted, to quit input
(answer didn't match)

Enter string:
(all done)
(That's All Folks!)

您可以根据需要添加(或删除)任意数量的读取循环退出条件。关键要点是:

  • 持续循环,直到用户提供所需的输入,
  • 总是在返回读取函数时退出读取循环,
  • 验证每个用户输入,
  • 提供所需的退出条件来控制读取循环的退出,以及
  • 根据需要处理发生的任何错误。

如果这是您需要的,请告诉我。如果没有,我很乐意为您提供进一步的帮助。

评论

0赞 chux - Reinstate Monica 11/7/2023
大量不必要的归零。我猜,对于学习者代码来说没问题。char string[MAXC] = {0};
1赞 David C. Rankin 11/7/2023
好电话。更具可读性。(并保留预期的访问,以提示 OP 进一步查看数组指针转换)。*string
0赞 mortenlund 11/8/2023
@DavidC.Rankin,我对你的回答真的很棒。谢谢。它在我的 maschine 上运行得很好,而且非常简单,只有一个变量!没有功能。非常翔实的评论。很明显,将业余爱好者与专业人士区分开来。我写的程序是为了练习,幸运的是,有这样的网站存在。我试图将 MAXC 减少到 6,但随后程序跳过了下一个 fget,因为 stdin 中隐藏了一个“\n”。我将测试 getchar() != ' \n' && EOF 函数以进一步测试。愿你安好。
1赞 David C. Rankin 11/8/2023
很高兴帮忙!用 10 种不同的方式重写它。这是很好的学习。在使用输入和小缓冲区时,我总是发现很有帮助的是写出缓冲区中每个字节的块(在一张真实的纸上)。当您使用填充缓冲区时,将该字符写在纸上的正确块中(不要忘记 and(相当于普通))。这教会您跟踪如何使用可用的存储。它构建了正确使用 C 语言的最重要技能之一。保护您的阵列/存储边界!祝你的编码好运!getchar()'\n''\0'0
0赞 mortenlund 11/8/2023 #2

这是 DavidC.Rankin 的代码,对溢出进行了修改。


    #include <stdio.h>     
    #include <string.h>     
    
    
    
    #define MAXC 6      //test with minimum storage
    #define ANS "guess"
    
    int c = 8;
    int d = 9;
    int e = 0;
    int len = 0;
    
    
    int main()
    {
        char string[MAXC] = {0};    
        
        /* read-loop continually, break on condition of your choice */
        while (1) {
            fputs ("\nEnter string: ", stdout); 
            
            /* validate EVERY input based on return of read function */
            if (c = !fgets (string, MAXC, stdin)) {     //c = 1 when EOF is entered, else 0.
                puts ("(user generated manual EOF)");   //ctrl + 'z' (manual EOF)
                return 0;
            }
            
            /* trim '\n' from string */
            string[strcspn (string, "\n")] = 0;     //compares string with \n and counts position.
    
            len = strlen(string);                   //needed when input is overflowing
            
            if (len >= (MAXC - 1)) {                //ditto
                 while ((d = getchar()) != '\n' && d != '\0') {}
            }
            
            /* exit if empty-string (user just pressed [Enter], or any condition) */
            if (*string == '\0') {
                puts ("(all done)");
                break;
            }
            
            /* check if correct answer ANS given */
            if (d = strcmp (string, ANS) == 0) {
                puts ("(correct!)");
                break;
            }
            
            puts ("(answer didn't match)");   /* otherwise, loop again */
        }
        
        puts ("(That's All Folks!)");
    
        return 0;
    }

评论

1赞 David C. Rankin 11/8/2023
干得好。您可以同时修剪 AND 保存字符串长度。请参见 man 3 strspn 的手册页。现在只需 do Now 保存 after the has been trimed 的字符串长度(注意:在 for proper operator-precedence 内的赋值周围)用作索引。\nstrcspn()string[(len = strcspn (string, "\n"))] = 0;lenstring'\n':)(...)string[(...)]...len