【问题标题】:Reading strings with spaces from a file从文件中读取带空格的字符串
【发布时间】:2014-10-26 22:59:05
【问题描述】:

我正在做一个项目,但遇到了一个非常烦人的问题。我有一个文件,其中存储了我的帐户收到的所有消息。消息是这样定义的数据结构:

typedef struct _message{
char dest[16]; 
char text[512];
}message;

dest 是一个不能包含空格的字符串,这与其他字段不同。 字符串是使用fgets() 函数获取的,因此desttext 可以具有“动态”长度(从1 个字符到length-1 个合法字符)。请注意,在从标准输入检索到每个字符串后,我手动删除了换行符。

“收件箱”文件使用以下语法存储消息:

dest
text

例如,如果我收到 Marco 发来的一条消息,上面写着“你好,你好吗?”以及来自 Tarma 的另一条消息“你今天要去健身房吗?”,我的收件箱文件看起来像这样:

Marco
Hello, how are you?

Tarma
Are you going to the gym today?

我想从文件中读取用户名并将其存储在字符串 s1 中,然后对消息执行相同操作并将其存储在字符串 s2 中(然后重复操作直到 EOF),但由于 text 字段承认我不能真正使用的空间fscanf()

我尝试使用fgets(),但正如我之前所说,每个字符串的大小都是动态的。例如,如果我使用fgets(my_file, 16, username),它最终会读取不需要的字符。我只需要读取第一个字符串直到到达\n,然后读取第二个字符串直到到达下一个\n,这次包括空格。

知道如何解决这个问题吗?

【问题讨论】:

  • fgets 在换行符或 EOF 处停止。如果你已经剥离了新行,你不知道字符串的精确长度吗?
  • 你传递给fgets()的16是字符串的最大长度。 fgets() 在 (16-1) 字符或换行符或 EOF 处停止
  • @Tony 当我从文件中检索字符串时,我不知道确切的长度,我只知道字符串可以具有的最大长度(dest 为 16 个字符,文本为 512 个字符,包括 \0 ) 很抱歉让自己不清楚。
  • @Weather Vane 你是对的。也许我应该更改用于在文件中写入消息的语法...

标签: c fgets scanf


【解决方案1】:
#include <stdio.h>

int main(void){
    char username[16];
    char text[512];
    int ch, i;
    FILE *my_file = fopen("inbox.txt", "r");

    while(1==fscanf(my_file, "%15s%*c", username)){
        i=0;
        while (i < sizeof(text)-1 && EOF!=(ch=fgetc(my_file))){
            if(ch == '\n' && i && text[i-1] == '\n')
                break;
            text[i++] = ch;
        }
        text[i] = 0;
        printf("user:%s\n", username);
        printf("text:\n%s\n", text);
    }
    fclose(my_file);
    return 0;
}

【讨论】:

  • 我对你的实现有一点疑问,“%*c”是否告诉 fscanf() 忽略 15 个字符后遇到的最后一个字符?
  • @Toxicroak 15 是要读取的最大字符数。实际读取的并不总是 15 个字符。 %*c 跳过名称后的换行符。
【解决方案2】:

由于每个字符串的长度是动态的,如果我是你,我会先读取文件以查找每个字符串的大小,然后创建一个字符串长度值的动态数组。

假设你的文件是:

A long time ago
in a galaxy far,
far away....

所以第一行长度是15,第二行长度是16,第三行长度是12

然后创建一个动态数组来存储这些值。

然后,在读取字符串时,将第二个参数作为第二个参数传递给 fgets 数组的相应元素。喜欢fgets (string , arrStringLength[i++] , f);

当然,这样一来,您就必须读取文件两次。

【讨论】:

    【解决方案3】:

    只要你小心,你可以很容易地使用fgets()。此代码似乎有效:

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    enum { MAX_MESSAGES = 20 };
    
    typedef struct Message
    {
        char dest[16]; 
        char text[512];
    } Message;
    
    static int read_message(FILE *fp, Message *msg)
    {
        char line[sizeof(msg->text) + 1];
        msg->dest[0] = '\0';
        msg->text[0] = '\0';
        while (fgets(line, sizeof(line), fp) != 0)
        {
            //printf("Data: %zu <<%s>>\n", strlen(line), line);
            if (line[0] == '\n')
                continue;
            size_t len = strlen(line);
            line[--len] = '\0';
            if (msg->dest[0] == '\0')
            {
                if (len < sizeof(msg->dest))
                {
                    memmove(msg->dest, line, len + 1);
                    //printf("Name: <<%s>>\n", msg->dest);
                }
                else
                {
                    fprintf(stderr, "Error: name (%s) too long (%zu vs %zu)\n",
                            line, len, sizeof(msg->dest)-1);
                    exit(EXIT_FAILURE);
                }
            }
            else
            {
                if (len < sizeof(msg->text))
                {
                    memmove(msg->text, line, len + 1);
                    //printf("Text: <<%s>>\n", msg->dest);
                    return 0;
                }
                else
                {
                    fprintf(stderr, "Error: text for %s too long (%zu vs %zu)\n",
                            msg->dest, len, sizeof(msg->dest)-1);
                    exit(EXIT_FAILURE);
                }
            }
        }
        return EOF;
    }
    
    int main(void)
    {
        Message mbox[MAX_MESSAGES];
        int n_msgs;
    
        for (n_msgs = 0; n_msgs < MAX_MESSAGES; n_msgs++)
        {
            if (read_message(stdin, &mbox[n_msgs]) == EOF)
                break;
        }
    
        printf("Inbox (%d messages):\n\n", n_msgs);
        for (int i = 0; i < n_msgs; i++)
            printf("%d: %s\n   %s\n\n", i + 1, mbox[i].dest, mbox[i].text);
    
        return 0;
    }
    

    阅读代码将处理名字之前、名字和文本之间以及姓氏之后的(多个)空行。它决定是否存储刚刚在消息的desttext 部分中读取的行的方式有点不寻常。它使用memmove(),因为它确切地知道要移动多少数据,并且数据以空值结尾。如果您愿意,您可以用strcpy() 替换它,但它应该更慢(可能不会明显更慢),因为strcpy() 必须在复制时测试每个字节,但memmove() 不需要。我使用memmove() 因为它总是正确的; memcpy() 可以在此处使用,但仅在您保证不重叠时才有效。安全总比后悔好;有很多软件错误而不会冒额外风险。您可以决定错误退出是否合适——这对于测试代码来说很好,但在生产代码中不一定是一个好主意。您可以决定如何处理“0 条消息”、“1 条消息”和“2 条消息”等。

    您可以轻松地修改代码以对消息数组使用动态内存分配。很容易将消息读入main() 中的一个简单的Message 变量,并在收到完整消息时安排复制到动态数组中。另一种方法是“冒险”过度分配数组,尽管这不太可能是一个主要问题(无论如何,您都不会一次增加一个数组,以避免在每次分配期间必须移动内存时出现二次行为) .

    如果要为每条消息处理多个字段(例如,接收日期和读取日期),那么您可能需要重新组织代码,可能使用另一个函数。

    请注意,代码避开了保留的命名空间。诸如_message 之类的名称是为“实现”保留的。诸如此类的代码不是实现(C 编译器及其支持系统的)的一部分,因此您不应创建以下划线开头的名称。 (这过度简化了约束,但只是略微简化,并且比更细微的版本更容易理解。)

    代码注意不要多次写入任何幻数。

    样本输出:

    Inbox (2 messages):
    
    1: Marco
       How are you?
    
    2: Tarma
       Are you going to the gym today?
    

    【讨论】:

    • 很详细的解释,对我帮助很大。谢谢!!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-03-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-05-06
    • 1970-01-01
    • 2011-09-11
    相关资源
    最近更新 更多