C - 使用 fgets 直到 newline/-1

C - Using fgets until newline/-1

所以我正在努力做到这一点,以便您可以将文本写入文件,直到您创建换行符或键入 -1。我的问题是,当您编写时,它会一直运行直到崩溃并给出错误 "Stack around the variable "inputChoice" was corrupted"

我认为问题在于程序不会停止接受 stdin 当您想停止输入 (-1, newline) 时,这会导致错误。我试过一个简单的 scanf 并且它有效,但你只能写一个字。没有空格,也不支持多行。这就是为什么我必须使用 fgets

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

C 字符串

C 字符串是一个字节序列。此序列 必须 以值 0 结尾。 序列中的每个值代表一个基于 ASCII 编码,例如 字符'a'是97,'b'是98,等等字符'[=19=]'有 值 0,它是确定字符串结尾的字符。 这就是为什么您经常听到 C 字符串以“\0”结尾。

在 C 中,您使用一个字符数组 (char string[], char string[SOME VALUE]) 来 保存一个字符串。对于长度为 n 的字符串,您需要一个维度为 n+1 的数组,因为 您还需要一个 space 作为终止 '[=19=]' 字符。

处理字符串时,你总是要考虑正确的类型, 无论您使用的是数组还是指针。指针 转换为 char 并不一定意味着您正在处理 C 字符串!

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

char inputChoice = 0;

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

I haven't changed much, got very demotivated after trying for a while. I changed the %s to an %c at scanf(" %c, &inputChoice) and that seems to have stopped the program from crashing.

说明还没有弄明白%s%c的区别。

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

的指针

man scanf

c

Matches a sequence of characters whose length is specified by the maximum field width (default 1); the next pointer must be a pointer to char, and there must be enough room for all the characters (no terminating null byte is added). The usual skip of leading white space is suppressed. To skip white space first, use an explicit space in the format.

忘了长度,现在不重要。 重要部分以粗体显示。对于格式 scanf("%c",函数 期望指向 char 的指针并且它不会写入终止 '[=19=]' 字符,它不会是 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 的 char 数组(即它包含 10 chars)。 scanf 将匹配单个字母并将其写在 c[0] 上。然而结果不会 一个 C 字符串,你不能将它传递给 puts 也不能传递给期望的其他函数 C 字符串(如 strcpy)。

%s 转换说明符字符告诉 scanf 它必须匹配一系列非白色-space 字符

man scanf

s Matches a sequence of non-white-space characters; the next pointer must be a pointer to the initial element of a character array that is long enough to hold the input sequence and the terminating null byte ('[=19=]'), which is added automatically.

这里的结果是保存了一个C-String。你也要有足够的 space 保存字符串:

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

如果字符串匹配9个或更少的字符,一切都会好起来的,因为 对于长度为 9 的字符串需要 10 spaces(永远不要忘记终止 '[=19=]')。如果字符串匹配超过 9 个字符,你将没有足够的 space 在缓冲区中并发生缓冲区溢出(访问超出大小)。 这是一个未定义的行为,任何事情都可能发生:你的程序可能 崩溃,你的程序可能不会崩溃,但会覆盖另一个变量,因此 扰乱你的程序流程,它甚至可以在某个地方杀死一只小猫,做 你真的想杀小猫吗?

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

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

inputChoice是一个char变量,它只能保存1个值。 &inputChoice 给你 inputChoice 变量的地址,但是 之后的字符超出范围,如果你 read/write 它,你将有一个 溢出,因此你杀死了一只小猫。即使您只输入 1 个字符,它也会 至少写 2 个字节,因为你只有 space 个字符,一只小猫会死。


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

从用户的角度:我为什么要输入文本行,可能是很多行文本 然后回答"No, I don't want to save the lines"。这没有意义 我.

在我看来你应该首先询问用户 he/she 是否想要保存 先输入,然后然后要求输入。如果用户不想保存 任何东西,那么要求用户输入任何东西是没有意义的 全部。但这只是我的意见。

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

#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] = '[=14=]';


        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或BUFFERLEN。再一次,那是我的 风格,但我认为这样做更好,因为即使你改变尺寸 通过更改宏或使用另一个显式大小 sizeof buffer 来调整缓冲区 将始终 return 正确的尺寸。请注意,这仅在 buffer是一个数组。

  • 函数 strdup returns a pointer 指向新字符串的指针 重复论证。它用于创建字符串的新副本。什么时候 使用这个函数,不要忘记你必须使用释放内存 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;
    }