从文件中读取带空格的字符串

时间:2014-10-26 22:59:06

标签: c fgets scanf

我正在研究一个项目,我刚刚遇到了一个非常烦人的问题。我有一个文件存储我的帐户收到的所有邮件。消息是以这种方式定义的数据结构:

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

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

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

dest
text

所以,例如,如果我有一条来自Marco的消息说“你好,你好吗?”还有来自Tarma的另一条消息说:“你今天去健身房吗?”,我的收件箱文件看起来像这样:

Marco
Hello, how are you?

Tarma
Are you going to the gym today?

我想从文件中读取用户名并将其存储在字符串s1中然后对消息执行相同操作并将其存储在字符串s2中(然后重复操作直到EOF),但是从{{1}开始}字段允许空格我无法真正使用text

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

关于如何解决这个问题的任何想法?

3 个答案:

答案 0 :(得分:2)

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

假设您的文件是:

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

因此第一行长度为15,第二行长度为16,第三行长度为12

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

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

但是这样你当然必须两次阅读你的文件。

答案 1 :(得分:2)

#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;
}

答案 2 :(得分:1)

只要您小心,就可以轻松使用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()可以在这里使用,但只有在保证不重叠时才有效。比抱歉更安全;有很多软件错误,没有额外的风险。您可以决定错误退出是否合适 - 它适用于测试代码,但在生产代码中不一定是个好主意。您可以决定如何处理&#39; 0消息&#39; vs&#39; 1消息&#39; vs&#39; 2条消息&#39;等

您可以轻松修改代码,以便为消息数组使用动态内存分配。很容易将消息读入Message中的简单main()变量,并在收到完整消息时安排复制到动态数组中。另一种选择是风险&#39;过度分配数组,虽然这不太可能是一个主要问题(你不会一次增加数组一个条目,以避免在每次分配时必须移动内存时的二次行为)。

如果每个消息都有多个字段需要处理(例如,收到日期和读取日期),那么您需要更多地重新组织代码,可能还有其他功能。

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

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

示例输出:

Inbox (2 messages):

1: Marco
   How are you?

2: Tarma
   Are you going to the gym today?