如何在c中使用fscanf从文件中读取格式化数据

时间:2017-12-29 19:56:09

标签: c scanf

我想读取这些数据并将它们放到struct数组中但它不起作用

#include<stdio.h>
struct book {
    char bookName[50];
    char authorName[50];
    long price;
    int year;
}
main() {
    FILE *data;
    data=fopen("library.txt", "r");
    if (data == NULL) {
        printf("File Could not be opened!\n");
    }
    else {
        struct book myBook;
        struct book books[20];

        int n=0;

        while (!feof(data))
        {
            fscanf(data,"%s***%s***%d***%d\n",&myBook.bookName,&myBook.authorName,&myBook.price,&myBook.year);
            books[n]=myBook;
            n++;
        }

        int i;
        for(i=0;i<n;i++){
            printf("%s - %s - %d - %d \n",books[i].bookName,books[i].authorName,books[i].price,books[i].year);
        }
    }
}

,输出

C - b - 0 - 232159429
programing***Fatemeh - b - 0 - 232159429
Kazemi***15000***1391 - b - 0 - 232159429
C - b - 0 - 232159429
programs***Ali - b - 0 - 232159429
Ahmadpour***20000***1392 - b - 0 - 232159429
Programing***Mona - b - 0 - 232159429
Ghassemi***25000***1389 - b - 0 - 232159429
C - b - 0 - 232159429
programing - b - 0 - 232159429
(advanced)***Sara - b - 0 - 232159429
Hamidi***40000***1385 - b - 0 - 232159429

但我的真实数据是

C programing***Fatemeh Kazemi***15000***1391
Cprograms***Ali Ahmadpour***20000***1392
Programing***Mona Ghassemi***25000***1389
C programing (advanced)***Sara Hamidi***40000***1385

我该怎么办? 它看起来fscanf只适用于空格,但我需要使用***来分隔我的数据

2 个答案:

答案 0 :(得分:0)

请勿使用fscanf()。错误检查太难或不完整。

while (!feof(data))不保证以下阅读成功。

// while (!feof(data)){  
//  fscanf(data,"%s***%s***%d***%d\n",....

使用fgets()读取文件输入的,然后解析。

size_t n = 0;
// Work with an ample sized buffer
char buf[sizeof (struct book) * 2];
while (n < 20 && fgets(buf, sizeof buf, data)) {

现在解析数据,寻找"***"

  char *sep[3];
  sep[0] = strstr(buf, "***");
  if (sep[0] == NULL) break;
  sep[1] = strstr(sep[0] + 3, "***");
  if (sep[1] == NULL) break;
  sep[2] = strstr(sep[1] + 3, "***");
  if (sep[2] == NULL) break;

让我们用零填充myBook - 对调试很有用,并确保字符串的空字符。

  struct book myBook;

保险隔离器距离不太远

  if (sep[0] - buf >= sizeof myBook.bookName) break;
  memcpy(myBook.bookName, buf, sep[0] - buf);    //
  if (sep[1] - sep[0] - 3 >= sizeof myBook.authorName) break;   
  memcpy(myBook.authorName, sep[0] + 3, sep[1] - sep[0] - 3);    

使用atol()或...

  myBook.price = atol(sep[1] + 3);

... strtol()sscanf()进行更多错误检查。也许检查有效年份值?

  char *endptr;
  errno = 0;
  myBook.year = strtol(sep[2] + 3, &endptr, 10);
  if (sep[2] + 3 == endptr) break;
  if (errno) break;
  if (myBook.year < year_MIN || myBook.year > year_MAX) break;

好的,代码成功了

   books[n] = myBook;
   n++;      
 }
  

但我需要使用***来分隔我的数据

如果"*""**"永远不会发生,代码可以使用以下错误检查较少的代码。务必检查输入功能的返回值。

while (n < 20 && fgets(buf, sizeof buf, data)) {
  if (sscanf(buf, "%49[^*]***%49[^*]***%ld***%d",
      myBook.bookName, myBook.authorName, &myBook.price, &myBook.year) != 4) {
    break;  // badly formatted data
  }
  books[n] = myBook;
  n++;      
}

答案 1 :(得分:0)

该程序以当前格式读取您的数据,并将输出生成为“ - ”分隔的条目(如您提供的printf语句中所示):

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

#define MAXBUF 500

int parse_string(char* s,char* a[]);

struct book {
    char bookName[50];
    char authorName[50];
    long price;
    int year;
};

int main() {

    FILE *data;
    char buf[MAXBUF];
    char *sp, *words[MAXBUF];
    int nw;

    data=fopen("library.txt", "r");
    if (data == NULL) {
        printf("File Could not be opened!\n");
    }
    else {
        struct book myBook;
        struct book books[20];

        int n=0;

        // Read each line into buf and parse it into words, 
        // assign words into structure fields   
        while((sp = fgets(buf, MAXBUF, data))!= NULL){
            nw=parse_string(buf, words);

            // If number of words different than number of
            // fields in the structure exit with an error
            if (nw!=4){
                printf("Error - number of parsed words does not match number of fields in the structure!\n");
                exit(1);
            }

            // Assign values, convert strings to long and int
            strncpy(myBook.bookName, words[0], strlen(words[0])+1);
            strncpy(myBook.authorName, words[1], strlen(words[1])+1);

            myBook.price = atol(words[2]);
            myBook.year = atoi(words[3]);

            // Add to book array
            books[n] = myBook;

            n++;
        }

       // Print 
       int i;
       for(i=0;i<n;i++){
           printf("%s - %s - %ld - %d \n",books[i].bookName,books[i].authorName,books[i].price,books[i].year);
       }
    }
}


/* Function to parse a line of input into an aray of words */
/* s - pointer to the char array (line) to be parsed
 * a - array of pointers to parsed elements 
 * returns number of elements in a*/
int parse_string(char* s, char* a[])
{
    int nw,j;
    a[0] = strtok(s,"*\t\n\r\v\f");
    nw = 1;
    while((a[nw]= strtok(NULL,"*\t\n\r\v\f"))!=NULL)
        nw++;
    return nw;
}

与评论中建议的一样,此程序使用fgets一次读取文件行。然后,它使用解析函数将每行解析为基于给定分隔符的单词。解析函数很小,使用strtok和硬编码分隔符集。根据您的输入,它不会将空格用作分隔符,而是使用*。它允许其他分隔符,如合理的新行,也许不是合理的标签。

下一步是将每个单词分配到book结构中的字段。在此之前检查从解析函数(nw)返回的单词数是否等于当前字段数。最后,如果是 - 它会将成员分配给myBook进行任何必要的转换,并将此条目添加到图书数组中。

可以省略的一些改进是使用输入文件名启用命令行参数而不是硬编码。将输出写入文件也很好,在这种情况下输出文件将是第二个命令行参数。最后,正如评论中所提到的,您可以并且应该根据您正在使用的数组的大小提供一些输入大小检查。