我需要有关在 C 文本文件中显示前 5 名分数的功能的帮助

I need help regarding the function to show the top 5 scores in text file in C

我目前正在为我的 C 编程初学者课程创建一个非常简单的程序,但我被卡住了。我希望能够打印保存在文本文件中的 5 个最佳分数,但现在它只显示前 5 行而不是最佳分数行。历史部分工作正常,正在打印所有内容,但我不明白如何打印 5 个最好的分数,也许它与整理外部文本文件中的信息有关?对不起,如果这个 post 很愚蠢,但我需要问问某个地方(我真的很基础)。

这是我认为无法正常工作的部分代码,它是针对代码块的:

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

char name [40];     // Here every integer and character etc. is introduced as global.
int menu(char *name);
int startPractice(int choice);
int startTest(int choice, char *name);
char randomOperator();
int getRandomNumber(int min, int max);
int calc(int a, int b, char c);
int rand(void);
int showHistory();
int saveHistory(char *name, float average);
int showTop5();

struct gamer{
char *username;
float average;
};

int main()
{
printf("Hello and welcome to the program!\n"); // This is the introduction
printf("\nWhat is your name? ");
scanf("%s", &name);


printf("\nWelcome %s! \n\nWhat would you like to do? \n" , name);
menu(&name); // This line calls the main menu.

return 0;
}

int compare(const void *s1, const void *s2)
{
struct gamer *g1 = (struct gamer *)s1;
struct gamer *g2 = (struct gamer *)s2;/* same gender so sort by id */

return g1->average - g2->average;
}

int menu(char *name) {        // This is the main menu and whenever this function is called the program starts from here.
int qanswer;        // This variable is set so it can be used in this body, it is not a global integer.

printf("\n1. Do practices \n2. Do a test \n3. Quit \n4. Test history \n5. Top 5 \n");
scanf("%d", &qanswer);

switch (qanswer) {      // Switch cases for a more compressed code, here only one case will be activated and that is decided by the value of "qanswer".
    case 1:
        printf("\nNow, you can choose to do practices on: \n\n1. Additions\n2. Subtractions\n3. Addition and Subtractions: ");
        printf("\n\nEnter your choice: ");
        scanf("%d" , &qanswer);
        startPractice(qanswer);
        break;      //This breaks the code at any chosen case.
    case 2:
        printf("\nYou will be given 15 questions to solve as the test!\n\n");
        printf("\nYou can choose to do the test on: \n\n1. Additions\n2. Subtractions\n3. Addition and Subtractions: ");
        printf("\n\nEnter your choice: ");
        scanf("%d", &qanswer);
        startTest(qanswer, name);
        break;
    case 3:
        printf("\nExiting program... \n");
        exit(0); // Shuts down the program.
        break;
    case 4:
        printf("Test history\n");
        showHistory();
        break;
    case 5:
        printf("Top 5 test results:\n");
        showTop5();
        break;
    default:        // if the user enter anything except 1,2,3 this line of code will be returned.
        printf("Your input wasn't valid. Please try again...\n\n");
        menu(name);     // This calls the main menu again
}

return 0;
}

int startPractice(int choice) {     // If practice is chosen then this function will be called.

int a, b, answer1;
char c;

int i;
for (i = 1; i <= 10; i++) {     // The for loop runs this code until i=10 and increases by 1 each time. Therefore this code will run 10 times fully.
    a = getRandomNumber(1, 30);     // This calls the function for a random number.
    b = getRandomNumber(1, 30);
    if (choice == 1) {      // If choice is set to "1" c will be t to + otherwise -.
        c = '+';
    } else if (choice == 2) {
        c = '-';
    } else {
        c = randomOperator();
    }

    printf("%d. %d %c %d = ", i, a, c, b);
    scanf("%d", &answer1);

    while (answer1 != calc(a, b, c)){
        printf("Try again! ");
        scanf("%d", &answer1);
    }

    printf("Very good!\n\n");
}
printf("Practice is complete\n");
printf("\nNow what would you like to do?\n");
menu(name);

return 0;
}

int startTest(int choice, char *name) {
int a, b, answer1;
char c;
int counter = 0;
float average;
int i;
for (i = 1; i <= 15; i++) {
    a = getRandomNumber(1, 30);
    b = getRandomNumber(1, 30);
    if (choice == 1) {
        c = '+';
    } else if (choice == 2) {
        c = '-';
    } else {
        c = randomOperator();
    }

    printf("%d. %d %c %d = ", i, a, c, b);
    scanf("%d", &answer1);
    if (answer1 == calc(a, b, c)){
        counter++;
    }

    printf("\n\n");
}
printf("The test is complete\n");
average = (float) counter/(float)15;        // This calculates the average score as a float with the counter / 15.
printf("You scored %d out of 15 which is and average of %d %%\n", counter, (int)average*100);       // This line takes the value of average and multiply it by 100 to show the percentage.
saveHistory("Test", average);
printf("\nNow what would you like to do?\n");
menu(name); // This function calls the main menu again.

return 0;

}

int calc(int a, int b, char c) {        // This function is used to define the equation as (a + b) or (a - b).
switch(c) {
    case '+':
        return a+b;
    case '-':
        return a-b;
}
}

char randomOperator() {         //This code is used to decide if + or - is going to be used.
switch(getRandomNumber(0,1)) {      //Switch statement when you need two or more ways to return a value.
    case 0:
        return '+';     //If the random number is 0 return is + otherwise it is -.
    case 1:
        return '-';
}

return '+';
}

int getRandomNumber(int min, int max) {     // This simply decides a random value of min 0 and max 1.
return rand() % (max + 1 - min) + min;
}

int showHistory(){
FILE *f;
char c;
f=fopen("test.txt","rt");

if (f) {
    while((c=fgetc(f))!=EOF){
        printf("%c",c);
    }

    fclose(f);
    printf("\nWhat would you like to do now?\n");
} else {
    printf("There is no history file at the moment.");
}

menu(name);

return 0;
}

int saveHistory(char *name, float average){
FILE *f;
char c;
f=fopen("test.txt","a");

fprintf(f, "%s_%.2f\n", name, average);

fclose(f);
return 0;
}
int showTop5(){
//printf("\n");
FILE *f;
char c;
char line[256];
int count = 0;
f=fopen("test.txt","rt");

if (f) {
    while (fgets(line, sizeof(line), f)) {
        if (strlen(line) > 0) {
            count++;
        }
    }

    char delimiter[] = "_";
    char *ptr;

    fclose(f);

    f=fopen("test.txt","rt");

    struct gamer gamers[count];
    int i = 0;
    printf("\n");
    while (fgets(line, sizeof(line), f)) {
        ptr = strtok(line, delimiter);
        char n[40];
        strcpy(n, ptr);
        //printf("n: %s, ", n);
        ptr = strtok(NULL, delimiter);
        float avg = *(float *) ptr;
        //printf("points: %f\n", avg);
        gamers[i].average = avg;
        gamers[i].username = n;

        i++;
    }

    qsort(gamers, count, sizeof(struct gamer), compare);

    int j;
    for(j = 0; j < count; j++){
        if (j==5){
        break;
    }
        printf("Rank %d: %s with %.2f %% as average.\n", j+1, gamers[j].username, gamers[j]. average);
    }

    fclose(f);
} else {
    printf("There is no history file at the moment.");
}

menu(name);

return 0;
}

因此,当我修复导致程序崩溃的代码时(如答案中所述,谢谢!)似乎排序问题来自这行代码,没有正确处理值,因为我想要到 return 它作为百分比并且它必须在小数点后停止读取,这使得每个用户在有意义的情况下得分为 0 或 1。当 return 值乘以 100 时,它现在有效。

从这段代码开始:

int compare(const void *s1, const void *s2)
{
struct gamer *g1 = (struct gamer *)s1;
struct gamer *g2 = (struct gamer *)s2;/* same gender so sort by id */

return g1->average - g2->average;
}

这段代码似乎是解决问题的方法。

int compare(const void *s1, const void *s2)
{
struct Gamer *g1 = (struct Gamer *)s1;
struct Gamer *g2 = (struct Gamer *)s2;/* same gender so sort by id */

return (int)((g2->average - g1->average)*100);

这行不通

 ptr = strtok(NULL, delimiter);
    float avg = *(float *) ptr;

您将文件中的分数视为浮点数的二进制表示。不是,是文字。你需要做

   ptr = strtok(NULL, delimiter);
    float avg = atof(ptr);

考虑到前面的评论,使用qsort()优化函数正确管理struct gamer的数组排序之前还有其他问题。

问题 1 - 没有分配 space 来将 username 存储到 struct gamer.

  1. struct gamer 正在使用 char *username;;
  2. "test.txt" 文件读取数据时,用户名只存储指向缓冲区的指针而不是内容 gamers[i]->username = n;;
  3. 即使读取的用户名是strcpy(n, ptr);,该存储在堆中并在下一次读取操作时被覆盖;
  4. 正如@pm100 所建议的那样,平均值被解码为二进制数据而不是文本值 float avg = *(float *) ptr;

showTop5()函数中使用:

while (fgets(line, sizeof(line), f)) {
    ptr = strtok(line, delimiter);
    char n[40];
    strcpy(n, ptr);
    ptr = strtok(NULL, delimiter);
    float avg = atof(ptr); // @pm100 proposed solution
    gamers[i].average = avg;
    gamers[i].username = (char *)malloc(sizeof(char)*(strlen(n)+1));
    strcpy(gamers[i].username,n); // allocate and copy the username

    i++;
}

而不是

    while (fgets(line, sizeof(line), f)) {
        ptr = strtok(line, delimiter);
        char n[40];
        strcpy(n, ptr);
        //printf("n: %s, ", n);
        ptr = strtok(NULL, delimiter);
        float avg = *(float *) ptr;
        //printf("points: %f\n", avg);
        gamers[i].average = avg;
        gamers[i].username = n; // error no size allocated

        i++;
    }

问题 2 - 使用 qsort() 时,数据结构应具有固定大小的长度。使用指针数组struct gamer *进行排序。

  1. 如果用sizeof(struct gamer)qsort()float averagechar *username会排序,但没有优化;
  2. 分配大小存储username后,如果数组struct gamer gamers[count];是从堆中分配的,将不容易释放内存;
  3. 如果玩家数量多,在堆中存储可能是个问题;

正在分配 gamers[] 数组:

struct gamer **gamers;
int i;

gamers = malloc(count*sizeof(struct gamer *));
for (i = 0; i < count; i++) {
    gamers[i] = malloc(sizeof(struct gamer));
}

管理对数组的读操作:

See the source code in the Problem 1

更新快速排序的compare函数:

int compare(const void *s1, const void *s2)
{
    struct gamer *g1 = *((struct gamer **)s1); // instead of (struct gamer *)s1;
    struct gamer *g2 = *((struct gamer **)s2); // instead of (struct gamer *)s2;
    // as @BeginnerC suggests, reverse the comparison
    return ((int)((g2->average - g1->average)*100));
}

执行快速排序:

qsort(gamers, count, sizeof(struct gamer *), compare);

打印前 5 名玩家:

for(j = 0; j < count; j++){
    if (j==5){
        break;
    }
    printf("Rank %d: %s with %.2f %% as average.\n", j+1,
        gamers[j]->username, gamers[j]->average);
}

释放分配的 gamers[] 数组:

for (i = 0; i < count; i++) {
    free (gamers[i]->username); // allocated to the username
    free (gamers[i]);
}
free(gamers);

注:为了简化问题1的求解,为了固定大小struct gamer,字段char *username可以替换为char username[40];(因为 char n[40];strcpy(n, ptr); 之前。