在C

时间:2016-05-18 20:13:15

标签: c file io fgets

我有一个非常奇怪的问题,我试图用C读取.txt文件,数据的结构如下: %s %s %d %d 因为我必须阅读字符串all the way to \n,所以我这样读:

while(!feof(file)){
        fgets(s[i].title,MAX_TITLE,file);
        fgets(s[i].artist,MAX_ARTIST,file);
        char a[10];
        fgets(a,10,file);
        sscanf(a,"%d %d",&s[i].time.min,&s[i++].time.sec);
    }

但是,我在very first中读到的s.time.min整数显示了一个随机的大数字。

我现在正在使用sscanf,因为有些人有类似的问题,但它没有帮助。

谢谢!

编辑:整数代表时间,它们永远不会超过5个字符,包括之间的空格。

3 个答案:

答案 0 :(得分:2)

注意,我会将您的帖子从3个不同的行读取,例如:

%s
%s
%d %d

(主要通过使用fgets,一个面向行的输入函数来证明,该函数读取一行输入(最多和包括 { {1}})每次调用它。)如果不是这种情况,则以下内容不适用(可以大大简化)

由于您正在读取struct数组中的单个元素中的多个值,因此在开始将信息复制到结构成员之前,您可能会发现它更好(并且更强大),读取每个值并使用临时值验证每个值他们自己。这允许您(1)验证所有值的读取,以及(2)在结构中存储成员并递增数组索引之前验证所有必需值的解析或转换。

此外,您需要从'\n''\n'移除尾部title,以防止在字符串末尾悬挂嵌入的换行符(这会对搜索造成严重破坏) artisttitle)。例如,将它们放在一起,你可以做类似的事情:

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时(请参阅相关链接:{{3 }}))

使用 feof

保护短读

根据Jonathan的评论,当使用fgets时,您应该检查以确保您实际上已经阅读了整行,并且没有经历过短读的最大值您提供的字符值不足以读取整行(例如短读,因为该行中的字符仍然未读)

如果发生短读,那将完全破坏您从文件中读取任何其他行的能力,除非您正确处理失败。这是因为下一次读取尝试不会在您认为正在读取的行上开始读取,而是尝试读取短读所在行的剩余字符。

您可以通过验证读入缓冲区的最后一个字符实际上是fgets字符来验证fgets的读取。 (如果该行长于您指定的最大值,则 nul-terminatedating 字符之前的最后一个字符将是普通字符。)如果遇到短读,然后,您必须读取并丢弃长行中的剩余字符,然后再继续下一次阅读。 (除非您使用动态分配的缓冲区,您可以根据需要'\n'读取行的其余部分和数据结构)

您的情况通过要求每个struct元素的输入文件中的3行数据使验证变得复杂。在读取循环的每次迭代期间,您必须始终保持3行读取同步读取所有3行作为一组(即使发生短读取)。这意味着您必须验证是否已读取所有3行并且没有发生短读取以处理任何一个短读取而不退出输入循环。 (如果你只是想在任何一个短读上终止输入,你可以单独验证每个,但这会导致一个非常不灵活的输入例程。

除了从输入中删除尾随换行符之外,您还可以将上面的realloc函数调整为验证每个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

答案 1 :(得分:0)

而不是sscanf(),我会使用strtok()和atoi()。

好奇,为什么两个整数只有10个字节?你确定他们总是这么小吗?

顺便说一下,我为这么短的答案道歉。我确信有一种方法可以让sscanf()为你工作,但根据我的经验,sscanf()可能相当挑剔,所以我不是一个大粉丝。使用C解析输入时,我刚刚发现它更有效(就编写和调试代码所需的时间而言)只是用strtok()标记输入并使用各种ato单独转换每个部分?函数(atoi,atof,atol,strtod等;参见stdlib.h)。它使事情变得更简单,因为每个输入都是单独处理的,这使得调试任何问题(如果它们出现)更容易。最后,与以前尝试使用sscanf()时相比,我通常花费更少的时间来使这些代码可靠地工作。

答案 2 :(得分:0)

使用"%*s %*s %d %d"作为格式字符串,而不是......

您似乎期望sscanf自动跳过导致十进制数字字段的两个令牌。除非你明确告诉它(因此%*s对),否则它不会这样做。

您不能指望设计C的人以与您相同的方式设计它。你需要检查返回值,正如iharob所说。

那不是全部。你需要阅读(并且理解相当好)整本scanf手册(OpenGroup编写的手册是可以的)。这样你就知道如何使用函数(包括格式字符串的所有微妙细微差别)以及如何处理返回值。

作为程序员,您需要阅读。记得那么好。