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
之前,你没有有效的方法来指示 mailloc
在 initData
中的成功或失败。相反,在任何分配内存、接受用户输入等的函数中(任何对代码的持续运行至关重要的函数),你需要选择一个有意义的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
无需为 data
在 main()
中分配
再看一看,main()
中的 DataType *data;
根本不需要分配。它是临时的,因此只需使用 automatic storage 类型声明它并将其重新用于所有输入——但您需要在 addDataToList
中分配 newNode->data
和memcpy
而不是分配 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
.
输出结果
基本上,要在一次遍历列表时捕获信息,需要构建一个包含唯一国家/地区的数组,以及捕获的 L6
、L7
和 L8
的总和使用 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
分配存储空间,并在该国家/地区的第一个节点上,复制 code
和 country
信息,并为该国家/地区的所有剩余条目添加 L6
、L7
和 L8
值以及 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
允许您简单地更新 L6
、L7
和 L8
指数。如果该索引不存在,则它是您添加到 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()
,如果需要,它会简单地复制 code
和 country
名称,并更新 L6
、L7
和 L8
对于给定的索引,
/* 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
的第二个字符,例如6
、7
或 8
更新为适当的值,而无需每次调用 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 之前被释放。您还需要确保在完成剩余代码后进行清理。
花点时间研究一下如何使用 countrytype
和 totaltype
结构来生成最终输出。那里有很多,所以只是慢下来然后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 中的值看起来是预期的。确认留给你。
您的代码应该 运行 适合这些更改。如果没有,请发表评论,我可能忘记了一两个更改。如果您还有其他问题,请告诉我。
我正在尝试实现一个从名为 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
之前,你没有有效的方法来指示 mailloc
在 initData
中的成功或失败。相反,在任何分配内存、接受用户输入等的函数中(任何对代码的持续运行至关重要的函数),你需要选择一个有意义的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
无需为 data
在 main()
再看一看,main()
中的 DataType *data;
根本不需要分配。它是临时的,因此只需使用 automatic storage 类型声明它并将其重新用于所有输入——但您需要在 addDataToList
中分配 newNode->data
和memcpy
而不是分配 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
.
基本上,要在一次遍历列表时捕获信息,需要构建一个包含唯一国家/地区的数组,以及捕获的 L6
、L7
和 L8
的总和使用 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
分配存储空间,并在该国家/地区的第一个节点上,复制 code
和 country
信息,并为该国家/地区的所有剩余条目添加 L6
、L7
和 L8
值以及 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
允许您简单地更新 L6
、L7
和 L8
指数。如果该索引不存在,则它是您添加到 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()
,如果需要,它会简单地复制 code
和 country
名称,并更新 L6
、L7
和 L8
对于给定的索引,
/* 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
的第二个字符,例如6
、7
或 8
更新为适当的值,而无需每次调用 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 之前被释放。您还需要确保在完成剩余代码后进行清理。
花点时间研究一下如何使用 countrytype
和 totaltype
结构来生成最终输出。那里有很多,所以只是慢下来然后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 中的值看起来是预期的。确认留给你。
您的代码应该 运行 适合这些更改。如果没有,请发表评论,我可能忘记了一两个更改。如果您还有其他问题,请告诉我。