代码之家  ›  专栏  ›  技术社区  ›  Cryptosyche

C-使用fgets直到换行/-1[关闭]

  •  1
  • Cryptosyche  · 技术社区  · 6 年前

    所以我试着让你可以将文本写入文件,直到你换行或键入-1。我的问题是,当你写的时候,它一直在运行,直到崩溃并给出错误 变量“inputChoice”周围的堆栈已损坏 .

    我认为问题是这个节目没有停止接受 标准DIN 当您要停止键入(-1,换行符)并且这会导致错误时。我试过用一个简单的scanf,它可以工作,但你只能写一个单词。没有空格,也不支持多行。这就是为什么我必须使用 fgets

    1 回复  |  直到 6 年前
        1
  •  4
  •   Pablo    6 年前

    根据您的评论,我假设C中有一些基本概念 你还没有完全理解。

    C字符串

    C字符串是字节序列。此序列 必须 以值0结束。 序列中的每个值表示一个基于 ASCII 编码,例如 性格 'a' 是97, 'b' 是98等字符 '\0' 有 值0,该字符决定字符串的结尾。 这就是为什么您经常听到C字符串以“\0”结尾的原因。

    在C中,使用字符数组( char string[] , char string[SOME VALUE] )至 保存字符串。对于一串长度 n ,您需要一个维度数组 n+1 因为 您还需要一个空间用于终止 '\0' 性格

    在处理字符串时,必须始终考虑正确的类型, 您使用的是数组还是指针。指针 到char并不一定意味着您正在处理一个C字符串!

    我为什么要告诉你这些?因为:

    char inputChoice = 0;
    
    printf("Do you wish to save the Input? (Y/N)\n");
    scanf("%s", &inputChoice);
    

    我没什么变化,在尝试了一段时间后,我变得非常缺乏动力。 我改变了 %s %c scanf(" %c, &inputChoice) 还有那个 似乎阻止了程序崩溃。

    这表明他们还不了解 %s %c类 .

    这个 %c类 转换说明符字符指示 scanf 它必须匹配单个字符,并且它需要一个指向 char .

    曼斯坎夫

    c

    匹配长度由最大字段指定的字符序列 宽度(默认值为1); 下一个指针必须是 指向char的指针,并且必须有足够的空间容纳所有字符 (未添加终止空字节)。 通常跳过 前导空格被抑制。要首先跳过空白,请在格式中使用显式空格。

    忘了长度吧,现在不重要了。 重要部分用粗体表示。对于格式 scanf("%c" ,函数 需要指向的指针 烧焦 它不会写终止 '\0' 字符,它将不是C字符串。如果你想读一个字母和一个 仅限信函:

    char c;
    scanf("%c", &c);
    
    // also possible, but only the first char
    // will have a defined value
    char c[10];
    scanf("%c", c);
    

    第一个很容易理解。第二个更有趣:这里 您有一个数组 烧焦 尺寸为10(即它能容纳10 烧焦 s) 。 scanf公司 将匹配一封信并将其写在 c[0] . 但结果不会是 C字符串,不能传递给 puts 也不适用于预期 C字符串(如 strcpy ).

    这个 %s 转换说明符字符指示 scanf公司 它必须匹配一系列非空白字符

    曼斯坎夫

    s 匹配一系列非空白字符; 下一个指针必须是 指向字符数组的初始元素的指针,该元素的长度足以 保持输入序列和终止空字节( '\0' ),已添加 自动地

    这里的结果将是保存一个C字符串。你也必须有足够的 保存字符串的空间:

    char string[10];
    scanf("%s", string);
    

    如果字符串匹配9个或更少的字符,一切都会很好,因为 对于长度为9的字符串,需要10个空格(不要忘记终止 '\0' ). 如果字符串匹配的字符数超过9个,您将没有足够的字符 缓冲区中的空间和缓冲区溢出(访问超出大小)发生。 这是一种未定义的行为,任何事情都可能发生:您的程序可能 崩溃,您的程序可能不会崩溃,但会覆盖另一个变量,因此 控制你程序的流程,甚至可能在某处杀死一只小猫,是吗 你真的想杀小猫?

    那么,你明白为什么你的代码错了吗?

    char inputChoice = 0;
    scanf("%s", &inputChoice);
    

    inputChoice 是一个 烧焦 变量,它只能保存 1. 价值 &inputChoice 为您提供 输入选择 变量,但 char之后是越界的,如果您读/写它,您将有一个 溢出,所以你杀了一只小猫。即使只输入1个字符,也会 至少写2个字节,因为它只有一个字符的空间,小猫就会死。


    那么,让我们来谈谈您的代码。

    从用户的角度来看:为什么我要输入几行文字,可能是很多行文字 然后回答“不,我不想保留台词”。这没有意义 我

    在我看来,您应该首先询问用户是否要保存 首先输入,然后 然后 询问输入。如果用户不想保存 任何内容,那么要求用户在 全部的但这只是我的观点。

    如果你真的想坚持你的计划,那么你必须保存每一行 当用户结束输入数据时,您会询问并保存文件。

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    #define BUFFERLEN 1024
    
    
    void printFile () {
        int i;
        char openFile[BUFFERLEN];
        FILE *file;
    
        printf("What file do you wish to write in?\n");
        scanf("%s", openFile);
        getchar();
    
        file = fopen(openFile, "w");
        if (file == NULL) {
            printf("Could not open file.\n");
            return;
        }
    
        // we save here all lines to be saved
        char **lines = NULL;
        int num_of_lines = 0;
    
        char buffer[BUFFERLEN];
        printf("Enter an empty line of -1 to end input\n");
    
        // for simplicity, we assume that no line will be
        // larger than BUFFERLEN - 1 chars
        while(fgets(buffer, sizeof buffer, stdin))
        {
            // we should check if the last character is \n,
            // if not, buffer was not large enough for the line
            // or the stream closed. For simplicity, I will ignore
            // these cases
            int len = strlen(buffer);
            if(buffer[len - 1] == '\n')
                buffer[len - 1] = '\0';
    
    
            if(strcmp(buffer, "") == 0 || strcmp(buffer, "-1") == 0)
                break; // either an empty line or user entered "-1"
    
            char *line = strdup(buffer);
    
            if(line == NULL)
                break; // if no more memory
                       // process all lines that already have been entered
    
    
            char **tmp = realloc(lines, (num_of_lines+1) * sizeof *tmp);
    
            if(tmp == NULL)
            {
                free(line);
                break; // same reason as for strdup failing
            }
    
            lines = tmp;
    
            lines[num_of_lines++] = line;  // save the line and increase num_of_lines
        }
    
        char inputChoice = 0;
    
        printf("Do you wish to save the Input? (Y/N)\n");
        scanf("%c", &inputChoice);
        getchar();
    
        if (inputChoice == 'Y' || inputChoice == 'y') {
    
            for(i = 0; i < num_of_lines; ++i)
                fprintf(file, "%s\n", lines[i]); // writing every line
    
            printf("Your file has been saved\n");
            printf("Please press any key to continue");
            getchar();
        }
    
        // closing FILE buffer
        fclose(file);
    
        // free memory
        if(num_of_lines)
        {
            for(i = 0; i < num_of_lines; ++i)
                free(lines[i]);
            free(lines);
        }
    
    }
    
    int main(void)
    {
        printFile();
        return 0;
    }
    

    规范备注

    我使用了与您相同的代码作为我的基本代码,以便您可以发现 差异更快。

    • 我使用宏 BUFFERLEN 用于声明缓冲区的长度。那是 我的风格。
    • 看看 fgets 生产线:

      fgets(buffer, sizeof buffer, stdin)
      

      我在这里使用 sizeof buffer 而不是1024或 缓冲区 . 再说一次,那是我的 但是我认为这样做更好,因为即使你改变尺寸 通过更改宏或使用其他显式大小, 缓冲区大小 将始终返回正确的大小。请注意,这仅在以下情况下有效 buffer 是一个数组。

    • 功能 strdup 返回指针指向新字符串的指针 复制参数。它用于创建字符串的新副本。什么时候 使用此功能时,不要忘记必须使用 free() . strdup标准 不是标准库的一部分,它符合 至SVr4、4.3BSD、POSIX。1-2001. 如果您使用Windows(我不使用Windows, 我不熟悉Windows生态系统),此功能可能不 目前在这种情况下,您可以自己编写:

      char *strdup(const char *s)
      {
          char *str = malloc(strlen(s) + 1);
          if(str == NULL)
              return NULL;
          strcpy(str, s);
          return str;
      }