使用 fscanf() 和 fseek() 读取和编辑结构体

Reading and editing structs with fscanf() and fseek()

提问人:Pepinho02 提问时间:3/3/2023 最后编辑:chqrliePepinho02 更新时间:3/3/2023 访问量:81

问:

我正在执行以下程序:

编程教师希望创建一个程序来计算学生的期末成绩。目前,所有学生的部分成绩都位于以下格式的文本文件中:

<first_name> <last_name1> <last_name2> | <AC1> <AC2> <AC3> <AC4> <AC5> <AC6> <AC7> <AC8> <AC9>  <AC10> | <project 1>  <project 2>  <project 3> <report> | <exam>

例如:

Joan Gomez Garcia | 8.0 9.0 7.0 4.0 5.0 6.0 7.0 8.0 9.0 10.0 | 10.0 9.0 8.5 9.0 | 6.0
Juan Perez Gonzalez | 3.0 3.0 4.8 5.0 5.0 4.0 6.0 2.0 0.0 0.0 | 8.0 7.5 7.0 8.0 | 7.5
Marta Bonet Call | 6.0 7.0 7.0 8.0 8.0 9.0 10.0 10.0 0.0 | 9.0 9.0 9.0 10.0 | 5.5
Jordi Fernandez Bou | 10.0 10.0 10.0 10.0 10.0 10.0 10.0 10.0 10.0 | 10.0 10.0 10.0 10.0 | 3.5

所有值都用空格分隔,连续评估分数、项目分数和期末考试分数之间有一个斜杠 ('|')。此信息必须存储在 Student 类型的结构中。您可以在下面所示的 grades.h 文件的 typedef 中找到它的定义。

连续评价 1 应存储在 CA[0] 盒中,连续评价 2 应存储在 CA[1] 中,依此类推,直到连续评价 10。练习 1 的成绩应保存在项目 [0] 中,练习 2 应保存在项目 [1] 中,练习 3 应保存在项目 [2] 中。内存注释必须保存在报告字段中。最后,考试成绩必须保存在final_exam中。

要计算每个学生的最终成绩,必须进行以下计算:

学期成绩 = 50% 知识成绩 + 50% 项目成绩

知识等级是以下两者之间的最高值:

知识等级 = 70% 期末考试 + 30% 10 个 CA 的平均分

知识等级 = 100% 期末考试

练习成绩的计算公式为:

项目等级 = 30% 项目 1 + 30% 项目 2 + 30% 项目 3 + 10% 报告

计算最终成绩的每个学生的记录必须保存在名为“students.bin”的二进制文件中。 应用模块概念,我们想创建一个模块来求解它,该模块由文件 grades.h 和 grades.c 组成。grades.h 文件如下所示:

#ifndef _GRADES_H
#define _GRADES_H
#include <stdio.h>

#define MAX 100
#define MAX_STR 50

typedef char String[31];

typedef struct {
   String first_name;
   String last_name1;
   String last_name2;
   float CA[10];
   float Project[3];
   float report;
   float final_exam;
   float sem_grade;
} Student;

int loadGrades(char filename[MAX_STR], Student students[MAX]);
void computeGrades(Student students[MAX], int n_students);
void save(Student students[MAX], int n_students);
void updateFile();

#endif

您的任务是编写 grades.c 文件的内容。loadGrades() 函数打开并读取 filename 的内容,并将所有信息存储在 students 数组中,并返回已从文件中读取的学生数。computeGrades() 过程在考虑 CA、项目和期末考试的情况下计算最后一个学期的成绩,并更新sem_grade字段。save() 过程将数组 students 的全部内容保存在 Student 类型的二进制文件 (“students.bin”) 中。

最后,评分系统表明,学期成绩达到 5.0 或更高但期末考试成绩未达到至少 4.0 分的学生将获得 4.0 分作为学期的最终成绩。有必要搜索整个学生二进制文件(“students.bin”)以查找这些情况,并更改发生这种情况的所有学期成绩。这就是 updateFile() 函数将要执行的操作。

主要有:

#include <stdio.h>
#include "grade.h"

int main()
{   char filename[MAX_STR];
    Student students[MAX];
    int n_students = 0;
    
    printf("Enter filename: ");
    scanf("%s", filename);
    
    n_students = loadGrades(filename, students);    
    computeGrades(students, n_students);
    save(students, n_students);
    updateFile();    
    
    return 0;
}

这是可编辑的代码:

int loadGrades(char filename[], Student students[]) {
    char buffer[MAX_STR];
    int n_students = 0;
    int i = 0;
    int j = 0;
    FILE *fitxer = NULL;
    fitxer = fopen(filename, "r");
    if (NULL == fitxer) {
        printf("error");
    } else {
        fscanf(fitxer, "%s", students[i].first_name);
        while (!feof(fitxer)) {
            fscanf(fitxer, "%s", students[i].last_name1);
            fscanf(fitxer, "%s", students[i].last_name2);
            fscanf(fitxer, "%s", buffer);
            for (j = 0; j < 10; j++) {
                fscanf(fitxer, "%f", &students[i].CA[j]);
            }
            fscanf(fitxer, "%s", buffer);
            for (j = 0; j < 3; j++) {
                fscanf(fitxer, "%f", &students[i].Project[j]);
            }
            fscanf(fitxer, "%f", &students[i].report);
            fscanf(fitxer, "%s", buffer);
            fscanf(fitxer, "%f", &students[i].final_exam);
            n_students++;
            fscanf(fitxer, "%s", students[i].first_name);
        }
        fclose(fitxer);
    }
    return n_students;
}

void computeGrades(Student students[], int n_students) {
    int i = 0;
    int j = 0;
    float average = 0;
    float kg = 0;
    float pg = 0;
    for (i = 0; i < n_students; i++) {
        average = 0;
        kg = 0;
        pg = 0;
        for (j = 0; j < 10; j++) {
            average += students[i].CA[j];
        }
        average /= 10;
        pg = students[i].Project[0] * 0.3 + students[i].Project[1] * 0.3 +
             students[i].Project[0] * 0.3 + students[i].report * 0.1;
        kg = 0.7 * students[i].final_exam + 0.3 * average;
        if (students[i].final_exam > kg) {
            kg = students[i].final_exam;
        }
        students[i].sem_grade = 0.5 * kg + 0.5 * pg;
    }   
}

void save(Student students[], int n_students) {
    FILE *fitxer = NULL;
    int i = 0;
    fitxer = fopen("students.bin", "wb");
    if (NULL != fitxer) {
        for (i = 0; i < n_students; i++) {
            fwrite(&students[i], sizeof(Student), 1, fitxer);
        }
    }
    fclose(fitxer);
}

void updateFile() {
    Student student;
    FILE *fitxer = NULL;
    int i = 1;
    char filename[MAX_STR];
    strcpy(filename, "students.bin");
    fitxer = fopen(filename, "rb+");
    if (NULL != fitxer) {
        fread(&student, sizeof(Student), 1, fitxer);
        while (!feof(fitxer)) {
            fseek(fitxer, i * sizeof(Student), SEEK_SET);
            if ((5 <= student.sem_grade) && (4 > student.final_exam)){
                student.sem_grade = 4;
            }
            fseek(fitxer, i * sizeof(Student), SEEK_SET);
            fwrite(&student, i - 1 * sizeof(student), 1, fitxer);
            i++;
            fread(&student, sizeof(Student), 1, fitxer);
        }
    } else {
        printf("error");
    }
}

我认为我的问题在于函数,不知何故,我只将最后一个学生存储在文件中。loadGrades()

c 结构 scanf binaryfiles fseek

评论

3赞 Some programmer dude 3/3/2023
首先,从事做起。在代码中只添加小的和简单的位,一次一个。这将使测试和调试变得更加容易。至于问题本身,我建议你从文件中读取行,然后在函数中分别解析每一(并让函数返回单个结构)。尽可能地分离,做很多功能,其中每个功能都有一个职责,它应该只做一件事。同样,这使得测试和调试变得更加容易。Student
3赞 Some programmer dude 3/3/2023
另外,请花一些时间阅读为什么“while( !feof(file) )”总是错的?为了继续之前的评论,只是不要试图一次做太多。一小段简单的代码,一个接一个。生成(启用额外警告,视为错误)、测试和可能的调试。只有在您当前拥有的所有内容都干净地构建并且正在工作时,才继续下一小段代码。
0赞 chux - Reinstate Monica 3/3/2023
fscanf(fitxer, "%s",....很糟糕。它应该有一个宽度,以防止缓冲区溢出。应检查返回值。 无法读取姓氏中包含空格的姓氏。"%s"

答:

0赞 Schiggy25 3/3/2023 #1

我认为你的问题的解决方案是你永远不要在你的 while 循环中增加 i。您可能希望将 i 替换为 n_students 来解决这个问题并节省一些空间。我很确定你的代码中还有更多的错误,但我们不是在这里为你做功课;)

为您提供更多提示:

  • 尽可能本地声明
  • 传递指针而不是数组, 从技术上讲,两者是相同的,但某人获得的概率 错误地使用 sizeof() 的想法减少了
  • 使用 if-else 的 Guard Conditions 即时进行错误处理
1赞 chqrlie 3/3/2023 #2

代码中存在多个问题:

  • 永远不要用于测试文件末尾,只需测试 和 的返回值。feof()fscanf()fread()

  • 中的元素长度似乎无效。你的意思是:fwrite(&student, i - 1 * sizeof(student), 1, fitxer);

      fwrite(&student, sizeof(student), 1, fitxer);
    
  • 您必须在更新模式下对文件进行读取和写入的调用之间发出调用。fseek()rewind()

  • 更新模式非常混乱且容易出错,您应该考虑使用不同的方法。