我正在尝试用 C 创建一个 tic tac toe 游戏,并且我有一个创建游戏板的函数:
void createBoard(char*** board, int size)
{
int i, j;
*(board) = malloc(size * sizeof( char* ));
for(i = 0; i < size; i++)
{
*(board)[i] = malloc(size * sizeof(char));
}
printf("Default board:\n");
for(i = 0; i < size; i++)
{
for(j = 0; j < size; j++)
{
*(board)[i][j] = '-';
printf("%c", *(board)[i][j]);
printf(" ");
if((j + 1) % size == 0)
printf("\n");
}
}
}
在主函数中,我向该函数传递一个字符**的地址:
int main()
{
int size;
char** board = NULL;
char userInp;
sizeOfBoard(&size);
createBoard(&board, size);
}
我使用 *(board) 访问指针的值(最初为 NULL),然后将 malloc 返回的地址分配给它,但我不知道为什么它不起作用。有人可以帮我解决这个问题吗?
最佳答案
不要那样做。相反,创建一个描述板和板状态的结构,并使用它。在该结构中,使用一维数组表示棋盘状态:
typedef struct {
int rows;
int cols;
char *cell;
} board;
#define BOARD_INIT { 0, 0, NULL }
#define CELL(b, r, c) ((b).cell[(c) + (r)*(b).cols])
要创建面板,您可以使用例如
int create_board(board *b, const int rows, const int cols)
{
const size_t cells = (size_t)rows * (size_t)cols;
const size_t bytes = sizeof (board) + cells * sizeof (b->cell[0]);
if (!b) {
/* No board specified. */
errno = EINVAL;
return -1;
}
b->rows = 0;
b->cols = 0;
b->cell = NULL;
if (rows < 1 || cols < 1) {
/* Invalid size. */
errno = EINVAL;
return -1;
}
if ((size_t)(cells / (size_t)rows) != (size_t)cols ||
(size_t)((bytes - sizeof (board)) / cells) != sizeof (b->cell[0])) {
/* Board is too large. */
errno = ENOMEM;
return -1;
}
b->cell = malloc(bytes);
if (!b->cell) {
/* Board was too large. */
errno = ENOMEM;
return -1;
}
b->rows = rows;
b->cols = cols;
return 0;
}
void free_board(board *b)
{
if (b) {
free(b->cell);
b->rows = 0;
b->cols = 0;
b->cell = NULL;
}
}
您可以使用CELL(board, row, column)
访问宏,或以下安全访问器函数:
char get_cell(board *b, const int r, const int c, const char outside)
{
if (!b || r < 0 || c < 0 || r >= b.rows || c >= b.cols)
return outside;
else
return b->cell[r*b->cols + c];
}
void set_cell(board *b, const int r, const int c, const char value)
{
if (b && r >= 0 && c >= 0 && r < b.rows && c < b.cols)
b->cell[r*b->cols + c] = value;
}
get_cell()
或 set_cell()
都不会尝试访问实际板之外的内容。 (get_cell()
的第四个参数 outside
定义了当您尝试访问无效板或板外时的结果值。)
说实话,我确实更喜欢使用 C99 灵活数组成员作为单元数组。在这种情况下,结构类似于
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <ctype.h>
#include <errno.h>
typedef struct {
int rows;
int cols;
unsigned char cell[];
} board;
board
类型有点类似于 FILE
流句柄:您永远不会静态声明它们(board b;
或 FILE f;
),但使用指向一的指针(board *b
,FILE *f
)。要创建新板,您可以使用例如
board *create_board(const int rows, const int cols)
{
board *b;
const size_t cells = (size_t)rows * (size_t)cols;
const size_t bytes = cells * sizeof (b->cell[0]);
const size_t total = bytes + sizeof (board);
/* Check for invalid size */
if (rows < 1 || cols < 1) {
errno = EINVAL;
return NULL;
}
/* Check for size overflow */
if ((size_t)(cells / (size_t)rows) != (size_t)cols ||
(size_t)(bytes / cells) != sizeof (b->cell[0]) ||
total <= bytes) {
errno = ENOMEM;
return NULL;
}
/* Allocate memory for the whole board */
b = malloc(total);
if (!b) {
/* Failed. Too large. */
errno = ENOMEM;
return NULL;
}
/* Optional: Clear the entire structure. */
memset(b, 0, total);
/* Initialize the board fields. */
b->rows = rows;
b->cols = cols;
return b;
}
void free_board(board *b)
{
if (b) {
b->rows = 0;
b->cols = 0;
free(b);
}
}
在 C99 中,我们可以将访问器函数声明为静态内联
,这意味着它们仅在同一翻译单元中可见(因此您可以在声明 的头文件中声明它们>board
结构),并且也是 C 编译器应该内联这些的提示。基本上,静态内联函数应该与预处理器宏一样快,而且还提供类型安全。
static inline int get_cell(board *b, const int row, const int col,
const int outside)
{
if (!b || row < 0 || col < 0 || row >= b->rows || col >= b->cols)
return outside;
else
return b->cell[row * b->cols + col];
}
static inline int set_cell(board *b, const int row, const int col,
const int value, const int outside)
{
if (b && row >= 0 && col >= 0 && row < b->rows && col < b->cols)
return b->cell[row * b->cols + col] = value;
else
return outside;
}
要打印板(使用 |
、+
和 -
作为单元格之间的线),您可以使用例如
static int board_char(board *b, const int row, const int col)
{
if (b && row >= 0 && col >= 0 &&
row < b->rows && col < b->cols) {
const int value = b->cell[row * b->cols + col];
if (isprint(value))
return value;
else
return ' ';
} else
return ' ';
}
void print_board(FILE *out, board *b)
{
if (out && b && b->rows > 0 && b->cols > 0) {
const int lastrow = b->rows - 1;
const int lastcol = b->cols - 1;
int r, c;
for (r = 0; r < lastrow; r++) {
for (c = 0; c < lastcol; c++) {
fputc(board_char(b, r, c), out);
fputc('|', out);
}
fputc(board_char(b, r, lastcol), out);
fputc('\n', out);
for (c = 0; c < lastcol; c++) {
fputc('-', out);
fputc('+', out);
}
fputc('-', out);
fputc('\n', out);
}
for (c = 0; c < lastcol; c++) {
fputc(board_char(b, lastrow, c), out);
fputc('|', out);
}
fputc(board_char(b, lastrow, lastcol), out);
fputc('\n', out);
}
}
其中board_char()
返回与引用的单元格相对应的描述性字符。上面的版本检查数组中的值是否是可打印字符,如果是,则返回它;否则,包括棋盘外的字符,它返回一个空格。您可以使用带有 case
语句的 switch (value) { ... }
来代替 if (isprint(value))
您为每个单元格使用的值。
(例如,如果零表示未使用/空闲,1表示第一个玩家(例如X
),2表示第二个玩家(例如O
),您可以使用3代表“被阻止”,标记一些单元格不可用,使游戏变得更有趣。)
您可以使用广泛的 Curses 库(例如 ncursesw)使您的游戏在终端中具有交互性,并使用漂亮的方框绘制字符来绘制游戏板。例如,
┌───┬───┬───┬───┬───┐
│ X │ │ │ │ │
├───┼───┼───┼───┼───┤
│ │ │ O │ │ │
├───┼───┼───┼───┼───┤
│ │ │ │ │ │
├───┼───┼───┼───┼───┤
│ │ │ │ │ │
├───┼───┼───┼───┼───┤
│ │ │ │ │ │
└───┴───┴───┴───┴───┘
您可以使用本地化和宽字符支持将上述类型的板打印到终端,而无需使用 Curses 库,但不幸的是在 Windows 中会出现问题。 (到目前为止,Microsoft 仍在致力于为终端/控制台应用程序提供适当的 Unicode 支持,而不需要使用 Microsoft 特定的 C 扩展。)
关于C - 使用 malloc 获取 char** 的地址,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51423531/