注意,我认为您的帖子是从 3 个不同的行读取值,例如:
%s
%s
%d %d
(主要体现在您使用了fgets,这是一个面向行的输入函数,它读取一行输入(直到并包括'\n' ) 每次调用。) 如果不是这样,则以下内容不适用(并且可以大大简化)
由于您正在将多个值读取到结构数组中的单个元素中,您可能会发现在开始将信息复制到结构成员之前读取每个值并使用临时值验证每个值会更好(并且更健壮)他们自己。这允许您 (1) 验证所有值的读取,以及 (2) 在将成员存储在结构中并增加数组索引之前验证所有必需值的解析或转换。
此外,您需要从title 和artist 中删除尾部'\n',以防止嵌入的换行符悬垂在字符串的末尾(这将导致搜索title 或artist)。例如,将它们放在一起,您可以执行以下操作:
void rmlf (char *s);
....
char title[MAX_TITLE] = "";
char artist[MAX_ARTIST = "";
char a[10] = "";
int min, sec;
...
while (fgets (title, MAX_TITLE, file) && /* validate read of values */
fgets (artist, MAX_ARTIST, file) &&
fgets (a, 10, file)) {
if (sscanf (a, "%d %d", &min, &sec) != 2) { /* validate conversion */
fprintf (stderr, "error: failed to parse 'min' 'sec'.\n");
continue; /* skip line - tailor to your needs */
}
rmlf (title); /* remove trailing newline */
rmlf (artist);
s[i].time.min = min; /* copy to struct members & increment index */
s[i].time.sec = sec;
strncpy (s[i].title, title, MAX_TITLE);
strncpy (s[i++].artist, artist, MAX_ARTIST);
}
/** remove tailing newline from 's'. */
void rmlf (char *s)
{
if (!s || !*s) return;
for (; *s && *s != '\n'; s++) {}
*s = 0;
}
(注意:这也将读取所有值,直到遇到EOF没有使用feof(参见相关链接:Why is “while ( !feof (file) )” always wrong?))
使用fgets防止短读
继 Jonathan 的评论之后,在使用 fgets 时,您应该真正检查以确保您确实阅读了整行,并且没有遇到您提供的最大字符值不是 short read足以读取整行(例如,短读,因为该行中的字符仍未读取)
如果发生短读,这将完全破坏您从文件中读取任何其他行的能力,除非您正确处理故障。这是因为下一次读取尝试不会从您认为正在读取的行开始读取,而是尝试读取 short read 发生的行的剩余字符。
您可以通过验证读入缓冲区的最后一个字符实际上是'\n' 字符来验证fgets 的读取。 (如果该行长于您指定的最大值,则 nul-terminating 字符之前的最后一个字符将是普通字符。)如果遇到 short read,然后,您必须读取并丢弃长行中的剩余字符,然后再继续下一次读取。 (除非您使用的是动态分配的缓冲区,您可以根据需要简单地 realloc 读取行的其余部分和您的数据结构)
您的情况使验证复杂化,因为每个结构元素都需要输入文件中的 3 行数据。在读取循环的每次迭代期间,您必须始终保持 3 行读取同步读取所有 3 行(即使发生短读取)。这意味着您必须验证是否已读取所有 3 行并且没有发生短读,以便在不退出输入循环的情况下处理任何一个 短读。 (如果您只想终止任何一个短读的输入,您可以单独验证每个,但这会导致输入例程非常不灵活。
除了从输入中删除尾随换行符之外,您还可以将上面的 rmlf 函数调整为验证 fgets 每次读取的函数。我在下面的一个名为shortread 的函数中完成了这项工作。对原始函数和读取循环的调整可以这样编码:
int shortread (char *s, FILE *fp);
...
for (idx = 0; idx < MAX_SONGS;) {
int t, a, b;
t = a = b = 0;
/* validate fgets read of complete line */
if (!fgets (title, MAX_TITLE, fp)) break;
t = shortread (title, fp);
if (!fgets (artist, MAX_ARTIST, fp)) break;
a = shortread (artist, fp);
if (!fgets (buf, MAX_MINSEC, fp)) break;
b = shortread (buf, fp);
if (t || a || b) continue; /* if any shortread, skip */
if (sscanf (buf, "%d %d", &min, &sec) != 2) { /* validate conversion */
fprintf (stderr, "error: failed to parse 'min' 'sec'.\n");
continue; /* skip line - tailor to your needs */
}
s[idx].time.min = min; /* copy to struct members & increment index */
s[idx].time.sec = sec;
strncpy (s[idx].title, title, MAX_TITLE);
strncpy (s[idx].artist, artist, MAX_ARTIST);
idx++;
}
...
/** validate complete line read, remove tailing newline from 's'.
* returns 1 on shortread, 0 - valid read, -1 invalid/empty string.
* if shortread, read/discard remainder of long line.
*/
int shortread (char *s, FILE *fp)
{
if (!s || !*s) return -1;
for (; *s && *s != '\n'; s++) {}
if (*s != '\n') {
int c;
while ((c = fgetc (fp)) != '\n' && c != EOF) {}
return 1;
}
*s = 0;
return 0;
}
(注意:在上面的示例中,shortread 检查构成标题、艺术家、时间组的每一行的结果。)
为了验证该方法,我整理了一个简短的示例,有助于将所有内容放在上下文中。查看示例,如果您有任何其他问题,请告诉我。
#include <stdio.h>
#include <string.h>
/* constant definitions */
enum { MAX_MINSEC = 10, MAX_ARTIST = 32, MAX_TITLE = 48, MAX_SONGS = 64 };
typedef struct {
int min;
int sec;
} stime;
typedef struct {
char title[MAX_TITLE];
char artist[MAX_ARTIST];
stime time;
} songs;
int shortread (char *s, FILE *fp);
int main (int argc, char **argv) {
char title[MAX_TITLE] = "";
char artist[MAX_ARTIST] = "";
char buf[MAX_MINSEC] = "";
int i, idx, min, sec;
songs s[MAX_SONGS] = {{ .title = "", .artist = "" }};
FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;
if (!fp) { /* validate file open for reading */
fprintf (stderr, "error: file open failed '%s'.\n", argv[1]);
return 1;
}
for (idx = 0; idx < MAX_SONGS;) {
int t, a, b;
t = a = b = 0;
/* validate fgets read of complete line */
if (!fgets (title, MAX_TITLE, fp)) break;
t = shortread (title, fp);
if (!fgets (artist, MAX_ARTIST, fp)) break;
a = shortread (artist, fp);
if (!fgets (buf, MAX_MINSEC, fp)) break;
b = shortread (buf, fp);
if (t || a || b) continue; /* if any shortread, skip */
if (sscanf (buf, "%d %d", &min, &sec) != 2) { /* validate conversion */
fprintf (stderr, "error: failed to parse 'min' 'sec'.\n");
continue; /* skip line - tailor to your needs */
}
s[idx].time.min = min; /* copy to struct members & increment index */
s[idx].time.sec = sec;
strncpy (s[idx].title, title, MAX_TITLE);
strncpy (s[idx].artist, artist, MAX_ARTIST);
idx++;
}
if (fp != stdin) fclose (fp); /* close file if not stdin */
for (i = 0; i < idx; i++)
printf (" %2d:%2d %-32s %s\n", s[i].time.min, s[i].time.sec,
s[i].artist, s[i].title);
return 0;
}
/** validate complete line read, remove tailing newline from 's'.
* returns 1 on shortread, 0 - valid read, -1 invalid/empty string.
* if shortread, read/discard remainder of long line.
*/
int shortread (char *s, FILE *fp)
{
if (!s || !*s) return -1;
for (; *s && *s != '\n'; s++) {}
if (*s != '\n') {
int c;
while ((c = fgetc (fp)) != '\n' && c != EOF) {}
return 1;
}
*s = 0;
return 0;
}
示例输入
$ cat ../dat/titleartist.txt
First Title I Like
First Artist I Like
3 40
Second Title That Is Way Way Too Long To Fit In MAX_TITLE Characters
Second Artist is Fine
12 43
Third Title is Fine
Third Artist is Way Way Too Long To Fit in MAX_ARTIST
3 23
Fourth Title is Good
Fourth Artist is Good
32274 558212 (too long for MAX_MINSEC)
Fifth Title is Good
Fifth Artist is Good
4 27
使用/输出示例
$ ./bin/titleartist <../dat/titleartist.txt
3:40 First Artist I Like First Title I Like
4:27 Fifth Artist is Good Fifth Title is Good