我正在研究一个项目,我刚刚遇到了一个非常烦人的问题。我有一个文件存储我的帐户收到的所有邮件。消息是以这种方式定义的数据结构:
typedef struct _message{
char dest[16];
char text[512];
}message;
dest
是一个不能包含空格的字符串,与其他字段不同。
使用fgets()
函数获取字符串,因此dest
和text
可以具有“动态”长度(从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
,这次包括空格。
关于如何解决这个问题的任何想法?
答案 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;
}
读取代码将在第一个名称之前,名称和文本之间以及姓氏之后处理(多个)空行。它们决定是否存储刚刚在消息的dest
或text
部分中读取的行,这有点不寻常。它使用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?