fgets 读入的字符串不会用 fputs 编写换行符

String read in by fgets doesn't write newline with fputs

提问人:Kim Minseo 提问时间:4/4/2023 最后编辑:chqrlieKim Minseo 更新时间:4/4/2023 访问量:74

问:

我正在编写一个程序,该程序可以接收用户对个人详细信息的输入,其中包括姓名、ID 和电话号码。我将我的 ID 和电话号码限制为最多 14 个和 13 个字符(不包括换行符字符) - 例如 用于 ID 和电话号码。如果我理解正确的话,应该在从用户输入中读取字符串后自动添加换行符字节,并且应该在存储在 和 中的字符串末尾写入换行符,但是当尝试输入的字符多于允许的字符时,例如 对于 ID,以下内容不会按应有的方式写出换行符。如何确保即使用户输入的字符数超过允许的字符数,换行符也由 ?010203-1234567010-1111-2222fgetsfputsidphoneNum010203-12345678fputsfputs

以下是我的代码:

#include <stdio.h>

void FlushInputBuf(void)
{
    while (getchar() != '\n');
}

int main(void)
{
    FILE *fp = fopen("details.txt", "wt");
    if (fp == NULL)
    {
        puts("File open failed.\n");
        return -1;
    }
    char name[20];
    char id[15];
    char phoneNum[14];

    printf("Enter name: ");
    fgets(name, sizeof(name), stdin);
    printf("Enter ID: ");
    fgets(id, sizeof(id), stdin);
    FlushInputBuf();
    printf("Enter phone number: ");
    fgets(phoneNum, sizeof(phoneNum), stdin);

    fputs("#Name: ", fp);
    fputs(name, fp);
    fputs("#SSID: ", fp);
    fputs(id, fp);
    fputs("#Phone number: ", fp);
    fputs(phoneNum, fp);
  
    fclose(fp);

    return 0;
}

我假设名称的输入不会超过 20。 当我输入要成为的ID和要成为的电话号码时,我在details.txt中得到以下结果:010203-1234567010-1111-2222

#Name: John
#SSID: 010203-1234567#Phone number: 010-1111-2222

如何获取要写入details.txt的代码,如下所示:

#Name: John
#SSID: 010203-1234567
#Phone number: 010-1111-2222
C 文件- IO

评论

0赞 S_IROTH 4/4/2023
最后一个字符将是 \0。它将覆盖输入的 \n。只是使 ID 1 个字符更大。
0赞 Retired Ninja 4/4/2023
char id[15];可以打孔 14 个字符和 0 个终结符。没有换行符的余地。通常,您会从任何读取的字符串中剥离换行符,然后假设它不存在,然后格式化输出。fgets
4赞 Jonathan Leffler 4/4/2023
fgets()不会“自动添加换行符”——如果它在存储数据的空间用完之前读取换行符,它将包含它读取的换行符,但如果没有足够的空间,它将在没有剩余空间时停止读取——可能会在输入中留下一个换行符(可能在行尾之前有其他未读字符)。
0赞 tadman 4/4/2023
退货时会发生什么?getchar()EOF

答:

4赞 John Bollinger 4/4/2023 #1

如果我理解正确的话,fgets 应该在从用户输入中读取字符串后自动添加换行符

不。 将字符复制到缓冲区中,直到它用完空间(同时为字符串终止符保留空间),或者它复制了换行符。它不会添加任何尚未在输入中的换行符,并且(因此)它无法确保字符串末尾有换行符。通常有一个,但如果你的缓冲区对于整条线来说太小,那么就不会有。这是判断你是否得到一条完整的线路的主要方法。fgets()

fputs 应该在存储在 id 和 phoneNum 中的字符串的末尾写入换行符

是的,将写入指定字符串中出现的任何换行符。没有打印任何内容是强有力的证据,表明没有内容被读入您的(和)缓冲区。fputsidphoneNum

当尝试输入超过允许的字符数时,例如“010203-12345678”作为 ID,以下 fputs 不会按应有的方式写出换行符。

它不会像预期的那样打印换行符。但它正在做它应该做的事情。

您的数组长度为 15 秒,足以容纳 14 个字符的 ID 和一个 null 终止符。因此,在复制第 14 个字符“7”后停止读取,并使用缓冲区的最后一个字节终止字符串。然后,您的函数读取并丢弃该行的尾部,直到并包括下一个换行符。不存储换行符。同样的事情也会发生在正好是 14 个字符的条目上。如果输入的 ID 短于此值,则呼叫将强制您在提示输入电话号码之前键入第二个换行符(并将丢弃您在此之前键入的任何其他内容)。idcharfgetsFlushInputBuf()FlushInputBuf()

一个很好的方法是

  1. 提供一个至少足够长的缓冲区以容纳预期的输入,加上换行符字符串终止符

    #define MAX_ID_LENGTH 14
    // ...
    char id[MAX_ID_LENGTH + 2];
    

    有时,允许比您实际预期的多一点是有利的。

  2. 调用后,检查缓冲区中的换行符。仅当存在任何换行符时,才使用输入行的尾部 -- 也就是说,如果尚未读取换行符。fgets()

    size_t newline_pos = strcspn(id, "\n");
    if (id[newline_pos] != '\0') {
        assert(id[newline_pos] == '\n');
        FlushInputBuf();
    }
    
  3. 验证或至少修复输入。特别是,删除换行符(如果存在),因为它实际上不是 ID 的一部分。

    id[newline_pos] = '\0';
    

    拒绝或截断过长的 ID。

    // maybe:
    id[MAX_ID_LENGTH] = '\0';
    

    执行任何其他适当的验证。

  4. 在需要的位置手动将换行符打印到输出中。在您的特定情况下,我将使用一个调用来代替每对调用,并在格式中适当放置换行符。fprintf()fputs()

    fprintf(fp, "#SSID: %s\n", id);
    

    但是,如果您必须仅使用,则可以改为添加 .fputs()fputs("\n", fp);

    如果您不关心短条目的影响(您应该这样做),那么这一步本身就足够了。

2赞 chqrlie 4/4/2023 #2

除了 @JohnBollinger 的出色响应之外,这里还有一个修改后的代码版本,使用辅助功能来控制用户输入:

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

int input_string(const char *prompt, char *dest, int size) {
    int c;      // byte read from stdin
    int i = 0;  // index into dest
    int n = 0;  // number of bytes read excluding the newline

    printf("%s", prompt);
    /* ensure prompt is visible (necessary on some older systems) */
    fflush(stdout);

    /* read all bytes from the user input line */
    while ((c = getchar()) != EOF && c != '\n') {
        if (i + 1 < size) {
            /* append the character to dest, space permitting */
            dest[i++] = (char)c;
        }
        n++;
    }
    if (size > 0) {
        // set the null terminator
        dest[i] = '\0';
    }
    if (n >= size) {
        printf("discarded %d extra characters\n", n - size - 1);
    }
    if (c == EOF) {
        if (i == 0)
            printf("premature end of file\n");
            return EOF;
        } else {
            printf("missing newline at end of file\n");
        }
    }
    /* return the number of characters stored into dest */
    return i;
}

int main(void) {
    FILE *fp = fopen("details.txt", "w");
    if (fp == NULL) {
        fprintf(stderr, "Failed to open %s: %s\n", "details.txt",
                strerror(errno));
        return 1;
    }
    char name[20];
    char id[15];
    char phoneNum[14];

    if (input_string("Enter name: ", name, sizeof(name)) < 0
    ||  input_string("Enter ID: ", id, sizeof(id)) < 0
    ||  input_string("Enter phone number: ", phoneNum, sizeof(phoneNum)) < 0) {
        fprintf(stderr, "Missing input\n");
        fclose(fp);
        return 1;
    }
    fprintf(fp, "#Name: %s\n", name);
    fprintf(fp, "#SSID: %s\n", id);
    fprintf(fp, "#Phone number: %s\n", phoneNum);

    fclose(fp);
    return 0;
}