C - 读取和迭代文件中的数据以计算总数

C - Reading and iterating through data from file to calculate totals

我正在尝试实现一个从名为 grad.dat 的文件中读取数据的程序。数据文件中的每条记录(即每一行)给出了给定国家、年份、学位和性别等的统计数据。记录的格式如下:

注意:只有 3 种不同类型的学位 - L6、L7 和 L8

grad.dat的一般格式:

AUS Australia F L6 2010 1276
AUS Australia M L6 2010 5779
AUS Australia F L6 2011 1255
AUS Australia M L6 2011 5739
BEL Belgium F L6 2017 157
BEL Belgium M L6 2017 1665
BEL Belgium F L7 2010 61
BEL Belgium F L8 2016 0
BEL Belgium F L8 2017 1
BEL Belgium M L8 2017 13
BRA Brazil F L6 2010 7187
BRA Brazil M L6 2010 32173
BRA Brazil F L6 2011 6240
BRA Brazil M L6 2011 30527
BRA Brazil M L6 2014 30156
BRA Brazil M L6 2016 32443
CAN Canada F L6 2010 561
CAN Canada F L6 2012 599
CAN Canada M L6 2012 3018

我正在尝试使用此数据生成一系列报告。我卡在第一份报告中,我必须包括:

每个国家/地区所有年份和所有性别的毕业生百分比(按学位)

一个。每个国家都是一行,每种学位都有一列,再加上一列 所有学位相加

b。每个单元格将显示该特定国家/地区特定学位的毕业生比例, 与所有国家/地区的毕业生总和相比,该学位

所以一般来说,我希望第一份报告打印成这样:

Country          L6         L7        L8       Total
----------------------------------------------------
Country#1         %          %         %       

Country#2         .....                         

Country#3                                       

我的程序可以编译。我需要将毕业生数据存储在一个链接列表中,我认为我做对了,但我无法弄清楚如何找到特定国家/地区所有学位的毕业生总数,以及总数为获得该学位,所有国家/地区的毕业生加起来。我的尝试是在 report.c 中,我认为我有正确的想法为每个学位(L6、L7、L8)声明变量,然后如果文件中记录的学位匹配,则将毕业人数添加到变量中变量。我还认为我有正确的想法,通过执行 totalAllDegreesForCountry = L6+L7+L8.

来计算特定国家/地区的毕业生总数

我 运行 遇到了一个问题,它多次打印同一个国家/地区,因为该国家/地区在文件中有多个记录。我想弄清楚如何只打印每个国家一次。我的程序似乎也打印了所有记录,然后在最后出现段错误,我不确定为什么。

我认为统计数据可能也有偏差,因为它打印的值是 1.00 和 0.00。

我非常感谢对此提供一些帮助,或者在正确的方向上推动我的第一份报告正确打印。

defs.h

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

#define MAX_STR 32

typedef struct {
  char code[MAX_STR];
  char country[MAX_STR];
  char gender[MAX_STR];
  char degree[MAX_STR];
  int year;
  int numberOfGrads;
} DataType;

typedef struct Node {
  DataType *data;
  struct Node *next;
} NodeType;

typedef struct {
    int size;
    NodeType *head;
    NodeType *tail;
} ListType;
void initData(DataType**, char*, char*, char*, char*, int, int);
void printData(const DataType*);
void addDataToList(ListType *list, DataType *m);
void printList(ListType*);  
void reportOne(ListType*);

main.c

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

#include "defs.h"   
int main()
{
  FILE *infile;  
  DataType *data;
  ListType *list ;
  char code[MAX_STR];
  char country[MAX_STR];
  char gender[MAX_STR];
  char degree[MAX_STR];
  int year;
  int numberOfGrads;
  char input[MAX_STR];

  list->size = 0;
  list->head = NULL;
  list->tail = NULL;

  infile = fopen("grad.dat", "r");
  if (!infile) {
    printf("Error: could not open file\n");
    exit(1);
  }
  
  while (1) {
    fscanf(infile, "%s %s %s %s %d %d ", code, country, gender, degree, &year,  &numberOfGrads);
    initData(&data, code, country, gender, degree, year, numberOfGrads);
    addDataToList(list, data);
    if (feof(infile))
      break;
  }
  fclose(infile);

  while (1){
     printf(" 0. Quit \n");
     printf(" 1 = Top 5 and bottom 5 countries of female graduates \n");

    printf("Enter a selection: ");
    scanf("%s", input);  

    if(strcmp(input, "1")==0){
        reportOne(list);
    }else if(strcmp(input, "0")==0){
      break;
    }
  }
}

void initData(DataType **r, char *co, char *c, char *g, char *d, int y, int n){
    *r = malloc(sizeof(DataType));
    strcpy((*r)->code, co);
    strcpy((*r)->country, c);
    strcpy((*r)->gender, g);
    strcpy((*r)->degree, d);
    (*r)->year = y;
    (*r)->numberOfGrads = n;
}

void printData(const DataType *data){
    printf("%s %s %s %s %d %d\n", data->code, data->country, data->gender, data->degree, data->year, data->numberOfGrads);
}

void addDataToList(ListType *list, DataType *m){
  NodeType *currNode;
  NodeType *prevNode;
  NodeType *newNode;

  prevNode=NULL;

  int currPos = 0;
  currNode = list->head;


  while (currNode != NULL) {
    prevNode = currNode;
    currNode = currNode->next;
  }

  newNode = malloc(sizeof(NodeType));
  newNode->data = m;
  newNode->prev = NULL;
  newNode->next = NULL;

  if (prevNode == NULL)
    list->head = newNode;
  else
    prevNode->next = newNode;

  if (currNode == NULL)
    list->tail = newNode;


  newNode->next = currNode;
  newNode->prev = prevNode;

  if (currNode != NULL)
    currNode->prev = newNode;
  list->size++;
  }

}

void printList(ListType* list){
    NodeType *currNode = list->head;
    while(currNode != NULL){
        printData(currNode->data);
        currNode = currNode->next;
    }
}

reports.c

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

#include "defs.h"

void reportOne(ListType* list){
  NodeType *currNode = list->head;
 
  float L6 = 0;
  float L7 = 0;
  float L8 = 0;
  
  float totalGradsCountry = 0; //specific country
  float totalGradsAll = 0; //all degrees all countries
  
  printf("%15s %10s %10s %10s %10s \n", "Country", "L6","L7","L8", "Total");
  printf("-------------------------------------------------------- \n");

  while (currNode != NULL) {
    if (strcmp(currNode->data->country, currNode->next->data->country) == 0){
            if(strcmp(currNode->data->degree, "L6")==0){ 
                L6 += currNode->data->numberOfGrads;
            }else if(strcmp(currNode->data->degree,"L7")==0){
                L7 += currNode->data->numberOfGrads;
            }else if(strcmp(currNode->data->degree,"L8")==0){
                L8 += currNode->data->numberOfGrads;
            }
            totalGradsCountry = L6 + L7 + L8;
            totalGradsAll += totalGradsCountry;
      printf("%-15s %6.2f %6.2f %6.2f %7.2f \n", currNode->data->country, L6/totalGradsCountry, L7/totalGradsCountry, L8/totalGradsCountry, totalGradsCountry/totalGradsAll);
        }
    currNode = currNode->next;
  }
}

我在下面附上了我的输出的 2 张图片,以显示我对我所面临的问题的看法。

好的,您的主要问题是在 reports.c 中取消对 NULL 的引用:

    while (currNode != NULL) {
        if (strcmp (currNode->data->country, currNode->next->data->country) ==

在你的最后一个节点上,node->next 指针是 NULL 但你试图访问 currNode->next->... 砰!段错误!

您只需要在条件中使用 currNode->next,例如

    while (currNode->next != NULL) {
        if (strcmp (currNode->data->country, currNode->next->data->country) ==

接下来,link Why is while ( !feof (file) ) always wrong? 解释了为什么你的读取循环会导致问题。永远用输入函数的return来控制你的read-loop,例如

    while (fscanf (infile, "%s %s %s %s %d %d ", code, country, gender, degree,
                &year, &numberOfGrads) == 6) {
        if (!(data = initData (code, country, gender, degree, year, numberOfGrads)))
            return 1;
        addDataToList (list, data);
    }

如果您注意到,initData 的声明已更改。在你盲目地开始使用 *r 之前,你没有有效的方法来指示 maillocinitData 中的成功或失败。相反,在任何分配内存、接受用户输入等的函数中(任何对代码的持续运行至关重要的函数),你需要选择一个有意义的return可用于指示函数的 success/failure 的类型。

不需要将指针data的地址作为参数传递。相反,在您的函数中声明 r 局部,然后在失败时声明 return NULL 或在调用者中返回分配块。您可以将原型更改为:

DataType *initData (char*, char*, char*, char*, int, int);

然后简单地让函数 return NULL 失败或 r 成功,例如

DataType *initData (char *co, char *c, char *g, char *d, int y, int n)
{
    DataType *r = malloc (sizeof (DataType));
    if (!r) {
        perror ("malloc-initData");
        return NULL;
    }
    strcpy (r->code, co);
    strcpy (r->country, c);
    strcpy (r->gender, g);
    strcpy (r->degree, d);
    r->year = y;
    r->numberOfGrads = n;
    
    return r;
}

最后,您的 addDataToList() 函数具有相同的 void 类型,无法指示 malloc() 的 success/failure(我会让您重写这个) .同时,您必须防止 out-of-memory 错误。您可以进一步大大缩短将节点添加到列表的方式。两者都做,并使用 tail 指针来实现 tail 指针应该用于的用途,您可以这样做:

void addDataToList (ListType * list, DataType * m)
{
    NodeType *newNode = malloc (sizeof (NodeType));
    if (!newNode) {
        perror ("malloc-newNode");
        exit (EXIT_FAILURE);
    }
    
    newNode->data = m;
    newNode->prev = NULL;
    newNode->next = NULL;
    
    if (!list->head)
        list->head = list->tail = newNode;
    else {
        newNode->prev = list->tail;
        list->tail->next = newNode;
        list->tail = newNode;
    }

    list->size++;
}

使用Header-Guards防止Header

的多重包含

写一个header文件时,只希望编译包含一次(多次包含会出问题,例如X的重定义等),以确保header 只包含一次,你把内容包在一个

#infdef SOME_HEADER_LABEL_H
#define SOME_HEADER_LABEL_H  1
...
your header content
...
#endif

这样在第一次包含时,编译器会检查是否定义了 SOME_HEADER_LABEL_H,如果没有,它会用 #define SOME_HEADER_LABEL_H 1 定义它,以便下次包含它时,它会直接跳过内容。 (1 不是绝对必要的——它可以省略,但如果你要定义一个常量,你也可以给它一个值)。对于您的 header 可能是:

#ifndef DEFS_H
#define DEFS_H 1

#define MAX_STR 32

typedef struct {
    char code[MAX_STR];
    char country[MAX_STR];
    char gender[MAX_STR];
    char degree[MAX_STR];
    int year;
    int numberOfGrads;
} DataType;

typedef struct Node {
    DataType *data;
    struct Node *prev;
    struct Node *next;
} NodeType;

typedef struct {
    int size;
    NodeType *head;
    NodeType *tail;
} ListType;

// void initData(DataType**, char*, char*, char*, char*, int, int);
DataType *initData (char*, char*, char*, char*, int, int);
void printData(const DataType*);
void addDataToList (ListType*, DataType*);
void reportOne(ListType*);

#endif

无需为 datamain()

中分配

再看一看,main() 中的 DataType *data; 根本不需要分配。它是临时的,因此只需使用 automatic storage 类型声明它并将其重新用于所有输入——但您需要在 addDataToList 中分配 newNode->datamemcpy 而不是分配 m。所以无论哪种方式都可以。 (该代码在与下面编辑中显示的报告输出相关的更新中显示为注释)

避免 MagicNumbers 和硬编码文件名

在您的代码中,您将 "grad.dat" 硬编码为要读取的文件名。这意味着每次要从另一个文件读取时都必须重新编译代码。相反,使用 int main (int argc, char **argv) 将要读取的文件名作为参数传递给您的程序——这就是 main() 的参数的用途。您可以使用简单的三元组从作为参数给定的文件名中读取,或者如果没有给出参数,则默认从 "grad.dat" 中读取。没关系,但是为文件名定义一个常量,例如

#define DATAFILE "grad.dat"
...
    /* do not hardcode filenames, you can use it as a default */
    infile = fopen (argc > 1 ? argv[1] : DATAFILE, "r");
    if (!infile) {  /* good job validating fopen */
        printf ("Error: could not open file\n");
        exit (1);
    }

制作reportOne()按国家汇总

这需要更多的思考才能达成。有几种方法可以解决这个问题。您要么使用稍微复杂的数据结构来保存每个国家/地区的摘要数据以及 totalGradsCountry 值,要么您只存储国家/地区数据的摘要,然后必须在 [=64= 中遍历列表两次],一次求和 totalGradsCountry,然后再次使用 totalGradsCountry.

输出结果

基本上,要在一次遍历列表时捕获信息,需要构建一个包含唯一国家/地区的数组,以及捕获的 L6L7L8 的总和使用 country(名称)和可选的 code(缩短 strcmp 以确定节点中的国家/地区)。所以你可以使用类似的东西:

#define NSUM 2                  /* initial no. of countrytype to allocate */

typedef struct {                /* struct to hold summary of country data */
    char  code[MAX_STR],
          country[MAX_STR];
    double  L6, 
            L7,
            L8;
} countrytype;

typedef struct {                /* struct to hold array of countries and totalgrads */
    countrytype *countries;
    double totalGradsAll;
} totaltype;

然后是遍历完整列表的问题,对于每个独特的国家/地区,为 countrytype 分配存储空间,并在该国家/地区的第一个节点上,复制 codecountry 信息,并为该国家/地区的所有剩余条目添加 L6L7L8 值以及 totalGradsAll 总和。在为 countrytype 分配(和重新分配)时,将内存归零允许您检查 code 是否是 empty-string 以了解您是否拥有该国家/地区的 first-occurrence。

reportOne()函数变为:

void reportOne (ListType *list)
{
    size_t used = 0, avail = NSUM;      /* number used, number allocated */
    totaltype total = { .countries = calloc (avail, sizeof(countrytype)) };
    NodeType *node = list->head;
    
    if (!total.countries) {             /* validate initial allocation */
        perror ("calloc-.countries");
        return;
    }
    
    while (node) {  /* loop over each node in list */
        /* check if country code exists in summary list of countries */
        size_t ndx = codeexists (total.countries, node->data->code, &used);
        
        if (used == avail)  /* check if realloc needed */
            total.countries = addcountry (total.countries, &avail);
        
        addtosum (&total, ndx, node->data);     /* add current node info to summary */
        
        node = node->next;      /* advance to next node */
    }
    
    puts (  "\nCountry            L6        L7        L8        Total\n"
            "-------------------------------------------------------");
    
    for (size_t i = 0; i < used; i++) {     /* loop over countries in summary */
        /* sum L6 + L7 + L8 for country */
        double totalGradsCountry =  total.countries[i].L6 + total.countries[i].L7 + 
                                    total.countries[i].L8;
        /* output results */
        printf ("%-15s %6.2f    %6.2f    %6.2f    %7.2f\n",
                total.countries[i].country,
                total.countries[i].L6 / totalGradsCountry,
                total.countries[i].L7 / totalGradsCountry,
                total.countries[i].L8 / totalGradsCountry,
                totalGradsCountry / total.totalGradsAll);
    }
    
    free (total.countries);     /* free all memory allocated for summary */ 
}

现在您看到了几个 helper-functions,将它们放在函数中有助于保持您的 reportOne 逻辑可读。助手是codeexists()(例如,这个国家代码是否已经存在于countrytype的块中)有了它return 分配给 countrytype 区块中的 index 允许您简单地更新 L6L7L8指数。如果该索引不存在,则它是您添加到 used 计数中的新国家/地区,以跟踪何时需要重新分配。这是一个简单的函数:

/* returns index of country matching code or next available index,
 * incrementing the value at n (used) for each new index returned.
 */
size_t codeexists (const countrytype *c, const char *code, size_t *n)
{
    size_t i;
    
    for (i = 0; i < *n; i++)                    /* loop over countries */
        if (strcmp (c[i].code, code) == 0)      /* if codes match, break to return index */
            break;
    
    if (i == *n)        /* if code not found in countries */
        *n += 1;        /* new country found, increment n (used) */
    
    return i;           /* return index */
}

下一个助手是 addcountry(),当您 used == avail(国家/地区使用的区块等于分配的数量)时,需要重新分配,这就是 addcountry() 所做的,返回一个指向新分配的分配块的指针(或如果发生错误则退出),例如

/* reallocate countries when used == available,
 * new memory is zeroed for 1st char check in addtosum,
 * program exits on memory failure.
 */
countrytype *addcountry (countrytype *c, size_t *avail)
{
    /* always realloc using a temporary pointer */
    void *tmp = realloc (c, 2 * *avail * sizeof *c);
    if (!tmp) { /* validate reallocation */
        perror ("realloc-countrytype");
        exit (EXIT_FAILURE);
    }
    c = tmp;                                        /* assing realloc'ed block to ptr */
    memset (c + *avail, 0, *avail * sizeof *c);     /* zero newly allocated memory */
    *avail *= 2;                                    /* update no. of countries avail */
    
    return c;       /* return pointer for assignment in caller */
}

最后一个助手是 addtosum(),如果需要,它会简单地复制 codecountry 名称,并更新 L6L7L8 对于给定的索引,

/* add current nodes information to the country and total sums */
void addtosum (totaltype *t, size_t ndx, DataType *d)
{
    if (!*t->countries[ndx].code) {                 /* if new country, copy info */
        strcpy (t->countries[ndx].code, d->code);
        strcpy (t->countries[ndx].country, d->country);
    }
    
    switch (d->degree[1]) { /* switch on 2nd character */
        case '6':   t->countries[ndx].L6 += d->numberOfGrads; break;
        case '7':   t->countries[ndx].L7 += d->numberOfGrads; break;
        case '8':   t->countries[ndx].L8 += d->numberOfGrads; break;
        default :   fputs ("error: addtosum switch -- we shouldn't be here\n", stderr);
                    return;
    }
    t->totalGradsAll += d->numberOfGrads;  /* add to total for all */
}

A switch() 用于上面 degree 的第二个字符,例如678 更新为适当的值,而无需每次调用​​ strcmp()

综合

这些是需要的改变。所以你的 defs.h 变成:

#ifndef DEFS_H
#define DEFS_H 1

#define MAX_STR 32

typedef struct {
    char code[MAX_STR];
    char country[MAX_STR];
    char gender[MAX_STR];
    char degree[MAX_STR];
    int year;
    int numberOfGrads;
} DataType;

typedef struct Node {
    DataType *data;
    struct Node *prev;
    struct Node *next;
} NodeType;

typedef struct {
    int size;
    NodeType *head;
    NodeType *tail;
} ListType;

// void initData (DataType*, char*, char*, char*, char*, int, int);
DataType *initData (char*, char*, char*, char*, int, int);
void printData (const DataType*);
void addDataToList (ListType*, DataType*);
void reportOne (ListType*);
// void reportTwo (ListType*);

#endif

你的main.c(注释掉的代码显示使用data和自动存储期限)变成:

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

#include "defs.h"

#define DATAFILE "grad.dat"

int main (int argc, char **argv)
{
    FILE *infile;
    DataType *data;
    ListType *list = malloc (sizeof *list);
    char code[MAX_STR];
    char country[MAX_STR];
    char gender[MAX_STR];
    char degree[MAX_STR];
    int year;
    int numberOfGrads;
    char input[MAX_STR];
    
    if (!list) {    /* validate EVERY allocation */
        perror ("malloc-list");
        return 1;
    }
    
    list->size = 0;
    list->head = NULL;
    list->tail = NULL;
    
    /* do not hardcode filenames, you can use it as a default */
    infile = fopen (argc > 1 ? argv[1] : DATAFILE, "r");
    if (!infile) {  /* good job validating fopen */
        printf ("Error: could not open file\n");
        exit (1);
    }
    
    /* always control read-loop with return of the input function itself */
    while (fscanf (infile, "%s %s %s %s %d %d ", code, country, gender, degree,
                &year, &numberOfGrads) == 6) {
        if (!(data = initData (code, country, gender, degree, year, numberOfGrads)))
            return 1;
        addDataToList (list, data);
    }
    fclose (infile);
    
    while (1) {
        fputs ( "\n 0. Quit \n"
                " 1 = Top 5 and bottom 5 countries of female graduates\n"
                "Enter a selection: ", stdout);
        if (scanf ("%s", input) != 1) {
            puts ("(user canceled input)");
            return 1;
        }

        if (strcmp (input, "1") == 0) {
            reportOne (list);
        }
        else if (strcmp (input, "0") == 0) {
            break;
        }
    }
}

DataType *initData (char *co, char *c, char *g, char *d, int y, int n)
{
    DataType *r = malloc (sizeof (DataType));
    if (!r) {
        perror ("malloc-initData");
        return NULL;
    }
    strcpy (r->code, co);
    strcpy (r->country, c);
    strcpy (r->gender, g);
    strcpy (r->degree, d);
    r->year = y;
    r->numberOfGrads = n;
    
    return r;
}

/* initData for use of data with automatic storage in main */
// void initData (DataType *r, char *co, char *c, char *g, char *d, int y, int n)
// {
//     strcpy (r->code, co);
//     strcpy (r->country, c);
//     strcpy (r->gender, g);
//     strcpy (r->degree, d);
//     r->year = y;
//     r->numberOfGrads = n;
// }

void printData (const DataType * data)
{
    printf ("%s %s %s %s %d %d\n", data->code, data->country, data->gender,
            data->degree, data->year, data->numberOfGrads);
}

void addDataToList (ListType * list, DataType * m)
{
    NodeType *newNode = malloc (sizeof (NodeType));
    if (!newNode) {
        perror ("malloc-newNode");
        exit (EXIT_FAILURE);
    }
    /* allocation and copy if using data with automatic storage in main */
    // if (!(newNode->data = malloc (sizeof *newNode->data))) {
    //     perror ("malloc-newNode->data");
    //     exit (EXIT_FAILURE);
    // }
    
    // memcpy (newNode->data, m, sizeof *m);
    newNode->data = m;
    newNode->prev = NULL;
    newNode->next = NULL;
    
    if (!list->head)
        list->head = list->tail = newNode;
    else {
        newNode->prev = list->tail;
        list->tail->next = newNode;
        list->tail = newNode;
    }

    list->size++;
}

最后更新后的 reports.c 变成:

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

#include "defs.h"

#define NSUM 2                  /* initial no. of countrytype to allocate */

typedef struct {                /* struct to hold summary of country data */
    char  code[MAX_STR],
          country[MAX_STR];
    double  L6, 
            L7,
            L8;
} countrytype;

typedef struct {                /* struct to hold array of countries and totalgrads */
    countrytype *countries;
    double totalGradsAll;
} totaltype;

/* returns index of country matching code or next available index,
 * incrementing the value at n (used) for each new index returned.
 */
size_t codeexists (const countrytype *c, const char *code, size_t *n)
{
    size_t i;
    
    for (i = 0; i < *n; i++)                    /* loop over countries */
        if (strcmp (c[i].code, code) == 0)      /* if codes match, break to return index */
            break;
    
    if (i == *n)        /* if code not found in countries */
        *n += 1;        /* new country found, increment n (used) */
    
    return i;           /* return index */
}

/* reallocate countries when used == available,
 * new memory is zeroed for 1st char check in addtosum,
 * program exits on memory failure.
 */
countrytype *addcountry (countrytype *c, size_t *avail)
{
    /* always realloc using a temporary pointer */
    void *tmp = realloc (c, 2 * *avail * sizeof *c);
    if (!tmp) { /* validate reallocation */
        perror ("realloc-countrytype");
        exit (EXIT_FAILURE);
    }
    c = tmp;                                        /* assing realloc'ed block to ptr */
    memset (c + *avail, 0, *avail * sizeof *c);     /* zero newly allocated memory */
    *avail *= 2;                                    /* update no. of countries avail */
    
    return c;       /* return pointer for assignment in caller */
}

/* add current nodes information to the country and total sums */
void addtosum (totaltype *t, size_t ndx, DataType *d)
{
    if (!*t->countries[ndx].code) {                 /* if new country, copy info */
        strcpy (t->countries[ndx].code, d->code);
        strcpy (t->countries[ndx].country, d->country);
    }
    
    switch (d->degree[1]) { /* switch on 2nd character */
        case '6':   t->countries[ndx].L6 += d->numberOfGrads; break;
        case '7':   t->countries[ndx].L7 += d->numberOfGrads; break;
        case '8':   t->countries[ndx].L8 += d->numberOfGrads; break;
        default :   fputs ("error: addtosum switch -- we shouldn't be here\n", stderr);
                    return;
    }
    t->totalGradsAll += d->numberOfGrads;  /* add to total for all */
}

void reportOne (ListType *list)
{
    size_t used = 0, avail = NSUM;      /* number used, number allocated */
    totaltype total = { .countries = calloc (avail, sizeof(countrytype)) };
    NodeType *node = list->head;
    
    if (!total.countries) {             /* validate initial allocation */
        perror ("calloc-.countries");
        return;
    }
    
    while (node) {  /* loop over each node in list */
        /* check if country code exists in summary list of countries */
        size_t ndx = codeexists (total.countries, node->data->code, &used);
        
        if (used == avail)  /* check if realloc needed */
            total.countries = addcountry (total.countries, &avail);
        
        addtosum (&total, ndx, node->data);     /* add current node info to summary */
        
        node = node->next;      /* advance to next node */
    }
    
    puts (  "\nCountry            L6        L7        L8        Total\n"
            "-------------------------------------------------------");
    
    for (size_t i = 0; i < used; i++) {     /* loop over countries in summary */
        /* sum L6 + L7 + L8 for country */
        double totalGradsCountry =  total.countries[i].L6 + total.countries[i].L7 + 
                                    total.countries[i].L8;
        /* output results */
        printf ("%-15s %6.2f    %6.2f    %6.2f    %7.2f\n",
                total.countries[i].country,
                total.countries[i].L6 / totalGradsCountry,
                total.countries[i].L7 / totalGradsCountry,
                total.countries[i].L8 / totalGradsCountry,
                totalGradsCountry / total.totalGradsAll);
    }
    
    free (total.countries);     /* free all memory allocated for summary */ 
}

注意 -- reportOne() 中使用的所有内存在 returning 之前被释放。您还需要确保在完成剩余代码后进行清理。

花点时间研究一下如何使用 countrytypetotaltype 结构来生成最终输出。那里有很多,所以只是慢下来然后line-by-line然后跟着它走。

示例Use/Output

根据所提供的有限样本数据,您将收到:

$ ./bin/main 

 0. Quit 
 1 = Top 5 and bottom 5 countries of female graduates
Enter a selection: 1

Country            L6        L7        L8        Total
-------------------------------------------------------
Australia         1.00      0.00      0.00       0.09
Belgium           0.96      0.03      0.01       0.01
Brazil            1.00      0.00      0.00       0.87
Canada            1.00      0.00      0.00       0.03

 0. Quit 
 1 = Top 5 and bottom 5 countries of female graduates
Enter a selection:

给定 sample-data 中的值看起来是预期的。确认留给你。

您的代码应该 运行 适合这些更改。如果没有,请发表评论,我可能忘记了一两个更改。如果您还有其他问题,请告诉我。