简体   繁体   English

如何使用 scanf 读取文本文件中的下一行

[英]How do I read the next line in a text file using scanf

I have an assignment and I need to use scanf to read the input from a text file and temporarily store the inputs into a structure and finally print using printf.我有一个作业,我需要使用 scanf 从文本文件中读取输入并将输入临时存储到结构中,最后使用 printf 打印。 We aren't allowed to use an array of structures.我们不允许使用结构数组。

Here's the text file:这是文本文件:

1010  Jose Manalo      3.5
2003  Luisa Santos     2.5
1008  Andres Espinosa  4.0

Here are the structures that were given (String20 is an array of 20 characters):以下是给出的结构(String20 是一个 20 个字符的数组):

struct nameTag {
    String20 first;
    String20 last;
};

struct studentTag {
    int ID;
    struct nameTag name;
    float grade;
};

My initial code looked like this (studentInfo is my structure variable for studentTag):我的初始代码如下所示(studentInfo 是我的 studentTag 结构变量):

scanf("%d %s %s %f", &studentInfo.ID, studentInfo.name.first, studentInfo.name.last, &studentInfo.grade);
printf("%d %s %s %.1f", studentInfo.ID, studentInfo.name.first, studentInfo.name.last, studentInfo.grade);

This of course only reads and prints the first line这当然只读取和打印第一行

Edit (Solved): Putting scanf into a loop until it reached EOF or NULL worked perfectly.编辑(已解决):将 scanf 放入循环中直到达到 EOF 或 NULL 工作完美。 Here's what I did:这是我所做的:

while(scanf("%d %s %s %f", &studentInfo.ID, studentInfo.name.first, studentInfo.name.last, &studentInfo.grade)!=NULL)
    printf("%d %s %s %.1f\n", studentInfo.ID, studentInfo.name.first, studentInfo.name.last, studentInfo.grade);

When you are reading a line-at-a-time, use a line-oriented input function such as fgets() or POSIX getline() .当您一次读取一行时,请使用面向行的输入函数,例如fgets()或 POSIX getline() This avoids the many pitfalls for new C programmers who attempt to read from the file with scanf() alone.对于尝试单独使用scanf()读取文件的新 C 程序员来说,这避免了许多陷阱。

The problems with using scanf() alone are many, but boil down to what is left in the input stream after an attempted read.单独使用scanf()的问题很多,但归结为尝试读取后输入流中剩余的内容。 For example, when using scanf() to attempt to read an integer value, if a non-digit is encountered before a digit, a matching-failure occurs and character extraction from the input stream ceases at that point leaving all offending characters in the input stream unread -- just waiting to bite you again on your next attempted read unless you manually empty the characters from your input stream.例如,当使用scanf()尝试读取整数值时,如果在数字之前遇到非数字,则会发生匹配失败并且此时停止从输入流中提取字符,将所有有问题的字符留在输入中流未读——除非您手动清空输入流中的字符,否则只是等待在下一次尝试读取时再次咬您。 After reading a float as the last value, the '\\n' is left in the input-stream unread and if on your next read, your format specifier doesn't ignore leading whitespace, the whitespace will be taken as your input.在读取float作为最后一个值后, '\\n'留在未读的输入流中,如果在您下次读取时,您的格式说明符不会忽略前导空格,空格将作为您的输入。 (this is just the tip of the iceberg...) (这只是冰山一角...)

So use fgets() to read an entire line-at-a-time into an appropriately sized character array.因此,使用fgets()一次将整行读取到适当大小的字符数组中。 That way you are consuming an entire line at a time.这样你一次消耗了整条线。 You then parse what you need from your line of data using sscanf() .然后,您使用sscanf()从数据行中解析您需要的内容。 Regardless of the success/failure of the parse, you do not impact your read of the next line of data from the file.无论解析成功/失败,您都不会影响从文件中读取下一行数据。 The read of data and parse of value are no longer coupled in a single operation.数据的读取和值的解析不再耦合在单个操作中。

Putting the pieces together, in your case you can do something similar to:将各个部分放在一起,在您的情况下,您可以执行以下操作:

#include <stdio.h>
#include <string.h>

#define MAXC  1024
#define MAXNM   20

typedef char String20[MAXNM];   /* your typedef */

struct nameTag {
    String20 first;
    String20 last;
};

struct studentTag {
    int ID;
    struct nameTag name;
    float grade;
};

int main (int argc, char **argv) {

    char buf[MAXC];     /* buffer to hold each line */
    size_t n = 0;       /* counter for number of students */
    struct studentTag std[MAXNM] = {{ .ID = 0 }};
    /* use filename provided as 1st argument (stdin by default) */
    FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;

    if (!fp) {  /* validate file open for reading */
        perror ("file open failed");
        return 1;
    }

    /* read each line -- until array full of EOF reached */
    while (n < MAXNM && fgets (buf, MAXC, fp)) {
        struct studentTag tmp = { .ID = 0 };    /* temporary struct to fill */
        if (sscanf (buf, "%d %19s %19s %f",     /* parse value from line */
                    &tmp.ID, tmp.name.first, tmp.name.last, &tmp.grade) == 4)
            std[n++] = tmp; /* assign temp struct to array on success */
        else    /* on error */
            fprintf (stderr, "error: line format - %s", buf);
    }
    if (fp != stdin)   /* close file if not stdin */
        fclose (fp);

    for (size_t i = 0; i < n; i++)  /* output all stored student data */
        printf ("\nID: %d\nFirst: %s\nLast: %s\nGrade: %f\n",
                std[i].ID, std[i].name.first, std[i].name.last, std[i].grade);

    return 0;
}

( note: you always validate every user-input and every parse of values by checking the return , see Henry Spencer's 10 Commandments for C Programmers - No. 6 "Ye be warned..." ) 注意:您总是通过检查 return来验证每个用户输入和每个值的解析,请参阅Henry Spencer 的 C 程序员的 10 条诫命 - 第 6 号“请注意...”

Example Use/Output示例使用/输出

With your data in dat/studenttag.txt , you would provide the filename as the first argument and receive:使用dat/studenttag.txt中的dat/studenttag.txt ,您将提供文件名作为第一个参数并接收:

$ ./bin/readstudenttag dat/studenttag.txt

ID: 1010
First: Jose
Last: Manalo
Grade: 3.500000

ID: 2003
First: Luisa
Last: Santos
Grade: 2.500000

ID: 1008
First: Andres
Last: Espinosa
Grade: 4.000000

Look things over and let me know if you have further questions.仔细检查一下,如果您还有其他问题,请告诉我。

scanf is used to read input streams, from stdin, that is, the most aproximate function you can use is fscanf, similar to scanf, but you can specify the file from which to read: scanf用于从 stdin 读取输入流,也就是说,您可以使用的最近似的函数是 fscanf,类似于 scanf,但您可以指定要从中读取的文件:

#include <stdio.h>

typedef char String20[20];

struct nameTag {
    String20 first;
    String20 last;
};

struct studentTag {
    int ID;
    struct nameTag name;
    float grade;
};

int main()
{
   FILE* f;
   if(!(f = fopen("file.txt", "r"))) {
       return 1;
   }

   struct studentTag studentInfo; 

   while(fscanf(f, "%d %19s %19s %f", &studentInfo.ID, studentInfo.name.first, studentInfo.name.last, &studentInfo.grade) == 4)
        printf("%d %s %s %.1f\n", studentInfo.ID, studentInfo.name.first, studentInfo.name.last, studentInfo.grade);
}

Live sample现场样品

Using the return of fscanf to verify if the correct number of fields was read and using it as stop argument.使用fscanf的返回来验证是否读取了正确数量的字段并将其用作停止参数。

Note that for char array reading it's important to specify the length of the string to be read, for instance, for a char[20] character array container you can use "%s19" specifier, to avoid stack smashing.请注意,对于 char 数组读取,指定要读取的字符串的长度很重要,例如,对于char[20]字符数组容器,您可以使用"%s19"说明符,以避免堆栈粉碎。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM