【问题标题】:using a strtok() token with fscanf() to do a date assignment使用带有 fscanf() 的 strtok() 标记进行日期分配
【发布时间】:2021-07-06 10:25:53
【问题描述】:

我有一个名为 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;

我正在创建一个团队的链接列表,团队信息是从一个文件中读取的,格式如下:

ID;teamname;status;points;score;goals;day/month/year hour:minute

我从文件中获取数据并完成所需的任务没有问题。但是我遇到了日期部分的问题。

我使用 strtok() 将一行拆分为标记,并尝试使用以下代码将标记的各个部分分配给它们各自的变量。

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);

但这给出了这个返回值 -1073741819 (0xC0000005) 据我所知,这被称为非法内存访问,我用 '&' 符号或 fscanf() 做的事情是错误的。

我的代码在没有 fscanf() 的情况下可以正常工作,那我做错了什么?

编辑:整个代码:

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();
}

【问题讨论】:

  • 我们无法调试不完整的代码片段。请提供complete minimal reproducible example
  • 编辑了问题
  • 现在在调试器中运行您的程序并找出它崩溃的确切代码行。当你得到它然后转储变量以查看是否有任何指针值无效。 How to debug small programs.
  • 您显示的代码没有您描述的代码行://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);。这既被注释掉了,也没有所需的&。请给出实际的非工作代码。目前尚不清楚您的真实代码是否缺少&
  • 感谢您的链接!我确实启用了所有警告说“|警告:从不兼容的指针类型[-Wincompatible-pointer-types]|传递'fscanf'的参数1”所以将 fscanf 更改为 sscanf 就可以了。

标签: c pointers scanf singly-linked-list


【解决方案1】:

创建列表过于复杂。这部分是由于试图将所有 I/O 和列表操作卸载到initialiseTeams()。通常,您希望将列表操作与程序界面(I/O、它如何与用户交互等)分开。这样,无论您需要从哪里添加节点(文件、用户输入、等等),您只需调用add()(或push())函数即可将新节点添加到您的列表中。

您在分离createnode() 函数方面做得很好。您可以通过将指向 TEAM 结构的指针作为参数传递来进一步利用它,该参数可用于在 createnode() 函数中初始化新节点。例如:

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;
}

只需要很少的额外努力。在验证要添加的数据之前,没有理由调用 createnode() 函数,因此传递 TEAM* 指针允许您处理函数中的所有创建和初始化。

您的initialiseTeams() 应该将打开的FILE* 流作为参数读取。如果文件无法在调用者中打开和验证,则无需调用 initialiseTeams() 函数。

您可以使用fgets() 进行阅读,但无需使用strtok() 标记该行。一个简单的精心制作的sscanf() 格式字符串就可以了。这大大简化了您的功能,将实现减少到:

/* 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 */
}

注意:您需要将"%127[^;]" 中的field-width 修饰符调整为MAX_LEN - 1 的任何值)

切勿在代码中硬编码文件名或使用 MagicNumbers。您不必为了读取不同的文件名而重新编译程序。将文件名的名称作为参数提供给您的程序(这就是int argc, char **argvint main (int argc, char **argv) 中的用途,或者提示用户输入文件名。由于您显示MAX_LENBUFFER,这些显然被声明为其他地方的常量——这很好。

将要读取的文件名作为程序的第一个参数,或者如果没有提供参数,则默认从stdin 读取,读取数据文件的程序可能类似于:

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 */
}

总而言之,编写快速的prn_list()del_list() 函数来输出列表,然后在完成后删除列表中的所有节点,您可以这样做:

#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 */
}

使用生成的符合您格式的数据文件测试代码:

ID;teamname;status;points;score;goals;day/month/year hour:minute

输入文件示例

例如:

$ 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

使用/输出示例

如下调用程序会产生如下输出:

$ ./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

当您将列表操作与程序的其余部分分开时,可以更轻松地测试每个单独的组件,并使您的列表代码可重用。如果您将所有内容放在几个大型函数中 - 那么您最终将不得不为将来的每个列表重写它们。保持函数小且易于测试。

查看一下,如果您还有其他问题,请告诉我。

【讨论】:

    【解决方案2】:

    fscanfFILE * 作为其第一个参数,但您将代码描述为传递了来自 strtok 的 token。你没有说token是什么,但是没有办法从strtok得到FILE *,所以可能不是那个。

    也许你的意思是打电话给sscanf

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-05-04
      • 1970-01-01
      • 1970-01-01
      • 2021-08-12
      • 2020-04-16
      • 2011-05-31
      相关资源
      最近更新 更多