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

问题描述 投票:0回答:2

我正在编写一个程序,接收用户输入的个人详细信息,包括姓名、ID 和电话号码。我将我的 ID 和电话号码分别限制为最多 14 个和 13 个字符(不包括换行符)——例如

010203-1234567
为 ID 和
010-1111-2222
为电话号码。如果我理解正确的话,
fgets
应该在从用户输入的字符串中读取后自动添加一个换行符字节,而
fputs
应该在存储在
id
phoneNum
中的字符串末尾写入换行符,但是当尝试输入比允许的更多的字符时,例如
010203-12345678
对于 ID,以下
fputs
没有写出换行符。我如何确保即使用户输入的字符多于允许的字符数,换行符也由
fputs
写出?

以下是我的代码:

#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 为

010203-1234567
和电话号码为
010-1111-2222
时,我在 details.txt 中得到以下结果:

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

如何让代码写入 details.txt 如下:

#Name: John
#SSID: 010203-1234567
#Phone number: 010-1111-2222
c file-io io
2个回答
3
投票

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

没有。

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

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

是的,

fputs
将写入出现在指定字符串中的任何换行符。没有被打印是有力的证据表明没有人被读入你的
id
(和
phoneNum
)缓冲区。

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

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

您的

id
数组有 15
char
s 长,足以容纳 14 个字符的 ID 和一个空终止符。因此,
fgets
在复制第 14 个字符“7”后停止读取,并以缓冲区的最后一个字节终止字符串。您的
FlushInputBuf()
函数然后读取并丢弃行尾,直到并包括下一个换行符。不存储换行符。同样的事情也会发生在恰好 14 个字符的条目上。如果输入的 ID 比那个短,那么
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);

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


1
投票

除了@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;
}
© www.soinside.com 2019 - 2024. All rights reserved.