c语言修炼秘籍【第三章】数组

c语言修炼秘籍【第三章】数组

【心法】
【第零章】c语言概述
【第一章】分支与循环语句
【第二章】函数
【第三章】数组
【第四章】操作符
【第五章】指针
【第六章】结构体
【第七章】const与c语言中一些错误代码
【禁忌秘术】
【第一式】数据的存储
【第二式】指针
【第三式】字符函数和字符串函数
【第四式】自定义类型详解(结构体、枚举、联合)
【第五式】动态内存管理
【第六式】文件操作
【第七式】程序的编译



前言

本文将会介绍常用到的一维数组和二维数组是如何使用及它们在内存中是如何存储,说明何为数组越界,并讨论数组在作为函数参数时应该如何使用。在最后附带三子棋和扫雷的代码实现详解。


一、数组是什么?为什么要有数组?

数组是一组相同类型的元素的集合。

思考一个问题,此时需要从输入流中获得10个整型值,如何在标准输出流中全部输出,并计算它们之和的值?
输入示例:1 2 3 4 5 6 7 8 9 10
输出示例:
1 2 3 4 5 6 7 8 9 10
55
如果没有数组的话,你想要保存这十个数值,就需要创建10个整型变量,虽然比较复杂但好像并非难以接受;那如果,此时需要获取100个整型值呢?如果要创建100个变量的话,这是否有点…?
所以,为了要临时保存多个相同类型的变量的值时,数组就能发挥它的作用了。

二、一维数组

1.数组的创建

type_t array_name[const_n];

// type_t 是指数组的元素类型
// array_name 是数组名
// const_n 是一个常量表达式,表示数组能保存的最大元素个数,即数组的大小

// 一个大小为5的char类型数组,能保存5个char类型元素
char arr1[5];
// 一个大小为10的int类型数组,能保存10个int类型元素
int arr2[10];

// error 错误的数组创建方式
int count = 10;
int arr3[count]; // 数组的大小必须是常量,const修饰的变量也不行,因为它只是不能被修改,具有常量的特性,但本质上仍是变量

2.数组的初始化

数组的初始化是指在创建的同时给数组的内容一些合理的初始值

看代码:

int arr1[10] = { 1, 2, 3, 4 }; // 不完全初始化
int arr2[] = { 1, 2, 3, 4 };
int arr3[4] = { 1, 2, 3, 4 };

char str1[6] = "hello";
char str2[] = "hello";
char str3[] = { 'h', 'e', 'l', 'l', 'o' };
char str4[] = { 'h', 101, 'l', 'l', 'o' };
char str5[10] = "hello";
char str6[10] = { 'h', 'e', 'l', 'l', 'o' };

arr1中有10个元素,在初始化时,只给了4个元素,这种做法叫做不完全初始化,剩余的元素全部默认初始化为0;
arr2中没有指定数组的元素个数,此时初始化列表有几个元素,该数组就有多少个元素;
arr3中指定了数组中有4个元素,初始化列表中包含了4个元素,这被称为完全初始化;
其中arr2arr3等价;


str1完全初始化;
str2str1等价;
str3str4等价,其中str4中的第二个元素'e'用ASCII码值101表示;
str5用"hello"这个字符串进行不完全初始化;
str6{ 'h', 'e', 'l', 'l', 'o' }这五个字符进行不完全初始化;
str5str6内存中的值虽然相同,但初始化过程并不相同
str5是用 { 'h', 'e', 'l', 'l', 'o' , '\0'}这六个字符进行不完全初始化,对剩余的4个元素默认初始化为0;
str5是用{ 'h', 'e', 'l', 'l', 'o' }这五个字符进行不完全初始化,对剩余的5个元素默认初始化为0;


内存空间:
arr
在这里插入图片描述

注意

一定要能够区分下面这类代码,
arr1中有4个元素,‘a’, ‘b’, ‘c’, ‘\0’,数组大小为4
arr2中只有3个元素,‘a’, ‘b’, ‘c’,数组大小为3

char arr1[] = "abc";
char arr2[3] = { 'a', 'b', 'c' };

3.一维数组的使用

我们要如何使用数组中呢?需要用到下标引用操作符[]

看代码

#include <stdio.h>

int main()
{
	// 创建一个大小为10的int类型数组,不完全初始化,数组内容为全0
	int a[10] = { 0 };
	// 计算数组大小(求数组中元素个数)
	// 整个数组所占内存空间的大小 / 数组中一个元素所占内存空间的大小 == 数组中元素个数
	int sz = sizeof(a) / sizeof(int);

	// 对数组内容进行赋值,数组是使用下标来访问的,下标从0开始
	// i作下标
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		a[i] = i;
	}

	// 输出数组的内容
	for (i = 0; i < sz; i++)
	{
		printf("%d ", a[i]);
	}

	return 0;
}

总结

  1. 数组中的元素是用下标来访问的,下标从0开始;还可以使用指针访问,后面章节会介绍。
  2. 数组的大小可以通过计算得到。

4.一维数组在内存中的存储

看代码:

#include <stdio.h>

int main()
{
	int arr[10] = { 0 };
	int i = 0;
	int sz = sizeof(arr) / sizeof(arr[0]);

	for (i = 0; i < sz; i++)
	{
		printf("arr[%d]`s address == %p\n", i, &arr[i]);
	}

	return 0;
}

在这里插入图片描述
可以看出,数组中元素在内存空间中的地址是连续的,数组的内部的空间使用是从低位到高位。
在这里插入图片描述

三、二维数组

1.数组的创建

type_t array_name[const_x][const_y]
// type_t 数组元素类型
// array_name 数组名
// const_x,const_y 数组大小

// 可以将其看作int (arr1[2])[3],
// arr1[]是一个一维数组,数组中元素类型为int,大小为3
// arr1也是一个一维数组,数组的元素是一个数组,类型为int[3],大小为2
int arr1[2][3];
// 可以将其看作char (arr2[2])[3],
// arr2[]是一个一维数组,数组中元素类型为char,大小为4
// arr2也是一个一维数组,数组的元素是一个数组,类型为char[4],大小为2
char arr2[2][4];

在这里插入图片描述

2.数组的初始化

int arr1[][3] = { 1,2,3,4 }; // 二维数组如果有初始化,行数可以省略,但列不行
int arr2[][3] = { { 1, 2 }, { 3, 4 } };
int arr3[2][3] = { { 1, 2 }, { 3, 4 } };
// arr2 和 arr3 等价

在这里插入图片描述

通过观察arr1可知,为什么行可以省略,而列不行
因为列的大小确定了,一行能有几个元素就确定了,给出了初始化列表,就能有唯一的数组;
但如果只给行的大小,不知道列的大小,一行可以只放一个元素,也可以放10个元素,相同的元素总数,此时能够出现的行和列的组合有很多种;
所以列不能省略。

3.二维数组的使用

二维数组的使用也是通过下标访问

#include <stdio.h>

int main()
{
	// 不完全初始化
	int arr[3][5] = { 0 };
	int row = sizeof(arr) / sizeof(arr[0]);
	int col = sizeof(arr[0]) / sizeof(arr[0][0]);

	int i = 0;
	int j = 0;
	// 对数组内容进行赋值
	for (i = 0; i < row; i++)
	{
		for (j = 0; j < col; j++)
		{
			arr[i][j] = i + j;
		}
	}

	// 输出数组内容
	for (i = 0; i < row; i++)
	{
		for (j = 0; j < col; j++)
		{
			printf("%d ", arr[i][j]);
		}
		printf("\n");
	}

	return 0;
}

在这里插入图片描述

4.二维数组在内存中的存储

打印int arr[2][3]的地址

#include <stdio.h>

int main()
{
	int arr[2][3] = { 0 };

	int i = 0;
	int j = 0;
	for (i = 0; i < 2; i++)
	{
		for (j = 0; j < 3; j++)
		{
			printf("arr[%d][%d]`s address == %p\n", i, j, &arr[i][j]);
		}
	}

	return 0;
}

在这里插入图片描述

可以看到,arr中元素的地址是连续的,即二维数组的内存空间如下图所示:
在这里插入图片描述

四、数组越界

数组是有大小的,那么数组其实是有界限的,使用下标时可能会出现超限越界的情况。
例如,

  1. int arr[10],该数组的大小为10,下标从0开始,最大的下标为9,当使用arr[10]时就出现越界了,此时编译器并不会报错;
  2. 初始化时,使用的初始化列表中元素个数超过数组大小,也会越界,但这种情况编译器会报错;
  1. 程序执行越界
    在这里插入图片描述
  2. 初始化越界
    在这里插入图片描述

二维数组也存在越界问题

c语言并不提供,数组的越界检测,需要程序员自己做好越界的检查

五、数组作为函数参数

我们在写代码时,往往会遇到需要将数组作为函数参数的情况,比如,写一个函数实现对整型数组进行冒泡排序
在这里插入图片描述
上图展示了冒泡排序的一轮交换的过程,每一轮排序都会找到数组中的最大值将其放置到数组末尾,
有n个元素,最多就需要进行n - 1轮排序。

1.冒泡排序函数的错误设计

#include <stdio.h>

void bubble_sort(int arr[])
{
	int sz = sizeof(arr) / sizeof(arr[0]);
	int i = 0;
	int j = 0;
	for (i = 0; i < sz - 1; i++)
	{
		for (j = 0; j < sz - 1 - i; j++)
		{
			if (arr[j] > arr[j + 1])
			{
				int tmp = 0;
				tmp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = tmp;
			}
		}
	}
}

int main()
{
	int arr[10] = { 10, 9, 8, 7, 6, 5, 4, 3, 2, 1 };
	bubble_sort(arr);
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");

	return 0;
}

观察结果,看看是否成功将数组排序
在这里插入图片描述
并未排序
这到底是为什么呢?
调试发现,bubble_sort中的sz == 1
要搞清楚这个问题,我们必须先弄清传参时的数组名表示什么。

2.数组名是什么?

打印以下数值

int main()
{
	int arr1[10];
	printf("arr1 == %p\n", arr1);
	printf("&arr1[0] == %p\n", &arr1[0]);
	printf("arr1 + 1 == %p\n", arr1 + 1);
	printf("&arr1[0] + 1 == %p\n", &arr1[0] + 1);
	printf("sizeof arr1 == %d\n", sizeof(arr1));
	printf("sizeof &arr1[0] == %d\n", sizeof(&arr1[0]));

	return 0;
}

运行结果:
在这里插入图片描述

其中arr1&arr1[0]打印的内容相同,arr1 + 1&arr1[0] + 1打印的内容相同
由此可以看出,arr1&arr1[0]等价
即数组名所代表的含义是首元素地址


sizeof(arr1)打印的值为40 - - 10个整型数值所占内存空间的大小。
sizeof(&arr1[0])打印的值为4,这是因为&arr1是一个地址,32位平台中地址所占内存空间大小就是4字节。
既然arr1表示首元素地址,为什么sizeof(arr1) == 40呢?
这是因为数组名的使用有两个特例:

  1. sizeof()中的数组名表示整个数组 - - 求整个数组所占内存空间的大小
  2. &后的数组名,&arr1表示整个数组的地址。

用代码验证&<数组名>是表示整个数组

int main()
{
	int arr[10];

	printf("&arr[0] == %p\n", &arr[0]);
	printf("&arr == %p\n", &arr);
	printf("&arr[0] + 1 == %p\n", &arr[0] + 1);
	printf("&arr + 1 == %p\n", &arr + 1);

	return 0;
}

运行结果:
在这里插入图片描述
从结果中可以看出,虽然&arr[0]&arr打印内容相同,
&arr[0] + 1的值只比&arr[0]增大了4字节;
&arr + 1的值比&arr增大了40字节;

说明&arr中的arr此时表示整个数组

3.冒泡排序函数的正确设计

当数组传参时,并不属于上述两种特例,所以仅是将数通的首元素地址传给了函数。
所以即使函数参数部分写成数组的形式:int arr[],其本质上依然是一个指针int* arr
那么函数内部的sizeof(arr),仅是计算一个地址的内存空间大小 - - 4 / 8(32位 / 64位)

将代码修改如下

#include <stdio.h>

void bubble_sort(int arr[], int sz)
{
	int i = 0;
	int j = 0;
	for (i = 0; i < sz - 1; i++)
	{
		for (j = 0; j < sz - 1 - i; j++)
		{
			if (arr[j] > arr[j + 1])
			{
				int tmp = 0;
				tmp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = tmp;
			}
		}
	}
}

int main()
{
	int arr[10] = { 10, 9, 8, 7, 6, 5, 4, 3, 2, 1 };
	// sizeof(arr)中的arr表示整个数组
	int sz = sizeof(arr) / sizeof(arr[0]);
	bubble_sort(arr, sz);
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");

	return 0;
}

运行结果:
在这里插入图片描述
注意

当前代码虽然成功的完成了冒泡排序所需的功能,但是仍存在优化空间,举一个比较极端的例子
当要排序的数组如下时
在这里插入图片描述
虽然原代码最终也能输出正确的结果,但是很显然,原数组本来就是有序的,直接输出即可,但原代码还是会吭哧吭哧的反复执行很多次,在这个过程中我们能够发现,当一轮排序中没有交换任何两个元素,此时的数组元素就已经处于有序状态,排序可以直接结束。
所以我们可以在每轮交换中增加一个变量flag,用于记录当前是否进行了交换

优化代码如下:

void bubble_sort(int arr[], int sz)
{
	int i = 0;
	int j = 0;
	int flag = 0;
	for (i = 0; i < sz - 1; i++)
	{
		for (j = 0; j < sz - 1 - i; j++)
		{
			if (arr[j] > arr[j + 1])
			{
				int tmp = 0;
				tmp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = tmp;
				flag++;
			}
		}
		if (!flag)
		{
			break;
		}
		flag = 0;
	}
}

六、数组使用实例

这两个数组的使用实例,代码中作出了详细注释,就不再赘述。

1.三子棋 – 可拓展成n子棋

tictactoe.h

#pragma once

#include <stdio.h>
#include <time.h>
#include <stdlib.h>

// 棋盘大小为 ROW x COL
#define ROW 3
#define COL 3

// 游戏状态
// 游戏过程中总共有4种状态
// 1. 玩家获胜
// 2.电脑获胜
// 3.平局
// 4.继续对局
typedef enum
{
	PlayerWin, // 玩家获胜
	ComputerWin, // 电脑获胜
	Equality, // 平局
	Continue // 继续对局
} Status;

// 函数声明
// 棋盘初始化
void initBoard(char [ROW][COL]);

// 三子棋盘游戏
void game(char [ROW][COL]);

// 玩家下棋
void Player(char [ROW][COL]);

// 判断落子位置是否合法:1 - 合法,0 - 非法
int islegal(char [ROW][COL], int, int);

// 打印棋盘
void printBoard(char[ROW][COL]);

//电脑下棋
void Computer(char[ROW][COL]);

// 判断胜负
Status isWin(char [ROW][COL]);

tictactoe.c

#define _CRT_SECURE_NO_WARNINGS

#include "tictactoe.h"

// 初始化棋盘 - 初始化为空格' '
void initBoard(char board[ROW][COL])
{
	int i = 0;
	int j = 0;
	for (i = 0; i < ROW; i++)
	{
		for (j = 0; j < COL; j++)
		{
			board[i][j] = ' ';
		}
	}
}

// 静态函数,只能在当前文件中使用
// 判断谁先手
// 1 - 玩家先,0 - 电脑先
static int whoisFirst()
{
	int input = 1;
	while (1)
	{
		printf("请选择谁先手(0 - 电脑, 1 - 你):>");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			return 1;
		case 0:
			return 0;
		default:
			printf("选项不存在\n");
			break;
		}
	}
}

// 用于判断平局的全局变量
static int count;

// 游戏函数
void game(char board[ROW][COL])
{
	// 初始化棋盘
	initBoard(board);
	// 棋盘中总共有ROW * COL个位置可以落子
	count = ROW * COL;
	// 选择谁先手
	int ret = whoisFirst();
	// 保存对局当前的状态
	Status status = Continue;

	// 双方轮流下棋
	if (ret)
	{
		// 打印棋盘
		printBoard(board);
		while (1)
		{
			// 玩家下棋
			Player(board);
			// 判断胜负
			status = isWin(board);
			// 只有Continue才会继续下棋
			if (status != Continue)
			{
				break;
			}
			// 电脑下棋
			Computer(board);
			// 判断胜负
			status = isWin(board);
			// 只有Continue才会继续下棋
			if (status != Continue)
			{
				break;
			}
		}
	}
	else
	{
		while (1)
		{
			// 电脑下棋
			Computer(board);
			// 判断胜负
			status = isWin(board);
			// 只有Continue才会继续下棋
			if (status != Continue)
			{
				break;
			}
			// 玩家下棋
			Player(board);
			// 判断胜负
			status = isWin(board);
			// 只有Continue才会继续下棋
			if (status != Continue)
			{
				break;
			}
		}
	}
	if (status == PlayerWin)
	{
		printf("玩家赢了\n");
	}
	else if (status == ComputerWin)
	{
		printf("电脑赢了\n");
	}
	else
	{
		printf("平局\n");
	}
	printf("终盘为:\n");
	printBoard(board);
}

void Player(char board[ROW][COL])
{
	int x = 0;
	int y = 0;
	printf("你的回合\n");
	// 玩家下棋
	while (1)
	{
		printf("输入落子坐标(输入示例:(1, 2) -- > 1 2):>", ROW, COL);
		scanf("%d %d", &x, &y);
		// 判断落子位置是否合法
		if (islegal(board, x, y))
		{
			board[x - 1][y - 1] = 'P';
			count--;
			// 打印棋盘,便于玩家选择落子点
			printBoard(board);
			break;
		}
		else
		{
			printf("非法位置\n");
		}
	}
}

int islegal(char board[ROW][COL], int row, int col)
{
	// 落子位置位于棋盘内
	if (row >= 1 && row <= ROW && col >= 1 && col <= COL)
	{
		if (board[row - 1][col - 1] == ' ')
		{
			return 1;
		}
		return 0;
	}
	// 落子位置位于棋盘外
	else
	{
		return 0;
	}
}

void printBoard(char board[ROW][COL])
{
	int i = 0;
	int j = 0;
	// 打印列号 - 0 1 2 ..... COL
	for (i = 0; i <= COL; i++)
	{
		if (i == 0)
		{
			printf("  ", i);
		}
		else
		{
			printf("%d ", i);
		}
	}
	printf("\n");

	// 打印一行 - <行号> *|*|.....|COL
	for (i = 0; i < ROW; i++)
	{
		printf("%d ", i + 1); // 打印行号
		// 打印一行棋盘
		for (j = 0; j < COL; j++)
		{
			printf("%c", board[i][j]);
			if (j != COL - 1)
			{
				printf("|");
			}
		}
		printf("\n");
		// 打印行间的分隔线
		if (i != ROW - 1)
		{
			printf("  "); // 空出行号位置
			for (j = 0; j < COL; j++)
			{
				printf("- ");
			}
			printf("\n");
		}
	}
}

// 以生成的随机数作为电脑的落子点
void Computer(char board[ROW][COL])
{
	int x = 0;
	int y = 0;
	printf("电脑的回合\n");
	while (1)
	{
		x = (rand() % ROW) + 1;
		y = (rand() % COL) + 1;
		if (islegal(board, x, y))
		{
			board[x - 1][y - 1] = 'C';
			count--;
			printBoard(board);
			break;
		}
	}
}

Status isWin(char board[ROW][COL])
{
	int i = 0;
	int j = 0;
	// 判断是否平局 -- 棋盘下满
	if (!count)
	{
		return Equality;
	}

	// 判断ROW行
	for (i = 0; i < ROW; i++)
	{
		for (j = 0; j < COL - 1; j++)
		{
			if (board[i][j] != board[i][j + 1])
			{
				break;
			}
		}
		if (j == COL - 1)
		{
			if (board[i][j] == 'P')
			{
				return PlayerWin;
			}
			else if (board[i][j] == 'C')
			{
				return ComputerWin;
			}
		}
	}
	
	// 判断COL列
	for (i = 0; i < COL; i++)
	{
		for (j = 0; j < ROW - 1; j++)
		{
			if (board[j][i] != board[j + 1][i])
			{
				break;
			}
		}
		if (j == ROW - 1)
		{
			if (board[j][i] == 'P')
			{
				return PlayerWin;
			}
			else if (board[j][i] == 'C')
			{
				return ComputerWin;
			}
		}
	}
	
	// 主对角线
	for (i = 0, j = 0; i < ROW - 1 && j < COL - 1; i++, j++)
	{
		if (board[i][j] != board[i + 1][j + 1])
		{
			break;
		}
	}
	if (j == ROW - 1)
	{
		if (board[i][j] == 'P')
		{
			return PlayerWin;
		}
		else if (board[j][i] == 'C')
		{
			return ComputerWin;
		}
	}
	
	// 副对角线
	for (i = ROW - 1, j = 0; i >= 0 && j < COL - 1; i--, j++)
	{
		if (board[i][j] != board[i - 1][j + 1])
		{
			break;
		}
	}
	if (j == ROW - 1)
	{
		if (board[i][j] == 'P')
		{
			return PlayerWin;
		}
		else if (board[j][i] == 'C')
		{
			return ComputerWin;
		}
	}
	
	return Continue;
}

test.c

#include "tictactoe.h"

void menu()
{
	printf("********************\n");
	printf("*****  1.Play  *****\n");
	printf("*****  0.Exit  *****\n");
	printf("********************\n");
}

int main()
{
	int input = 0;
	printf("********************\n");
	printf("*****  三子棋  *****\n");

	// 创建棋盘
	char board[ROW][COL];
	// 设置随机数
	srand((unsigned int)time(NULL));

	do
	{
		menu();
		printf("请选择:>");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			game(board);
			break;
		case 0:
			printf("退出游戏\n");
			break;
		default:
			printf("选项错误\n");
			break;
		}
	}while(input);

	return 0;
}

2.扫雷 - - 实现了雷区发散查找

假设布雷区域的大小为9x9,内部黑框部分,在它的外围,增加了一圈格子用以辅助,
使得计算布雷区域的任意一个格子周围的布雷数量的逻辑相同
在这里插入图片描述

mine_sweeping.h

#pragma once

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

// 布雷区域大小
#define MAX_ROW 9
#define MAX_COL 9

// 保存数据空间大小
// 排雷时,需要判断一个格子周围的8个格子中有几个地雷
// 为了保证整个布雷区域的判断逻辑相同,在该区域外增加了一圈辅助格子
#define MAX_ROWS MAX_ROW + 2
#define MAX_COLS MAX_COL + 2

// 地雷数量
#define MAX_MINE 10

typedef enum
{
	Unknown, // 未知
	Sweep, // 已排雷
	Mark // 标记为有雷
} Status;

typedef struct
{
	char mine; // 'm' - 有地雷,' ' - 无地雷
	Status status;
	int numofMine;
} Cell;

// 布置雷区
void initBoard(Cell [MAX_ROWS][MAX_COLS]);

// 打印雷区 -- 测试用函数
void displayMine(Cell [MAX_ROWS][MAX_COLS]);

// 打印玩家看到的雷区
void displayBoard(Cell [MAX_ROWS][MAX_COLS]);

// 排雷
int sweep(Cell [MAX_ROWS][MAX_COLS]);

// 判断玩家排雷位置是否合法,1 - 合法,0 - 非法
int islegal(Cell [MAX_ROWS][MAX_COLS], int, int);

// 计算周围的地雷数量
void findMine(Cell [MAX_ROWS][MAX_COLS], int, int);

// 放置标记
void putMark(Cell[MAX_ROWS][MAX_COLS], int, int);

// 玩家选择操作 - 排雷,放置标记
int operation();

// 游戏逻辑
void game();

mine_sweeping.c

#define _CRT_SECURE_NO_WARNINGS

#include "mine_sweeping.h"

// 随机布雷
void initBoard(Cell board[MAX_ROWS][MAX_COLS])
{
	int i = 0;
	int j = 0;
	int x = 0;
	int y = 0;

	// 数组元素初始化
	for (i = 0; i < MAX_ROWS; i++)
	{
		for (j = 0; j < MAX_COLS; j++)
		{
			// 雷区重置 - 没有雷
			board[i][j].mine = ' ';
			board[i][j].status = Unknown;
			board[i][j].numofMine = 0;
		}
	}

	i = MAX_MINE;
	// 布雷,每布一颗,i--,i == 0时,循环结束 
	while (i)
	{
		// 雷区的下标范围是 (1, 1) 到 (MAX_ROW, MAX_COL)
		x = (rand() % MAX_ROW) + 1;
		y = (rand() % MAX_COL) + 1;
		// 该位置没有雷
		if (board[x][y].mine != 'm')
		{
			board[x][y].mine = 'm';
			i--;
		}
	}
}

void displayMine(Cell board[MAX_ROWS][MAX_COLS])
{
	int i = 0;
	int j = 0;

	printf("  ");
	// 打印列号
	for (i = 1; i <= MAX_COL; i++)
	{
		printf("%d ", i);
	}
	printf("\n");

	// 打印雷区
	for (i = 1; i <= MAX_ROW; i++)
	{
		// 行号
		printf("%d ", i);
		// 打印一行
		for (j = 1; j <= MAX_COL; j++)
		{
			printf("%c ", board[i][j].mine);
		}
		printf("\n");
	}
}

void displayBoard(Cell board[MAX_ROWS][MAX_COLS])
{
	int i = 0;
	int j = 0;

	printf("  ");
	// 打印列号
	for (i = 1; i <= MAX_COL; i++)
	{
		printf("%d ", i);
	}
	printf("\n");

	// 打印雷区
	for (i = 1; i <= MAX_ROW; i++)
	{
		// 行号
		printf("%d ", i);
		// 打印一行
		for (j = 1; j <= MAX_COL; j++)
		{
			if (board[i][j].status == Unknown)
			{
				printf("%c ", '*'); 
			}
			else if (board[i][j].status == Mark)
			{
				printf("%c ", '#'); // 标记为有雷
			}
			else
			{
				printf("%d ", board[i][j].numofMine);
			}
		}
		printf("\n");
	}
}

// 返回1 - 排除该位置,返回0 - 踩到地雷,你被炸死了,游戏结束
int sweep(Cell board[MAX_ROWS][MAX_COLS])
{
	int x = 0;
	int y = 0;
	while (1)
	{
		printf("请输入排雷位置 (1, 2) -> 1 2:>");
		scanf("%d %d", &x, &y);
		// 当前位置合法 -- (x, y)指向的格子的status为Unknown
		if (islegal(board, x, y))
		{
			// 未踩到地雷
			if (board[x][y].mine != 'm')
			{
				findMine(board, x, y); // 寻找(x, y)附近的地雷数量
				return 1;
			}
			else
				return 0;
		}
	}
}

int islegal(Cell board[MAX_ROWS][MAX_COLS], int row, int col)
{
	if (row >= 1 && row <= MAX_ROW && col >= 1 && col <= MAX_COL)
	{
		if (board[row][col].status == Unknown)
		{
			return 1;
		}
		return 0;
	}
	return 0;
}

// 保存雷区中目前需要排雷的格子数
static int num;

// 如果(row, col)周围没有地雷,会向周围发散寻找,
void findMine(Cell board[MAX_ROWS][MAX_COLS], int row, int col)
{
	int count = 0;
	int i = 0;
	int j = 0;
	// 将当前格子的状态转为已排雷
	board[row][col].status = Sweep;
	num--;
	
	// 遍历周围
	// 上、中、下三行
	for (i = -1; i <= 1; i++)
	{
		// 前、中、后三列
		for (j = -1; j <= 1; j++)
		{
			if (board[row + i][col + j].mine == 'm')
			{
				count++;
			}
		}
	}
	// 当前位置没有雷,向周围发散寻找
	if (count == 0)
	{
		for (i = -1; i <= 1; i++)
		{
			for (j = -1; j <= 1; j++)
			{
				// 格子的状态为Sweep就不用计算了,边界的辅助区域也不用计算
				if (board[row + i][col + j].status != Sweep 
						&& row + i >= 1
						&& row + i <= MAX_ROW
						&& col + j >= 1
						&& col + j <= MAX_COL)
				{
					findMine(board, row + i, col + j);
				}
			}
		}
	}
	board[row][col].numofMine = count;
}

// 可以选择已排雷区域,相当于什么也没干
void putMark(Cell board[MAX_ROWS][MAX_COLS])
{
	int x = 0;
	int y = 0;

	printf("请输入标记位置 (1, 2) -> 1 2:>");
	scanf("%d %d", &x, &y);
	// 当前位置合法 -- (x, y)指向的格子的status为Unknown
	if (islegal(board, x, y))
	{
		if (board[x][y].status != Sweep)
		{
			board[x][y].status = Mark;
		}
	}
}

int operation()
{
	int input = 0;
	printf("1.排雷\n");
	printf("2.放标记\n");
	printf("选择操作:>");
	scanf("%d", &input);
	switch (input)
	{
	case 1:
		return 1;
	case 2:
		return 0;
	default:
		printf("选项错误\n");
		break;
	}
}

void game()
{
	// 用一个二维数组保存地雷信息
	// 每个元素有几个属性
	// 1.是否有地雷
	// 2.当前的状态
	//   a.未知
	//	 b.已排雷
	//	 c.标记为有雷
	// 3.周围地雷的数量
	Cell board[MAX_ROWS][MAX_COLS];
	// 初始化 -- 往雷区中埋入MAX_MINE颗地雷
	initBoard(board);
	num = MAX_ROW * MAX_COL - MAX_MINE;
	// 打印雷区 -- 测试用函数
	//displayMine(board);
	// 打印排雷用雷阵
	displayBoard(board);

	// 游戏持续进行,直到待排雷区域数 == 0
	while (num)
	{
		if (operation())
		{
			// 排雷
			int ret = sweep(board);
			if (ret == 0)
			{
				printf("踩到地雷,你被炸死了,游戏结束\n");
				break;
			}
		}
		else
		{
			putMark(board);
		}
		// 打印雷区当前状态
		displayBoard(board);
	}
	if (num)
	{
		printf("游戏失败\n");
	}
	else
	{
		printf("游戏胜利\n");
	}
	printf("雷区情况:\n");
	displayBoard(board);
	printf("地雷分布:\n");
	displayMine(board);
}

test.c

#include "mine_sweeping.h"

void menu()
{
	printf("********************\n");
	printf("*****  1.Play  *****\n");
	printf("*****  0.Exit  *****\n");
	printf("********************\n");
}

int main()
{
	int input = 0;
	srand((unsigned int)time(NULL));

	do
	{
		printf("********************\n");
		printf("*****   扫雷   *****\n");
		menu();
		printf("请选择:>");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			//printf("mine_sweeping\n");
			game();
			break;
		case 0:
			printf("退出游戏\n");
			break;
		default:
			printf("没有这个选项\n");
			break;
		}
	} while(input);

	return 0;
}

雷区发散查找,函数实现如下

// 如果(row, col)周围没有地雷,会向周围发散寻找,
void findMine(Cell board[MAX_ROWS][MAX_COLS], int row, int col)
{
	int count = 0;
	int i = 0;
	int j = 0;
	// 将当前格子的状态转为已排雷
	board[row][col].status = Sweep;
	num--;
	
	// 遍历周围
	// 上、中、下三行
	for (i = -1; i <= 1; i++)
	{
		// 前、中、后三列
		for (j = -1; j <= 1; j++)
		{
			if (board[row + i][col + j].mine == 'm')
			{
				count++;
			}
		}
	}
	// 当前位置没有雷,向周围发散寻找
	if (count == 0)
	{
		for (i = -1; i <= 1; i++)
		{
			for (j = -1; j <= 1; j++)
			{
				// 格子的状态为Sweep就不用计算了,边界的辅助区域也不用计算
				if (board[row + i][col + j].status != Sweep 
						&& row + i >= 1
						&& row + i <= MAX_ROW
						&& col + j >= 1
						&& col + j <= MAX_COL)
				{
					findMine(board, row + i, col + j);
				}
			}
		}
	}
	board[row][col].numofMine = count;
}

在这里插入图片描述


总结

本文对一维和二维数组的初始化、创建、使用,作出的简要的介绍,并通过冒泡排序算法的实现,详细说明了当数组作为函数参数时的性质 - - 传递的是首元素地址。在最后,给出了两个数组的使用实例,附带了完整的实现代码,并配有详细注释。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值