首页 > 解决方案 > 如何读取外部文件中的前 n 个结构变量 - C

问题描述

我正在尝试运行一个程序,其中用户输入要阅读的员工数量,程序显示前 n 个员工。

#include <stdio.h>

struct employee {
    int id;
    char name[50];
    float salary;
}
struct employee e;
adddata(){
        FILE *fp;
        fp=fopen("record.txt","a");
        printf("        Employee Name : ");
        scanf("%s",e.name);
        printf("        ID :");
        scanf("%d",&e.id);
        printf("        Salary :");
        scanf("%f",e.salary);
        fseek(fp,0,SEEK_END);
        fwrite(&e,sizeof(e),1,fp);
        fclose(fp);
}
get(){
        int n;
        printf("amount to display: ");
        scanf("%d", &n);
        FILE *fp;
        fp = fopen("record.txt", "r");
        //code to get and display first n employees
        //e.g. n = 3; displays employee 1, 2, 3
}
int main(){
    int opt;
    printf("1 - add data \n 2 - get data");
    scanf("%d", &opt);
    switch(opt){
    case 1 : adddata();
    break;
    case 2 : get();
    }
}

外部文本的内容:

id        name        salary

1       John Doe      10000.00
2       Kyle Smith    15000.00
3       Ron Adams     20000.00
4       Alice Wilde   21000.00
5       Zoe Jordan    18000.00

您如何阅读它(不是逐行而是按结构变量)?

标签: c

解决方案


鉴于您添加的内容表明您正在以二进制模式将结构写入文件,这会极大地改变原始问题的特征。它不是从原始问题中显示的文本文件中读取和解析值之一,而是从二进制文件中简单地读取给定数量的结构,所有结构的大小都是固定的。

由于最初的问题表明阅读和解析文本,这将提供一个机会,在回答您的问题的同时完成这两项工作。因此,让我们读取并解析您提供的原始文本,然后编写一个包含结构的二进制文件,然后从该文件中读取并显示所请求员工数量的输出。(将您喜欢的任何标题信息添加到输出中留给您)

C 程序员的第 6 条诫命 - “你要被警告......”

首先,您的adddata()函数非常脆弱,并且是在无辜的错误击键上调用未定义行为的秘诀。如果不检查 return,您将无法正确使用任何输入函数(与此相关的任何函数)。以免你们违反Henry Spencer 的 C 程序员 10 条诫命中的第 6 条诫命

避免幻数和硬编码文件名

此外,不要在代码中使用幻数硬编码文件名。如果您需要一个常量,#define一个(或多个)并将要读取的文件名作为参数传递给main()或将其作为输入。例如:

#define MAXC 512        /* max characters for read buffer */
#define MAXN  50        /* max characters in name */

struct employee {
    int id;
    char name[MAXN];
    float salary;
};

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

    int n, nemp;    /* n for user input, nemp for number read from file */
    char ofn[MAXC], /* buffer to hold output file name for writing bin file */
        *p = ofn;   /* pointer to output file name buffer to swap extensions */
    FILE *fp;       /* file pointer */
    struct employee e[MAXN] = {{ .id = 0 }};    /* array of MAXN employee */

    if (argc < 2 ) {    /* validate 1 argument given for filename */
        fprintf (stderr, "error: insufficient input,\n"
                        "usage: %s filename\n", argv[0]);
        return 1;
    }

    if (!(fp = fopen (argv[1], "r"))) { /* validate file open for reading */
        perror ("file open failed");
        return 1;
    }

原始问题文本文件读取

继续处理您的数据。对于以文本形式发布的原始数据,如果您面临读取数据并将其解析为. 的问题struct employee,您只需忽略所有不以isdigit().

然后要提取信息,您基本上会从头到尾工作,读取id并保存所占用的字符数。然后,您将从行尾开始备份,直到您的第一个空白字符(使用strrchr()),然后将其转换salaryfloat. All that is left is scanning forward from the end ofiduntil the first non-whitespace character is found, and then backing up from the space before工资until the first non-whitespace character is found. What is left between is the名称which you can copy withmemcpy()`(不要忘记 *nul-terminate)。

将它们放在一个readtext()函数中,该函数接受一个打开的FILE*流指针,一个指向struct employee要填充的数组的指针(假定它足够大以保存从文件中读取的数据——您应该通过将最大大小作为另一个传递来添加额外的验证参数),最后是要读取的员工人数。你可以做类似的事情:

/** read text file as shown in question into array of struct */
int readtext (FILE *fp, struct employee *emp, int n)
{
    int nemp = 0;       /* number of employees read */
    char buf[MAXC];     /* buffer to hold each line (don't skimp on size) */
    struct employee tmp = { .id = 0 };  /* temp struct to fill */

    while (nemp < n && fgets (buf, MAXC, fp)) { /* read each line */
        char *p, *ep;           /* pointer & end-pointer to use in parsing */
        int offset;             /* offset (no. of chars for id) */

        if (!isdigit (*buf))    /* if 1st char not digit, get next line */
            continue;
        /* if no successful convertion to int for id, get next line */
        if (sscanf (buf, "%d%n", &tmp.id, &offset) != 1)
            continue;
        /* if space before salary or no conversion to float, get next line */
        if (!(ep = strrchr (buf, ' ')) || sscanf (ep, "%f", &tmp.salary) != 1)
            continue;

        p = buf + offset;       /* skip whitespace until start of name */
        while (isspace (*p))
            p++;

        do                      /* backup until last char in name found */
            ep--;
        while (ep > p && isspace(*ep));
        ep++;                   /* advance 1 past last char in name */

        memcpy (tmp.name, p, ep - p);   /* copy name to tmp.name */
        tmp.name[ep - p] = 0;           /* nul-terminate tmp.name */

        emp[nemp++] = tmp;  /* assign tmp to array, increment nemp */
    }

    return nemp;    /* return number of employees read into array */
}

你会从 main 调用它来填充你的结构数组e

    ...
    fputs ("amount to display: ", stdout);  /* prompt */
    fflush (stdout);                        /* optional (but recommended) */
    if (scanf ("%d", &n) != 1) {            /* validate integer input */
        fputs ("error: invalid integer input.\n", stderr);
        return 1;
    }

    if ((nemp = readtext(fp, e, n)) != n)   /* if less than n read, warn */
        fprintf (stderr, "\nwarning: less than %d employees found.\n\n", n);
    fclose (fp);                        /* close file */
    ...

您可以通过简单地输出数据来检查您是否拥有所需的信息:

    putchar ('\n');                 /* add newline before output */
    for (int i = 0; i < nemp; i++)  /* output employees read from file */
        printf ("%3d  %-20s  %.2f\n", e[i].id, e[i].name, e[i].salary);

其余的处理将数据写入二进制文件,然后可以读取,因为更新的问题表明这是真正的问题。从二进制文件写入和读取都是对fwrite和的简单调用fread。你可以为每个函数创建函数,但这留给你。

编写二进制文件以与更新的问题一起使用

由于我们是根据从文本文件中读取的信息写入二进制数据,因此需要做的就是以新的文件名写出信息。".txt"简单地将文件扩展名从to交换".bin"是区分文件的简单方法。如果输入文件没有扩展名,则只需将".bin"扩展名添加到末尾。(您还应该添加额外的验证文件名是否适合 - 提供的空间ofn- 这也留给您)

对于此示例,我们只需找到最后一个'.'并将右侧的所有内容视为文件扩展名。这可以根据需要进行扩展。一个简单的实现是:

    /* form output filename by changing extension to ".bin" */
    if ((p = strrchr (argv[1], '.')) == NULL)   /* if no last '.' in argv[1] */
        strcpy (ofn, argv[1]);                  /* copy all to ofn */
    else {  /* otherwise */
        memcpy (ofn, argv[1], p - argv[1]);     /* copy up to last '.' to ofn */
        ofn[p - argv[1]] = 0;                   /* nul-terminate */
    }
    strcat (ofn, ".bin");                       /* concatenate .bin extension */

现在形成了输出文件名,只需将结构写出,然后关闭文件流(注意: 始终验证 close-after-write以捕获刷新文件流时不会被您的验证捕获的任何错误fwrite

    /* open/validate output file open for writing */
    if (!(fp = fopen (ofn, "wb"))) {
        perror ("fopen-idnamesal.bin");
        return 1;
    }

    /* write array to output file in binary */
    if (fwrite (e, sizeof *e, nemp, fp) != (size_t)nemp) {
        fputs ("error: short write to binary.\n", stderr);
        return 1;
    }

    if (fclose (fp) == EOF) {           /* always validate close-after-write */
        perror ("fclose-after-write");
        return 1;
    }

struct employee从二进制文件中读取

现在我们有一个包含您的结构的二进制文件,我们可以使用它来回答您更新的问题。虽然您可以使用结构数组来读取数据,但由于您不知道将读取多少数据struct employee,因此传统上您会在其中分配足够大小的内存块来保存struct employee用户输入的数量使用malloc(). 这使您能够在运行时将存储大小调整为保存数据所需的确切数量。做起来很简单,但要遵循第 6 条诫命,例如

    /* allocate block of storage to hold data read from binary file */
    struct employee *empfrombin = malloc (nemp * sizeof *empfrombin);
    if (!empfrombin) {
        perror ("malloc-empfrombin");
        return 1;
    }

使用一块足够大的内存来保存struct employee用户输入的数量,只需将二进制文件中的结构数量读取到新块中,只需一次调用fread并关闭文件(无需验证关闭后-读)。然后,您可以以与上述相同的方式输出信息:

    /* read/validate struct data from binary file into newly allocated blockm */
    if (fread (empfrombin, sizeof *empfrombin, nemp, fp) != (size_t)nemp) {
        fprintf (stderr, "error: file read failed - '%s'.\n", ofn);
        return 1;
    }
    fclose (fp);    /* close file */

    /* output employees from newly allocated/filled block of memory */
    fprintf (stdout, "\nemployees read from binary file '%s'\n\n", ofn);
    for (int i = 0; i < nemp; i++)
        printf ("%3d  %-20s  %.2f\n", 
                empfrombin[i].id, empfrombin[i].name, empfrombin[i].salary);

最后,在您编写的任何动态分配内存的代码中,对于分配的任何内存块,您有两个责任:(1)始终保留指向内存块起始地址的指针,(2)它可以在它被释放时被释放不再需要。所以只需free()你的内存块,你就完成了:

    free (empfrombin);  /* don't forget to free what you have allocated */
}

总而言之,涵盖您的原始和更新问题的整个示例将是:

#include <stdio.h>
#include <stdlib.h>     /* for malloc/free */
#include <string.h>     /* for strrchr, memcpy */
#include <ctype.h>      /* for isspace, isdigit */

#define MAXC 512        /* max characters for read buffer */
#define MAXN  50        /* max characters in name */

struct employee {
    int id;
    char name[MAXN];
    float salary;
};

/** read text file as shown in question into array of struct */
int readtext (FILE *fp, struct employee *emp, int n)
{
    int nemp = 0;       /* number of employees read */
    char buf[MAXC];     /* buffer to hold each line (don't skimp on size) */
    struct employee tmp = { .id = 0 };  /* temp struct to fill */

    while (nemp < n && fgets (buf, MAXC, fp)) { /* read each line */
        char *p, *ep;           /* pointer & end-pointer to use in parsing */
        int offset;             /* offset (no. of chars for id) */

        if (!isdigit (*buf))    /* if 1st char not digit, get next line */
            continue;
        /* if no successful convertion to int for id, get next line */
        if (sscanf (buf, "%d%n", &tmp.id, &offset) != 1)
            continue;
        /* if space before salary or no conversion to float, get next line */
        if (!(ep = strrchr (buf, ' ')) || sscanf (ep, "%f", &tmp.salary) != 1)
            continue;

        p = buf + offset;       /* skip whitespace until start of name */
        while (isspace (*p))
            p++;

        do                      /* backup until last char in name found */
            ep--;
        while (ep > p && isspace(*ep));
        ep++;                   /* advance 1 past last char in name */

        memcpy (tmp.name, p, ep - p);   /* copy name to tmp.name */
        tmp.name[ep - p] = 0;           /* nul-terminate tmp.name */

        emp[nemp++] = tmp;  /* assign tmp to array, increment nemp */
    }

    return nemp;    /* return number of employees read into array */
}

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

    int n, nemp;    /* n for user input, nemp for number read from file */
    char ofn[MAXC], /* buffer to hold output file name for writing bin file */
        *p = ofn;   /* pointer to output file name buffer to swap extensions */
    FILE *fp;       /* file pointer */
    struct employee e[MAXN] = {{ .id = 0 }};    /* array of MAXN employee */

    if (argc < 2 ) {    /* validate 1 argument given for filename */
        fprintf (stderr, "error: insufficient input,\n"
                        "usage: %s filename\n", argv[0]);
        return 1;
    }

    if (!(fp = fopen (argv[1], "r"))) { /* validate file open for reading */
        perror ("file open failed");
        return 1;
    }

    fputs ("amount to display: ", stdout);  /* prompt */
    fflush (stdout);                        /* optional (but recommended) */
    if (scanf ("%d", &n) != 1) {            /* validate integer input */
        fputs ("error: invalid integer input.\n", stderr);
        return 1;
    }

    if ((nemp = readtext(fp, e, n)) != n)   /* if less than n read, warn */
        fprintf (stderr, "\nwarning: less than %d employees found.\n\n", n);
    fclose (fp);                        /* close file */

    putchar ('\n');                 /* add newline before output */
    for (int i = 0; i < nemp; i++)  /* output employees read from file */
        printf ("%3d  %-20s  %.2f\n", e[i].id, e[i].name, e[i].salary);

    /* form output filename by changing extension to ".bin" */
    if ((p = strrchr (argv[1], '.')) == NULL)   /* if no last '.' in argv[1] */
        strcpy (ofn, argv[1]);                  /* copy all to ofn */
    else {  /* otherwise */
        memcpy (ofn, argv[1], p - argv[1]);     /* copy up to last '.' to ofn */
        ofn[p - argv[1]] = 0;                   /* nul-terminate */
    }
    strcat (ofn, ".bin");                       /* concatenate .bin extension */

    /* open/validate output file open for writing */
    if (!(fp = fopen (ofn, "wb"))) {
        perror ("fopen-idnamesal.bin");
        return 1;
    }

    /* write array to output file in binary */
    if (fwrite (e, sizeof *e, nemp, fp) != (size_t)nemp) {
        fputs ("error: short write to binary.\n", stderr);
        return 1;
    }

    if (fclose (fp) == EOF) {           /* always validate close-after-write */
        perror ("fclose-after-write");
        return 1;
    }

    /* allocate block of storage to hold data read from binary file */
    struct employee *empfrombin = malloc (nemp * sizeof *empfrombin);
    if (!empfrombin) {
        perror ("malloc-empfrombin");
        return 1;
    }

    if (!(fp = fopen (ofn, "rb"))) {    /* open/validate binary file */
        perror ("fopen-ofn-rb");
        return 1;
    }

    /* read/validate struct data from binary file into newly allocated blockm */
    if (fread (empfrombin, sizeof *empfrombin, nemp, fp) != (size_t)nemp) {
        fprintf (stderr, "error: file read failed - '%s'.\n", ofn);
        return 1;
    }
    fclose (fp);    /* close file */

    /* output employees from newly allocated/filled block of memory */
    fprintf (stdout, "\nemployees read from binary file '%s'\n\n", ofn);
    for (int i = 0; i < nemp; i++)
        printf ("%3d  %-20s  %.2f\n", 
                empfrombin[i].id, empfrombin[i].name, empfrombin[i].salary);

    free (empfrombin);  /* don't forget to free what you have allocated */
}

注意:尝试添加上面段落中提到的其他验证,以确保您从文本文件中读取的数据不会超过可用数组存储的数据)

使用的原始文本输入文件

$ cat dat/idnamesal.txt
id        name        salary

1       John Doe      10000.00
2       Kyle Smith    15000.00
3       Ron Adams     20000.00
4       Alice Wilde   21000.00
5       Zoe Jordan    18000.00

生成的二进制文件

$ hexdump -C dat/idnamesal.bin
00000000  01 00 00 00 4a 6f 68 6e  20 44 6f 65 00 00 00 00  |....John Doe....|
00000010  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
00000030  00 00 00 00 00 00 00 00  00 40 1c 46 02 00 00 00  |.........@.F....|
00000040  4b 79 6c 65 20 53 6d 69  74 68 00 00 00 00 00 00  |Kyle Smith......|
00000050  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
00000070  00 00 00 00 00 60 6a 46  03 00 00 00 52 6f 6e 20  |.....`jF....Ron |
00000080  41 64 61 6d 73 00 00 00  00 00 00 00 00 00 00 00  |Adams...........|
00000090  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
000000b0  00 40 9c 46 04 00 00 00  41 6c 69 63 65 20 57 69  |.@.F....Alice Wi|
000000c0  6c 64 65 00 00 00 00 00  00 00 00 00 00 00 00 00  |lde.............|
000000d0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
000000e0  00 00 00 00 00 00 00 00  00 00 00 00 00 10 a4 46  |...............F|
000000f0  05 00 00 00 5a 6f 65 20  4a 6f 72 64 61 6e 00 00  |....Zoe Jordan..|
00000100  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
00000120  00 00 00 00 00 00 00 00  00 a0 8c 46              |...........F|
0000012c

示例使用/输出

从显示的文本格式读取,写入二进制文件struct employee并从二进制文件读取并显示所有5员工。

$ ./bin/readidnamesal dat/idnamesal.txt
amount to display: 5

  1  John Doe              10000.00
  2  Kyle Smith            15000.00
  3  Ron Adams             20000.00
  4  Alice Wilde           21000.00
  5  Zoe Jordan            18000.00

employees read from binary file 'dat/idnamesal.bin'

  1  John Doe              10000.00
  2  Kyle Smith            15000.00
  3  Ron Adams             20000.00
  4  Alice Wilde           21000.00
  5  Zoe Jordan            18000.00

阅读少于全部:

$ ./bin/readidnamesal dat/idnamesal.txt
amount to display: 1

  1  John Doe              10000.00

employees read from binary file 'dat/idnamesal.bin'

  1  John Doe              10000.00

尝试读取比文件中更多的员工:

$ ./bin/readidnamesal dat/idnamesal.txt
amount to display: 100

warning: less than 100 employees found.


  1  John Doe              10000.00
  2  Kyle Smith            15000.00
  3  Ron Adams             20000.00
  4  Alice Wilde           21000.00
  5  Zoe Jordan            18000.00

employees read from binary file 'dat/idnamesal.bin'

  1  John Doe              10000.00
  2  Kyle Smith            15000.00
  3  Ron Adams             20000.00
  4  Alice Wilde           21000.00
  5  Zoe Jordan            18000.00

如果您还有其他问题,请仔细查看并告诉我。虽然您的问题有所改变,但来自原始问题的文本操作和来自更新问题的二进制操作都是“面包和黄油”操作,您将一次又一次地执行,因此您不妨与两者交朋友。


推荐阅读