首页 > 解决方案 > 文件读取后将值分配给自定义类型

问题描述

对不起,如果这个问题有点愚蠢,但我对 C 了解不多,所以我在 char 指针等方面苦苦挣扎。让我解释一下我有什么。

细胞.h

typedef struct{
    //Some things
    char value;
}Cell;

细胞.c

#include "cell.h"
void cell_init(Cell cell, char value){
    cell.value = value;
}

板子.h

typedef struct{
    //Some things
    Cell cells[5][5]
}Board;

板子.c

#include "board.h"
#include "cell.h"
void board_init(Board* board){
    FILE* file;
    char* filename = "file.txt";
    char newLine[255];
    file = fopen(filename, "r");

    for(int i=0; i<5; i++){
        char* line = fgets(newLine, sizeof(newLine),file);
        char* character = strtok(line, " ");
        int j = 0;
        while(character != NULL && j<5){
            cell_init(board->cells[i][j], *character); //This is surely wrong
            j++;
        }
    }
}

主程序

#include "board.h"
int main(void){
    Board* board;
    board_init(board)
}

文件.txt

5 7 2 5 2
2 1 5 6 1
2 4 7 4 3
2 3 5 6 5
2 3 5 3 5

基本上我在板内制作了一个单元格矩阵,读取一个文件并将文件中的数据放入每个单元格中(这就是我想要它做的),但我不断收到 segFault 错误(肯定是*character)。

显然我做的事情真的错了,我怎样才能把我从文件中得到的值放到我的单元格中?我真的迷失了指针如何分配值等。

如果需要更多信息,请询问。

编辑1:添加了新行

Edit2:修改代码以作为错误再现

编辑 3:哇,问题是当我应该通过板的实际方向时,我正在传递一个Board*to 。board_init&board

标签: c

解决方案


我想你已经发现事情一团糟了......由于你将所有内容在一个简单的例子中分离到单独的文件中而使混乱变得复杂(这很好,但你的注意力不断在文件之间转移,需要你保留东西如果所有内容都在同一个源文件中,这些文件将立即显现出来)

对于每个头文件,如果使用的文件不止一个,您需要包含header-guards以防止头被多次包含,例如cell.h

/* protect every header with header-guards */
#ifndef CELL_H
#define CELL_H  1

/* your header content goes here! */

#endif

CELL_H现在,仅当先前未定义时才包含标头。

让我们在这里看看您cell.h需要的内容。您必须包括所需的所有信息,cell.c并且必须包括可用于包含在标头中的文件的所有函数声明,例如:cell.h

/* protect every header with header-guards */
#ifndef CELL_H
#define CELL_H  1

typedef struct {
    //Some things
    char value;
} Cell;

/* declare function prototypes in header */
void cell_init (Cell *cell, char value);

#endif

注意:上面cell_init必须有一个指向的指针cell,例如Cell *cell,允许您使用新值更新该地址处的内存,而不是更新内存以获取if 作为类型而不是cell传递的本地副本)CellCell*

cell.c只需定义带有声明的函数cell.h并实现所需的任何其他逻辑,例如

#include "cell.h"

/* must take pointer to cell to update memory at that address in funciton */
void cell_init (Cell *cell, char value)
{
    cell->value = value;
}

board.h由于#includeboard.c. _ 例如,您需要stdio.hforfgets()string.hfor strtok()。您进一步不应在代码中使用幻数5。如果你需要一个常数,#define一个。您也未能包含本地标头cell.h,因此类型在和Cell中是未知的。board.hboard.c

要纠正board.h您可以执行的问题:

/* protect every header with header-guards */
#ifndef BOARD_H
#define BOARD_H  1

#include <stdio.h>  /* include system headers required by board.c */
#include <string.h>

#include "cell.h"   /* include local headers required by board.c */

#define BOARDSZ 5   /* if you need a constant, #define one (or more) */

typedef struct {
    //Some things
    Cell cells[BOARDSZ][BOARDSZ];   /* use constants, not magic-numbers */
} Board;

/* declare function prototypes in header */
int board_init (Board *board, FILE *file);

#endif

注意:如何在调用函数(main()此处)中打开文件并验证它是否已打开以供读取并作为参数传递给board_init。这是一个好习惯,如果您无法在调用者中打开文件,则无需打电话board_init

board.c您只调用strtok一次以获得第一个令牌。您必须调用strtok文件中的每一列(BOARDSZ其中)。注意:所有后续调用都strtok用作NULL第一个参数而不是原始指针。如上所述,您必须将地址传递cells[i][j]cell_init函数,因此您的调用将是:

            cell_init (&board->cells[i][j], *token);

(我使用token而不是lineasstrtok将一行拆分为标记)

您还必须验证您是否读取BOARDSZ了每BOARDSZ行的值和值的行。由于您接受输入,因此board_init您需要选择一个有意义的返回类型而不是void可以指示. 总而言之,你可以这样做:board_init

#include "cell.h"
#include "board.h"

int board_init (Board *board, FILE *file)
{
    char newline[255];  /* storage for line */
    int i = 0;          /* required after loop to validate no. of rows */

    for (; i < BOARDSZ; i++) {  /* loop for BOARDSZ lines */
        /* read line into newline, validate */
        if (fgets (newline, sizeof(newline),file) == NULL)
            return 0;

        char *token = strtok (newline, " \n");  /* get first token */
        int j = 0;      /* required after loop to validate columns */

        while (token != NULL && j < BOARDSZ){   /* while token loop BOARDSZ */
            /* initialize cell[i][j].value passing pointer to cells[i][j] */
            cell_init (&board->cells[i][j], *token);
            j++;                                /* increment columns */
            token = strtok (NULL, " \n");       /* get next token */
        }

        if (j < BOARDSZ)    /* validate BOARDSZ columns per-row */
            return 0;
    }
    if (i < BOARDSZ)        /* validate BOARDSZ rows */
        return 0;

    return 1;
}

最后board必须有 VALID 存储(用自动存储声明或用 分配malloc/calloc/realloc)。您的 current除了声明一个未初始化的指针Board *board之外什么都不做。其余的只是打开文件,验证它是否可以读取,然后调用传递board的地址(所以你传递一个指向 的指针)以及打开的文件流。然后,您在使用这些值之前验证是否成功填充了所有行和列。(这里它们只是再次输出)main.cboard_initboardboard_init

main.c可能是:

#include "board.h"

int main (int argc, char **argv) {

    Board board;    /* board must have valid memory */
    /* 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;
    }
    if (!board_init (&board, fp)) { /* validate board_init succeeds */
        fputs ("error: board_init failed.\n", stderr);
        return 1;
    }

    if (fp != stdin)   /* close file if not stdin */
        fclose (fp);

    for (int i = 0; i < BOARDSZ; i++) {         /* loop over rows */
        for (int j = 0; j < BOARDSZ; j++) {     /* loop over cols */
            if (j)                              /* if not 1st, add space */
                putchar (' ');
            putchar (board.cells[i][j].value);  /* output cells[i][j].value */
        }
        putchar ('\n');     /* tidy up with newline */
    }
}

编译时启用警告

始终在启用警告的情况下进行编译,并且在没有警告的情况下干净地编译之前不要接受代码。要启用警告,请添加到您的编译字符串(也可以考虑添加以警告阴影变量)。对于VS(在 Windows 上),使用. 阅读并理解每个警告——然后去修复它。他们将识别任何问题,以及它们发生的确切线路。通过聆听编译器告诉您的内容,您可以学到很多东西。-Wall -Wextra -pedanticgcc/clang-Wshadowcl.exe/W3

例如:

gcc -Wall -Wextra -pedantic -std=c11 -Ofast board.c cell.c -o bin/main main.c

示例使用/输出

stdin只需将要读取的文件名作为第一个参数传递给程序(如果没有给出参数,则默认读取),例如

$ ./bin/main file.txt
5 7 2 5 2
2 1 5 6 1
2 4 7 4 3
2 3 5 6 5
2 3 5 3 5

而已。您有点偏离并遗漏了代码中的一些关键元素——但是,您的调用cell_init基本上是正确的。

如果您还有其他问题,请仔细查看并告诉我。


推荐阅读