I have this struct named TEAM which holds several information regarding a football team.
typedef struct team{
int ID; //1
char Team_name[MAX_LEN]; //2
int Status; //3
int Points; //4
int Score; //5
int Goals; //6
struct tm Date; //7
struct team *next;
}TEAM;
I am creating a linked list of teams and team information is read from a file and it is in the following format:
ID;teamname;status;points;score;goals;day/month/year hour:minute
I have no problem getting the data from the file and doing the required assignments. However I am having problem with the date part.
I use strtok() to split a line into tokens and I try to use the following code to assign the parts of the token to their respective variables.
fscanf(token, "%d/%d/%d %d:%d", &tmp->Date.tm_mday, &tmp->Date.tm_mon, &tmp->Date.tm_year, &tmp->Date.tm_hour, &tmp->Date.tm_min);
But this gives this return value -1073741819 (0xC0000005) which is as far as I know known as illegal memory access something I did with '&' symbol or fscanf() is wrong.
My code works fine without fscanf() so what am I doing wrong?
Edit: The whole code:
typedef struct team{
int ID; //1
char Team_name[MAX_LEN]; //2
int Status; //3
int Points; //4
int Score; //5
int Goals; //6
struct tm Date; //7
struct team *next;
}TEAM;
TEAM *createNode(void){
TEAM *node;
node = (TEAM *)malloc(sizeof(TEAM));
node->next = NULL;
return node;
}
TEAM *initialiseTeams(void){
FILE *fp;
TEAM *head;
TEAM *tmp;
char line[BUFFER];
char *token;
const char delim[2] = ";";
fp = fopen("Teams.txt","r");
if(fp == NULL){
printf("Failed to open Teams.txt\n");
exit(EXIT_FAILURE);
}
head = createNode();
tmp = head;
while(fgets(line, BUFFER, fp)){
int i;
for(i = 0, token = strtok(line, delim); token != NULL; i++, token = strtok(NULL, delim)){
printf("%s\n", token);
switch(i){
case 0: // ID
tmp->ID = atoi(token);
break;
case 1: // Team name
strcpy(tmp->Team_name, token);
break;
case 2: // Status
if(strcmp(token, "L") == 0){
tmp->Status = 0;
}
if(strcmp(token, "W") == 0){
tmp->Status = 1;
}
if(strcmp(token, "D") == 0){
tmp->Status = 2;
}
break;
case 3: // Points
tmp->Points = atoi(token);
break;
case 4: // Score
tmp->Score = atoi(token);
break;
case 5: // Goals
tmp->Goals = atoi(token);
break;
case 6: // Date
fscanf(token, "%d/%d/%d %d:%d", &tmp->Date.tm_mday, &tmp->Date.tm_mon, &tmp->Date.tm_year, &tmp->Date.tm_hour, &tmp->Date.tm_min);
break;
}
}
tmp->next = createNode();
tmp = tmp->next;
}
free(tmp);
return head;
}
int main(void){
TEAM *head;
head = initialiseTeams();
}
You are wildly over-complicating creating your list. This is partially due to trying to offload all I/O and list operations to the initialiseTeams()
. Generally you want to keep your list operations separate from your program interface (the I/O, how it interacts with the user, etc..) That way, regardless of where you need to add a node from (file, user-input, etc..), you can simply call an add()
(or push()
) function to add a new node to your list.
You have done a good job separating the createnode()
function. You can utilize it further by passing a pointer to a TEAM
struct as a parameter that can be used to initialize the new node in the createnode()
function. For example:
TEAM *createNode (TEAM *t)
{
TEAM *node = malloc (sizeof *node); /* allocate */
if (!node) { /* validate */
perror ("malloc-node");
return NULL;
}
if (t) /* assign struct (if not NULL) */
*node = *t;
node->next = NULL;
return node;
}
It take very little extra effort. There is no reason to call your createnode()
function until you have validated the data to add, so passing a TEAM*
pointer allows you to handle all the creation and initialization in the function.
Your initialiseTeams()
should take an open FILE*
stream to read from as a parameter. If the file cannot be opened and validate back in the caller -- there is no need to call the initialiseTeams()
function to begin with.
You can read with fgets()
as you do, but there is no need to tokenize the line with strtok()
. A simple carefully crafted sscanf()
format string will do. This greatly simplifies your function, reducing the implementation to:
/* read all records from fp, return pointer to beginning of list */
TEAM *initialiseTeams (FILE *fp)
{
char line[BUFFER];
TEAM *head = NULL, /* using both head & tail pointer allows O(1) in-order */
*tail = NULL; /* insertions to the list. */
while (fgets (line, BUFFER, fp)) { /* read each line */
TEAM tmp = { .ID = 0 }; /* temporary struct */
/* parse values with sscanf() validating return */
if (sscanf (line, "%d; %127[^;]; %d; %d; %d; %d; %d/%d/%d %d:%d",
&tmp.ID, tmp.Team_name, &tmp.Status, &tmp.Points,
&tmp.Score, &tmp.Goals, &tmp.Date.tm_mday, &tmp.Date.tm_mon,
&tmp.Date.tm_year, &tmp.Date.tm_hour, &tmp.Date.tm_min) == 11) {
TEAM *node; /* node to allocate */
if (!(node = createNode (&tmp))) /* create node passing tmp struct */
break;
tmp.Date.tm_year -= 1900; /* subtract 1900 from year */
*node = tmp; /* assign tmp to allocated node */
if (!head) /* if head NULL, add 1st node */
head = tail = node;
else {
tail->next = node; /* add all subsequent nodes in-order */
tail = node;
}
}
}
return head; /* return pointer to beginning of list */
}
(note: you will need to adjust the field-width modifier in "%127[^;]"
to whatever you have for MAX_LEN - 1
)
Never hard-code filenames or use MagicNumbers in your code. You should not have to re-compile your program just to read from a different filename. Either provide the name of the filename as an argument to your program (that's what int argc, char **argv
are for in int main (int argc, char **argv)
or prompt the user to input the filename. Since you show MAX_LEN
and BUFFER
, those are obviously declared as constants elsewhere -- which is good.
Taking the filename to read from as the first argument to your program, or reading from stdin
by default if no argument is provided, your program to read you data file could be similar to:
int main (int argc, char **argv) {
TEAM *head = NULL; /* initialize pointers NULL */
/*
* keep interface separate from implementation.
* open file/validate and pass open FILE* to initialiseTeams()
* use filename provided as 1st argument (stdin by default)
*/
FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;
if (!fp) { /* validate file open for reading */
perror ("file open failed");
return 1;
}
head = initialiseTeams (fp); /* read all records in file into list */
if (fp != stdin) /* close file if not stdin */
fclose (fp);
prn_list (head); /* print list */
del_list (head); /* free all nodes */
}
Putting it altogether, and writing quick prn_list()
and del_list()
functions to output the list and then to delete all nodes in the list when you are done, you could do:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define MAX_LEN 128
#define BUFFER 1024
typedef struct team {
int ID;
char Team_name[MAX_LEN];
int Status;
int Points;
int Score;
int Goals;
struct tm Date;
struct team *next;
} TEAM;
TEAM *createNode (TEAM *t)
{
TEAM *node = malloc (sizeof *node); /* allocate */
if (!node) { /* validate */
perror ("malloc-node");
return NULL;
}
if (t) /* assign struct (if not NULL) */
*node = *t;
node->next = NULL;
return node;
}
/* read all records from fp, return pointer to beginning of list */
TEAM *initialiseTeams (FILE *fp)
{
char line[BUFFER];
TEAM *head = NULL, /* using both head & tail pointer allows O(1) in-order */
*tail = NULL; /* insertions to the list. */
while (fgets (line, BUFFER, fp)) { /* read each line */
TEAM tmp = { .ID = 0 }; /* temporary struct */
/* parse values with sscanf() validating return */
if (sscanf (line, "%d; %127[^;]; %d; %d; %d; %d; %d/%d/%d %d:%d",
&tmp.ID, tmp.Team_name, &tmp.Status, &tmp.Points,
&tmp.Score, &tmp.Goals, &tmp.Date.tm_mday, &tmp.Date.tm_mon,
&tmp.Date.tm_year, &tmp.Date.tm_hour, &tmp.Date.tm_min) == 11) {
TEAM *node; /* node to allocate */
if (!(node = createNode (&tmp))) /* create node passing tmp struct */
break;
tmp.Date.tm_year -= 1900; /* subtract 1900 from year */
*node = tmp; /* assign tmp to allocated node */
if (!head) /* if head NULL, add 1st node */
head = tail = node;
else {
tail->next = node; /* add all subsequent nodes in-order */
tail = node;
}
}
}
return head; /* return pointer to beginning of list */
}
/* simple print function */
void prn_list (TEAM *list)
{
if (!list) {
puts ("(list empty)");
return;
}
for (TEAM *iter = list; iter; iter = iter->next)
printf ("%4d %s\n"
" Status: %d\n"
" Points: %d\n"
" Score : %d\n"
" Goals : %d\n"
" Date : %d/%d/%d %d:%d\n\n",
iter->ID, iter->Team_name, iter->Status, iter->Points, iter->Score,
iter->Goals, iter->Date.tm_mday, iter->Date.tm_mon,
iter->Date.tm_year + 1900, iter->Date.tm_hour, iter->Date.tm_min);
}
/* function to delete all nodes in list */
void del_list (TEAM *list)
{
while (list) {
TEAM *victim = list;
list = list->next;
free (victim);
}
}
int main (int argc, char **argv) {
TEAM *head = NULL; /* initialize pointers NULL */
/*
* keep interface separate from implementation.
* open file/validate and pass open FILE* to initialiseTeams()
* use filename provided as 1st argument (stdin by default)
*/
FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;
if (!fp) { /* validate file open for reading */
perror ("file open failed");
return 1;
}
head = initialiseTeams (fp); /* read all records in file into list */
if (fp != stdin) /* close file if not stdin */
fclose (fp);
prn_list (head); /* print list */
del_list (head); /* free all nodes */
}
Testing the code with a generated data file that meets your format of:
ID;teamname;status;points;score;goals;day/month/year hour:minute
Example Input File
For example:
$ cat dat/soccerteams.txt
42;Team42;3;0;0;2;9/2/2020 1:5
97;Team97;6;42;2;2;18/3/2020 2:11
54;Team54;2;35;0;4;9/4/2020 3:17
49;Team49;10;0;2;4;18/5/2020 4:23
46;Team46;7;28;8;1;18/6/2020 5:29
98;Team98;7;0;4;3;27/1/2020 6:35
15;Team15;2;7;6;0;9/2/2020 7:41
8;Team8;8;7;4;3;27/3/2020 8:47
4;Team4;4;28;8;4;18/4/2020 9:53
51;Team51;12;14;6;1;9/5/2020 10:59
Example Use/Output
Calling the program as follows results in the output shown:
$ ./bin/ll_soccer dat/soccerteams.txt
42 Team42
Status: 3
Points: 0
Score : 0
Goals : 2
Date : 9/2/2020 1:5
97 Team97
Status: 6
Points: 42
Score : 2
Goals : 2
Date : 18/3/2020 2:11
54 Team54
Status: 2
Points: 35
Score : 0
Goals : 4
Date : 9/4/2020 3:17
49 Team49
Status: 10
Points: 0
Score : 2
Goals : 4
Date : 18/5/2020 4:23
46 Team46
Status: 7
Points: 28
Score : 8
Goals : 1
Date : 18/6/2020 5:29
98 Team98
Status: 7
Points: 0
Score : 4
Goals : 3
Date : 27/1/2020 6:35
15 Team15
Status: 2
Points: 7
Score : 6
Goals : 0
Date : 9/2/2020 7:41
8 Team8
Status: 8
Points: 7
Score : 4
Goals : 3
Date : 27/3/2020 8:47
4 Team4
Status: 4
Points: 28
Score : 8
Goals : 4
Date : 18/4/2020 9:53
51 Team51
Status: 12
Points: 14
Score : 6
Goals : 1
Date : 9/5/2020 10:59
When you separate your list operation from the rest of your program, it makes testing each individual component much easier and makes your list code reusable. If you put everything together in a few large functions -- then you will end up having to rewrite them for every list in the future. Keep functions small and easy to test.
Look things over and let me know if you have further questions.
fscanf
takes a FILE *
as its first argument, but you describe your code as passing token
which came from strtok somehow. You don't say what token
is, but there's no way to get a FILE *
from strtok, so it is probably not that.
Perhaps you mean to call sscanf
?
User contributions licensed under CC BY-SA 3.0