C语言 从文本文件中获取数据并将数据插入到另一个文件中

dfty9e19  于 2022-12-03  发布在  其他
关注(0)|答案(1)|浏览(249)

我尝试用C语言编写一个程序,从input.txt文件中提取数据,并按学生ID的升序将其插入record.txt文件中。
输入.txt的内容:

1
2012 Bob CS21
1999 Teddy CS35
2
3
2001 Eric CS11
2011 CS12 CS87

记录.txt的内容:

1287 Nancy CS11
1865 Brown CS33

当我运行程序时,input.txt中的以下数据应该被插入到record.文件中(包含学生ID、姓名和课程的有效数据):

2012 Bob CS21
1999 Teddy CS35
2001 Eric CS11

然后插入record.txt文件后的内容(升序):

1287 Nancy CS11
1865 Brown CS33
1999 Teddy CS35
2001 Eric CS11
2012 Bob CS21

下面是我的代码:

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

#define MAX 100

typedef struct {
int id;
    char name[20];
    char course[5];
} Student;

int read_records(Student s[]) {
    FILE *fp;
    int i=0;
    fp = fopen("record.txt", "r");
    if (fp == NULL) {
        printf("File doesn't exist\n");
        return 0;
    }
    while (!feof(fp)) {
        fscanf(fp, "%d %s %s\n", &s[i].id, s[i].name, s[i].course);
        i++;
    }
    fclose(fp);
    return i;
}

void write_records(Student s[], int n) {
    FILE *fp;
    fp = fopen("record.txt", "w");
    if (fp == NULL) {
        printf("File doesn't exist\n");
        return;
    }
    for (int i=0; i<n; i++) {
        fprintf(fp, "%d %s %s\n", s[i].id, s[i].name, s[i].course);
    }
    fclose(fp);
}

void insert_records(Student s[], int n) {
    FILE *fp;
    fp = fopen("input.txt", "r");
    if (fp == NULL) {
        printf("File doesn't exist\n");
        return;
    }
    int i=n;
    while (!feof(fp)) {
        fscanf(fp, "%d %s %s\n", &s[i].id, s[i].name, s[i].course);
        i++;
    }
    fclose(fp);
    write_records(s, i);
    printf("Insertion is done.\n");
}
void display_records(Student s[], int n) {
    for (int i=0; i<n; i++) {
        printf("%d %s %s\n", s[i].id, s[i].name, s[i].course);
    }
}
int main() {
    int n;
    Student s[MAX];
    n = read_records(s);
    int opt;
    while (1) {
        printf("1. Insert\n");
        printf("2. Display\n");
        printf("3. Exit\n");
        printf("Choose an option: ");
        scanf("%d", &opt);
        switch(opt) {
            case 1: insert_records(s, n);
                    break;
            case 2: display_records(s, n);
                    break;
            case 3: exit(0);
        }
    }
}

当我运行程序时,将要求用户选择一个选项。
如果我输入2,它将显示record.txt的内容。正如我所期望的,它运行得很好。
如果我输入1,应该执行插入,并打印“插入完成”。但是,程序并没有像我预期的那样工作。它只是什么也不显示。
我对此感到困惑,我想知道如何修复程序。

mpbci0fu

mpbci0fu1#

此循环

while (!feof(fp)) {
    fscanf(fp, "%d %s %s\n", &s[i].id, s[i].name, s[i].course);
    i++;
}

因为当fscanf遇到无法转换的输入时,该输入会留在流中。该文件永远不会耗尽。
用文件

1
2012 Bob CS21
1999 Teddy CS35
2
3
2001 Eric CS11
2011 CS12 CS87

第一个fscanf调用将转换1 "2012" "Bob"。第二个调用将无法将CS21转换为整数。
此外,请参阅Why is “while( !feof(file) )” always wrong?
另请注意,*scanf函数中的无界%s说明符是dangerous as gets。使用field-width specifier来限制可以读入缓冲区的数据量。这应该是允许的最大 * 字符串长度 *,并且最多应该是缓冲区的大小减1,为空终止字节留出空间。(即char s[256]; scanf("%255s", s);)。
解决方案是永远不要忽略fscanf系列函数的返回值。使用这些返回值来确定程序应该如何进行。如果fscanf无法匹配预期的 * 转换 * 数,则必须准备好清理输入流。这通常意味着消耗字符,直到找到行尾。
然而,这说起来容易做起来难。如上所述,%s跳过空格(包括换行符)的方式意味着它可能会在寻找有效字符时越界。
一般的建议是避免使用scanf/fscanf,而是使用fgets读取整行,然后使用sscanf(或其他工具)解析这些行。
实际情况如下:

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

#define INPUT "input.txt"

int main(void)
{
    FILE *file = fopen(INPUT, "r");

    if (!file) {
        perror(INPUT);
        return EXIT_FAILURE;
    }

    struct {
        int id;
        char name[32];
        char course[16];
    } student;

    char buffer[512];

    while (fgets(buffer, sizeof buffer, file)) {
        int cv = sscanf(buffer, "%d%31s%15s",
                &student.id, student.name, student.course);

        if (3 == cv) {
            /* three successful conversions, do whatever with `student` */
            printf("%s<%d> [%s]\n",
                    student.name, student.id, student.course);
        }
    }

    fclose(file);
}

对于input.txt文件,将打印:

Bob<2012> [CS21]
Teddy<1999> [CS35]
Eric<2001> [CS11]
CS12<2011> [CS87]

一旦您知道sscanf成功解析了预期的转换次数,您就可以继续验证记录(例如,根据您的示例,“CS12” 是一个无效名称,您可能需要一种方法来避免重复条目)。
在代码中,只有在确保遵循了所有这些步骤之后,才应该递增i
在获得合并的记录列表之后,应该使用qsort对数组进行排序,比较函数如下

int student_sort(const void *va, const void *vb)
{
    const Student *a = va, *b = vb;

    return (a->id > b->id) - (a->id < b->id);
}

然后再写出记录。

  1. i最终将溢出,调用Undefined Behaviour。在此点之后的程序结果通常无法推断。

相关问题