文章目录

  • 数组
    • 一:一维数组的创建和初始化
      • 1.数组的创建
      • 2.数组的初始化
      • 3.一维数组的使用
      • 4.一维数组在内存中的存储
    • 二:二维数组的创建和初始化
      • 1.数组的创建
      • 2.数组的初始化
      • 3.二维数组的使用
      • 4.二维数组在内存中的存储
    • 三:数组越界
    • 四:数组作为函数参数
      • 1.数组名是什么?
      • 2.冒泡排序函数的正确设计
    • 五:数组实例
      • 1.三子棋
        • (1):文件的编排
        • (2):实现游戏的进入与退出
          • 分析逻辑:
          • 代码:
        • (3):对棋盘的设计与打印
          • 分析逻辑:
          • 代码:
        • (4):玩家与电脑开始玩游戏
          • 分析逻辑:
          • 代码:
        • (5):胜负的判定
          • 分析逻辑:
          • (最终) 代码:
      • 2.扫雷游戏
        • (1):文件的编排
        • (2):实现游戏的进入与退出
          • 分析逻辑:
        • (3):对棋盘的设计与打印
          • 分析逻辑:
        • (4):设置地雷
          • 分析逻辑
        • (5):判断胜利
          • 分析逻辑
        • 说明:
        • 代码:

数组

一:一维数组的创建和初始化

1.数组的创建

定义:数组是一组相同类型元素的集合。
创建方式:

type_t arr_name [const_n];//type_t 是指数组的元素类型//const_n 是一个常量表达式,用来指定数组的大小

实例:

int arr1[10];//int表示数组内的元素为整形char arr2[20];//arr2表示数组名float arr3[2+3];//在C89标准下,[]内为常量或常量表达式,2+3属于常量表达式double arr4[5] = 10;//浮点数在内存中无法精确保存int arr5[num];//此时[]内为变量,在C99标准中可以支持这种写法,创建的数组叫做变长数组

注意:变长数组支持变长数组的大小用变量来指定,变长数组不是数组的长度可以变化,而是数组的大小可以用变量来指定。

2.数组的初始化

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

数组初始化又分为完全初始化和不完全初始化

完全初始化:表示对数组的每一个元素进行赋值。不完全初始化:表示对数组的部分元素进行赋值,没有被赋值的元素设为0。
int arr1[10] = { 1,2,3 };//将前三个元素初始化为1,2,3,后面的元素为0,这种初始化叫做不完全初始化int arr2[] = { 1,2,3,4 };//没有给定元素个数时,内存会默认分配足够放置元素的空间,此时元素个数为4int arr3[5] = { 12345 };//每个元素都被赋值,完全初始化char arr4[3] = { 'a',98, 'c' };//98代表ASCII码值对应的字符bchar arr5[] = { 'a','b','c' };//这里只储存了a、b、c三个字符,共三个元素char arr6[] = "abc";//字符串有一个结束标志\0也要储存到数组中,共4个元素//注意:arr6中如果叫你算元素多少种(数组的大小)?sizeof=4//如果是算字符串长度strlen那就=3(\0的长度为0)char arr7[5] = {'a','b','c'};//这里储存了a,b,c,0,0五个元素char arr7[5] = "abc";//这里储存了a,b,c,'\0'(ASCII码值还是0),0//arr7的两者性质不同,前者是内存默认分配abc三个元素,后者是内存默认分配a,b,c,'\0'四个元素

3.一维数组的使用

对于数组的使用我们之前介绍了一个操作符: [] ,下标引用操作符。它其实就数组访问的操作符。

#include int main(){ int arr[10] = {0};//数组的不完全初始化 //计算数组的元素个数 int sz = sizeof(arr)/sizeof(arr[0]);//sz为元素个数 //对数组内容赋值,数组是使用下标来访问的,下标从0开始。所以: int i = 0;//做下标 for(i=0; i<sz; i++)//这里写sz表明循环sz次,并且保证数组不会越界 { arr[i] = i;//把数组的下标赋给对应元素 }//输出数组的内容 for(i=0; i<10; ++i) { printf("%d ", arr[i]);//打印 } return 0; }//输出:0 1 2 3 4 5 6 7 8 9 

总结:

  1. 数组是使用下标来访问的,下标是从0开始。
  2. 数组的大小可以通过计算得到
int arr[10];int sz = sizeof(arr)/sizeof(arr[0]);

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

接下来我们来探讨数组在内存中的存储

#include 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] = %p\n", i, &arr[i]);//这个i不能少,是打印arr[%d]里面的}return 0;}


由图我们可以看出
随着数组下标的增长,由于每个元素都为int类型,所以下标每增加1,数组元素的地址会加4,也就是说,元素的地址,也在有规律的递增。
由此可以得出结论:数组在内存中是连续存放的。元素下标越大它的地址也越高。

二、二维数组

二:二维数组的创建和初始化

1.数组的创建

//数组创建,其中前面的[]表示行,后面的[]表示列int arr[3][4];char arr[3][5];

2.数组的初始化

//数组初始化int arr1[3][4] = {1,2,3,4,5};//元素表示:// 1 2 3 4 // 5 0 0 0// 0 0 0 0// 0 0 0 0int arr2[3][4] = {{1,2},{3,4}};//元素表示:// 1 2 0 0// 3 4 0 0// 0 0 0 0// 0 0 0 0int arr3[][4] = {{2,3},{4,5}};//二维数组如果有初始化,行可以省略,列不能省略//元素表示:// 2 3 0 0// 4 5 0 0

提示:

  • 在二维数组中,如果只有一个{},那么数组会先按行来赋值,一直到赋满行为止,如果有多个括号,不同情况不同分析,来看看下面的这个例子。
  • arr2是个三行四列的数组,这个时候我们来看括号内的元素,它是一个嵌套括号,所以我们应该先排第一个内括号的元素,也就是{1,2},第一行排完1 2后发现还少了两个元素,由于第一个内括号只有两个元素,所以也是不完全初始化,从而我们需要在第一行的剩下两个元素中补0,所以第一行是1 2 0 0,以两个括号之间的逗号为分界点 我们开始排第二个内括号的元素{4,5},与上面一样,同理可得第二行的元素是4 5 0 0。
  • 在arr3中行没有初始化,那我们就看列数以及{}内有没有嵌套{}(也就是{ { } } ),如果有嵌套括号,比如arr3中的{{2,3},{4,5}},先将第一个内括号的{2,3}排在第一行,由于是四列元素,第一行只不完全初始化两个数,所以剩下的第一行剩下的两个数都为0。同理第二行排{4,5},剩下两个数都为0,由于行数没有初始化,内存会根据你内部的情况分配相应的行数。所以最终arr3就是一个两行四列的二维数组。

3.二维数组的使用

二维数组的使用也是通过下标的方式

#include int main(){int arr[3][4] = { 0 };int i = 0;for (i = 0; i < 3; i++){int j = 0;for (j = 0; j < 4; j++){arr[i][j] = i * 4 + j;}}for (i = 0; i < 3; i++){int j = 0;for (j = 0; j < 4; j++){printf("%d ", arr[i][j]);}}return 0;}

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

像一维数组一样,这里我们尝试打印二维数组的每个元素

#include int main(){int arr[3][4];int i = 0;for (i = 0; i < 3; i++){int j = 0;for (j = 0; j < 4; j++){printf("&arr[%d][%d] = %p\n", i, j, &arr[i][j]);}}return 0;}

这个代码可以打印数组的每一个元素的地址,输出结果为:

通过结果我们可以分析到:

  • 与一维数组的规律一样,其实二维数组在内存中也是连续存储的。(可以看下图1)
  • 上图中程序先排列再排行。或者可以这么理解——如果第一行我们想象成一维数组的话,它的数组名就是(arr[0]), 然后arr[0][j]中的j就是它的下标(看下图2)

三:数组越界

数组的下标是有范围限制的。

数组的下标规定是从0开始的,如果数组有n个元素,最后一个元素的下标就是n-1。 [0,n-1]
所以数组的下标如果小于0,或者大于n-1,就是数组越界访问了,超出了数组合法空间的访问。

C语言本身不会做数组下标的越界检查,所以当我们越界访问时编译器也不一定报错,但是编译器不报错,并不意味着程序就是正确的,所以程序员写代码时,最好自己做越界的检查。

#include int main(){int arr[10] = {1,2,3,4,5,6,7,8,9,10};int i = 0;for(i = 0; i <= 10; i++){printf("%d\n", arr[i]);//当i等于10的时候越界访问了}return 0; }//输出:1 2 3 4 5 6 7 8 9 10 -858993460(最后的数字是个随机值)

四:数组作为函数参数

1.数组名是什么?

一般情况下
除了下面两个特殊情况外,所有的数组名只表示数组首元素的地址。

特殊情况下
(1)sizeof(数组名)——这里计算的是整个数组的大小

(2)&数组名——这里拿出的也是整个数组的地址

来看看下面的代码

#includeint main(){int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };printf("%p\n", arr);//数组首元素地址printf("%p\n", &arr[0]);//也是数组首元素地址printf("%d\n", *arr);//数组首元素地址对应的元素printf("%p\n", &arr);//数组的地址,虽然这个地址也是数组首元素的地址但还是有所区别printf("%p\n", &arr+1);//取出整个数组的地址,指针指向一个数组大小后的元素printf("%d",arr[i])<==>printf("%d",*(p+i));//arr[i]==*(arr+i)//p+i就是下标为i元素的地址,通过对p+i的解引用,找到下标为i的元素。return 0;}

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

冒泡排序的思想:两两相邻的元素进行比较
分析:假设我们要排10个元素 9 8 7 6 5 4 3 2 1 0将其排成顺序
那么我们根据冒泡排序思想:9和8先进行比较,发现9更大,则两者换序,然后9和7比较…以此类推我们可以把9放到最后一位去。这是一趟冒泡排序。(N个元素,最坏的情况有N-1趟冒泡排序)…以此类推从而将这10个元素排成顺序

则我们来看下面这个代码

//冒泡排序#include void bubble_sort(int arr[],int sz){int i = 0;for (i = 0; i < sz - 1; i++)//每进入一次,就可以排一个元素//i表示一个数字冒泡排序的趟数//在排完n-1个元素后,最后一个元素实际上也排好了,//所以进入n-1时即可{int j = 0;//for循环的判断条件就是一趟里面数字j里面交换了多少次,//然后再减去趟数即可for (j = 0; j < sz - 1 - i; j++)//每一次把较大的数字往后放,//因为后面的数字已经排好了,//所以只需要比较前面乱序的部分就够了//-i的原因是因为随着每次交换,//还没被交换的数字越来越少,//趟数在逐渐减少,所以我们得减去第i趟{if (arr[j] > arr[j + 1])//前大于后则前后换位{int tmp = arr[j];arr[j] = arr[j + 1];arr[j + 1] = tmp;}}}}int main(){int arr[] = { 2,1,4,5,9,8,6,3,7,0 };int sz = sizeof(arr) / sizeof(arr[0]);//数组传参的时候,传递的就是首元素的地址,我们把这个叫做数组名的降级bubble_sort(arr, sz);int i = 0;for (i = 0; i < sz; i++){printf("%d ", arr[i]);}return 0;}// 0 1 2 3 4 5 6 7 8 9

五:数组实例

1.三子棋

写一个程序项目需要很多个代码,创建一个文件可能会太复杂不方便管理,因此我们就通过创建多文件来方便代码的优化与管理。

(1):文件的编排

我们需要创建三个文件:
test.c:放置实现/测试该游戏程序逻辑的内部细节函数
game.h:放置游戏函数的声明,头文件的引用和标识符常量的定义
game.c:放置主程序的实现,包括进入、退出游戏的程序和游戏的整体实现的函数

game的模块被test.c声明引用,最终形成了一个游戏。

(2):实现游戏的进入与退出

分析逻辑:

1:首先我们需要设计程序的菜单和界面,要让玩家知道怎么选择,从而我们设计一个menu函数
2.为了让这个游戏至少执行一次,我们运用do....while循环.
3.这个时候我们要定义一个input变量(在do…while上面定义),需要用scanf来输入这个变量,用switch语句来匹配输入input的值的想法,输入1开始游戏,0退出游戏,输入其他数值就重新输入。
4.判断条件为input,input输入值为0时,判断为假,跳出循环。
5.如果我们选择1,开始玩游戏,这个时候可以设置一个game函数,在game函数里进行简单的三子棋游戏。
6.我们可以把需要使用的头文件的引用放在game.h这个文件中,在每个源文件中再引用这个头文件即可。

代码:

test.c

#include"game.h"void menu(){printf("*********************************\n");printf("********1. play********\n");printf("********0. exit********\n");printf("*********************************\n");}void game(){printf("三子棋游戏\n");}#includeint main(){int input = 0;do{menu();printf("请选择:>");scanf("%d", &input);switch (input){case 1:game();break;case 0:printf("退出游戏\n");break;default:printf("选择错误,请重新选择!\n");break;}} while (input);return 0;}

game.h

#pragma once#include

这个时候只是打印了界面,它的游戏功能还没有实现,那么我们接下来就要实现这个功能

(3):对棋盘的设计与打印

分析逻辑:

1.我们可以创建一个3×3的简式棋盘,这个时候我们就联想到了可以通过二维数组的思想来设计棋盘,玩家下棋下*,电脑下棋用的是#,所以我们需要定义一个char类型的二维数组,不过3*3设定的太死板,所以我们可以运用#define这个语法来定义行(ROW)和列(COL)。
2.我们这个时候对棋盘初始化,我们设定一个初始化棋盘的函数init_board(),我们初始化数组的每一个元素都为空格(如果初始化为0,那么会导致棋盘上下左右长度大小不一样)。
3.这个时候我们开始对棋盘打印,我们设定一个打印棋盘的函数display_board(),
下面就是我们想要打印出来的棋盘

 | | ---|---|--- | | ---|---|--- | | //共五行()//我们可以把第一行进行拆分://每一个元素就是:空格+元素+空格 和 |的交替,而最后一个|不需要打印//我们再把第二行进行拆分://每一个元素就是:---- 和 |的交替,同样最后一个|不需要打印//第一行和第二行算一组row,一共有三组row,而第三组row里面没有---//所以这个程序可以通过选择和循环进行实现//列也是这样的方法来拆分的//我们可以把第一列进行拆分//也就是两个---为一组col,|为另一组col,一共三组col,而第三组col里面没有竖线// 
代码:

test.c

#include"game.h"void menu(){printf("*********************************\n");printf("********1. play********\n");printf("********0. exit********\n");printf("*********************************\n");}void game(){//数据的存储需要一个3*3的二维数组char board[ROW][COL] = { 0 };init_board(board,ROW,COL);display_board(board,ROW,COL);}#includeint main(){int input = 0;do{menu();printf("请选择:>");scanf("%d", &input);switch (input){case 1:game();break;case 0:printf("退出游戏\n");break;default:printf("选择错误,请重新选择!\n");break;}} while (input);return 0;}

game.c

#include"game.h"//初始化棋盘的每一个数组都为空格void init_board(char board[ROW][COL], int row, int col){int i = 0;int j = 0;for (i = 0; i < row; i++){for (j = 0; j < col; j++){board[i][j] = ' ';}}}//void display_board(char board[ROW][COL], int row, int col)//{//int i = 0;//for (i = 0; i < row; i++)//{////printf(" %c | %c | %c \n", board[i][0], board[i][1], board[i][2]);////这样打印会导致最后一行也会出现---|---|---,所以我们再加if//if (i < row - 1)//printf("---|---|---\n");//}//}//但实际这个代码还是不好,如果我改成10行10列,最后的结果还是10行3列,//因为第21行代码把列数写死了//所以我们这样修改void display_board(char board[ROW][COL], int row, int col){int i = 0;for (i = 0; i < row; i++)//决定我们要打印多少行{int j = 0;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("---");if (j < col - 1)printf("|");}printf("\n");}}}

game.h

#pragma once#include#define ROW 3#define COL 3//初始化棋盘void init_board(char board[ROW][COL], int row, int col);//打印棋盘void display_board(char board[ROW][COL], int row, int col);

(4):玩家与电脑开始玩游戏

分析逻辑:

1.设置一个玩家下棋的函数play_move(),由于我们下棋还是要下到数组里面去,所以我们把board,ROW,COL放到play_move函数里面作形参。
2.这个时候发现我们只光有一个棋盘,玩家没法找地方落子,这个时候我们在game.c主函数里面设置横纵坐标x,y,由于存在越界的可能,这个时候我们要判断坐标的合法性->坐标是否被占用
3.由于棋盘是由二维数组的思想创建的,第一行第一列的下标为0,但可能有些玩家小白会认为第一行第一列的坐标为(1,1),所以我们利用if.....else语句,横坐标、纵坐标的范围我们就确定了。由于我们是下棋,要多次输入下棋的坐标,所以我们要用循环语句while(1)死循环,直到我们输入合法了才会跳出死循环,这个时候我们要在棋盘(数组)里面下棋,但其实我们第一行第一列的坐标是(0,0),所以我们在if…else语句里面再用if语句判断坐标是不是空格,如果是空格,则我们可以在里面下棋(也就是玩家将*填进数组里面去)然后用break跳出循环,如果不是,那说明该点已经被占用了。
4.下完棋后棋盘需要重新打印(把玩家函数和打印函数放到test.c内部细节函数的while(1)里面)
5.同理我们也要设置个电脑下棋函数computer_move()函数,与玩家不同的是,电脑是随机下棋的,同样我们先设置横纵坐标x,y,生成随机的下标,我们利用rand函数(rand函数可以生成一个随机数,这个数字的范围是0~RAND_MAX(32767)) 但想调用rand需要先在主函数里调用srand函数
调用完后,由于横纵坐标范围都是[0,2],所以我们分别对行列取余,这样就可以确保横纵坐标不会越界,然后与玩家下棋的逻辑一样,用if语句判断坐标是不是空格,如果是空格,则电脑可以在里面下棋(也就是电脑将#填进数组里面去)然后用break跳出循环,如果不是,那说明该点已经被占用了。(注意:与玩家下棋不同的之处在于:玩家下棋他可能不知道第一行第一列的坐标其实是(0,0),而电脑的横纵坐标已经确定范围是[0,2]了,所以if语句判断坐标的时候,不需要再像玩家下棋那样横纵坐标减1来判断)

代码:

game.h

#pragma once#include#define ROW 3#define COL 3//初始化棋盘void init_board(char board[ROW][COL], int row, int col);//打印棋盘void display_board(char board[ROW][COL], int row, int col);//玩家下棋void player_move(char board[ROW][COL], int row, int col);//电脑下棋void computer_move(char board[ROW][COL], int row, int col);

game.c

#include"game.h"void init_board(char board[ROW][COL], int row, int col)//初始化棋盘的每一个数组都为空格{int i = 0;int j = 0;for (i = 0; i < row; i++){for (j = 0; j < col; j++){board[i][j] = ' ';}}}//void display_board(char board[ROW][COL], int row, int col)//{//int i = 0;//for (i = 0; i < row; i++)//{////printf(" %c | %c | %c \n", board[i][0], board[i][1], board[i][2]);////这样打印会导致最后一行也会出现---|---|---,所以我们再加if//if (i < row - 1)//printf("---|---|---\n");//}//}//但实际这个代码还是不好,如果我改成10行10列,最后的结果还是10行3列,//因为第21行代码把列数写死了//所以我们这样修改void display_board(char board[ROW][COL], int row, int col){int i = 0;for (i = 0; i < row; i++)//决定我们要打印多少行{int j = 0;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("---");if (j < col - 1)printf("|");}printf("\n");}}}//玩家下棋void player_move(char board[ROW][COL], int row, int col){int x = 0;int y = 0;printf("玩家下棋:>\n");while (1){printf("请输入要下棋的坐标:>");scanf("%d %d", &x, &y);//1.坐标的合法性//2.坐标是否被占用if (x >= 1 && x <= row && y >= 1 && y <= col){if (board[x - 1][y - 1] == ' '){board[x - 1][y - 1] = '*';break;}else{printf("该坐标被占用,请重新输入\n");}}else{printf("坐标非法,重新输入\n");}}}//电脑随机下棋void computer_move(char board[ROW][COL], int row, int col){printf("电脑下棋:>\n");//0~32726//%3-->0~2while (1){int x = rand() % row;int y = rand() % col;if (board[x][y] == ' '){board[x][y] = '#';break;}}}

test.c

#include"game.h"void menu(){printf("*********************************\n");printf("********1. play********\n");printf("********0. exit********\n");printf("*********************************\n");}void game(){//数据的存储需要一个3*3的二维数组char board[ROW][COL] = { 0 };init_board(board,ROW,COL);display_board(board,ROW,COL);//玩游戏while (1){player_move(board, ROW, COL);display_board(board, ROW, COL);computer_move(board, ROW, COL);display_board(board, ROW, COL);}}#includeint main(){int input = 0;//设置随机数的生成器srand((unsigned int)time(NULL));//调用srand函数确定一个生成随机数的起始位置//(srand函数里面有个变化值即可,因为括号里随机输入一个数,结果都不一样)//srand函数需要一个unsigned int类型的变动的值。//我们要产生一个随机值,而这个随机值的生成需要一个随机值//我们似乎进入了一个先有鸡先有蛋的问题,这时就需要引入一个概念叫做时间戳//时间戳表示一个时间,//这个时间就是相对于1970年1月1日到现在的时刻我们经过了多少秒,然后将它转化为一个数字,//其实这个数字指的是://电脑此时此刻运行这个代码的时间和计算机的起始时间的一个差值,//单位为s,就是一个时间戳//由于时间是不断变动的,它就可以满足我们的要求,//time函数可以实现当前的这个时间戳,可以返回这个数字,//time函数需要一个指针类型的参数,//如果不想要那个参数,那我们就给它一个空指针.//它的返回类型是time_t,需要强制类型转换为unsigned int//让我们回到rand函数那里do{menu();printf("请选择:>");scanf("%d", &input);switch (input){case 1:game();break;case 0:printf("退出游戏\n");break;default:printf("选择错误,请重新选择!\n");break;}} while (input);return 0;}

(5):胜负的判定

分析逻辑:

1.既然是三子棋,是一个竞技类游戏,那就必然有胜负,是不是有三种状态:玩家赢,电脑赢,平局? 错 其实按照计算机思维,有四种状态:“玩家赢、电脑赢、平局、游戏继续”
2.这四种状态我们应该在玩游戏那一行代码开始检测,玩家/电脑每走一步棋就要判定棋局是否满足那四种状态,——在玩家/电脑每走一步棋play_move()函数和棋盘打印函数display_move()函数之间判定四种状态,所以我们加入状态函数is_win(),来判定四种状态,我们在循环外面用字符变量 ret来接收四种状态,而状态函数要告诉我们4种状态,我们规定:玩家赢:‘*’;电脑赢:‘#’,平局:‘Q’,游戏继续:‘C’ 。
3.“游戏继续”这个状态出现的次数最多,而且走完棋后我们要判断四种状态,所以在test.c上利用if语句——如果状态变量不等于’C’,则跳出循环语句,再来用多分支语句判断是赢是输还是平局,判断完后打印最终棋盘。如果状态变量等于’C’,则游戏继续。
4.然后我们要在game.c的游戏实现函数里面用状态函数来实现赢法。
三行我们可以利用循环语句,里面再用if语句通过判断行数,列数。然后再用两个if语句来判断左对角线和右对角线。从而来判断谁赢谁输。
5.平局——(没人赢且没空格)得单独拿出来讨论。我们再写一个函数is_full(),判断它是否空格满了,如果满了返回1
6.为了支持is_win函数,从而写了(简单分装了)is_full函数(),我们不想把这个接口(函数)暴露到头文件里面去,那么我们不用在头文件里面声明这个函数,如果不想让其他源文件看到接口,那么我们直接在int is_full()前面加static,从而只能在game.c里面看到它,别人想用?No way!
7.与第四步不同,还有一种方法(可以不需要把is_win函数的代码写死):判断行,定义对应的count变量对棋子进行计数,若有一个变量等于行数代表有人胜利,返回对应的棋子。(其实我们可以直接把每一个元素进行比较,并且它们还需要满足不为空格)。判断列,判断对角线也是同样的道理。

(最终) 代码:

game.h

#pragma once#include#include#include#define ROW 3#define COL 3//初始化棋盘void init_board(char board[ROW][COL], int row, int col);//打印棋盘void display_board(char board[ROW][COL], int row, int col);//玩家下棋void player_move(char board[ROW][COL], int row, int col);//电脑下棋void computer_move(char board[ROW][COL], int row, int col);//判断游戏状态char is_win(char board[ROW][COL], int row, int col);

game.c

#include"game.h"void init_board(char board[ROW][COL], int row, int col)//初始化棋盘的每一个数组都为空格{int i = 0;int j = 0;for (i = 0; i < row; i++){for (j = 0; j < col; j++){board[i][j] = ' ';}}}//void display_board(char board[ROW][COL], int row, int col)//{//int i = 0;//for (i = 0; i < row; i++)//{////printf(" %c | %c | %c \n", board[i][0], board[i][1], board[i][2]);////这样打印会导致最后一行也会出现---|---|---,所以我们再加if//if (i < row - 1)//printf("---|---|---\n");//}//}//但实际这个代码还是不好,如果我改成10行10列,最后的结果还是10行3列,//因为第21行代码把列数写死了//所以我们这样修改void display_board(char board[ROW][COL], int row, int col){int i = 0;for (i = 0; i < row; i++)//决定我们要打印多少行{int j = 0;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("---");if (j < col - 1)printf("|");}printf("\n");}}}//玩家下棋//方法一:void player_move(char board[ROW][COL], int row, int col){int x = 0;int y = 0;printf("玩家下棋:>\n");while (1){printf("请输入要下棋的坐标:>");scanf("%d %d", &x, &y);//1.坐标的合法性//2.坐标是否被占用if (x >= 1 && x <= row && y >= 1 && y <= col){if (board[x - 1][y - 1] == ' '){board[x - 1][y - 1] = '*';break;}else{printf("该坐标被占用,请重新输入\n");}}else{printf("坐标非法,重新输入\n");}}}//方法二:void player_move(char board[ROW][COL], int row, int col)//玩家下棋{printf("玩家下棋\n");while (1){int i = 0;int j = 0;printf("请输入坐标:");scanf("%d %d", &i, &j);if (board[i - 1][j - 1] == ' '){board[i - 1][j - 1] = '*';break;}else{printf("请重新输入\n");}}}//电脑随机下棋//方法一:void computer_move(char board[ROW][COL], int row, int col){printf("电脑下棋:>\n");//0~32726//%3-->0~2while (1){int x = rand() % row;int y = rand() % col;if (board[x][y] == ' '){board[x][y] = '#';break;}}}//方法二:void computermove(char board[ROW][COL], int row, int col)//电脑下棋{printf("电脑下棋\n");while (1){int i = rand() % ROW;int j = rand() % COL;if (board[i][j] == ' '){board[i][j] = '#';break;}}}//如果棋盘满了返回1,不满返回0static int is_full(char board[ROW][COL], int row, int col){int i = 0;for (i = 0; i < row; i++){int j = 0;for (j = 0; j < col; j++){if (board[i][j] == ' '){return 0;}}}return 1;}//判断游戏状态(与上面static连在一起)//方法一:char is_win(char board[ROW][COL], int row, int col){int i = 0;for (i = 0; i < row; i++){if (board[i][0] == board[i][1] && board[i][1] == board[i][2] && board[i][0] != ' '){return board[i][0];}}for (i = 0; i < col; i++){if (board[0][i] == board[1][i] && board[1][i] == board[2][i] && board[0][i] != ' '){return board[0][i];}}if (board[0][0] == board[1][1] && board[1][1] == board[2][2] && board[1][1] != ' '){return board[1][1];}if (board[0][2] == board[1][1] && board[1][1] == board[2][0] && board[1][1] != ' '){return board[1][1];}//判断平局if (is_full(board, row, col) == 1)return 'Q';//继续elsereturn 'C';}//方法二:(把static去掉了)//判断胜负//玩家胜利return *//电脑胜利return #//没人胜利同时还可以继续游戏return 'c'//所有的位置都被占满了,return 'q'char is_win(char board[ROW][COL], int row, int col){int i = 0;//判断行for (i = 0; i < row; i++){int j = 0;int count1 = 0;int count2 = 0;for (j = 0; j < col; j++){if (board[i][j] == '*'){count1++;}if (board[i][j] == '#'){count2++;}if (count1 == row)return '*';if (count2 == row)return '#';}}//判断列for (i = 0; i < col; i++){int j = 0;int count1 = 0;int count2 = 0;for (j = 0; j < row; j++){if (board[j][i] == '*'){count1++;}if (board[j][i] == '#'){count2++;}if (count1 == col)return '*';//玩家赢if (count2 == col)return '#';//电脑赢}}//判断对角线int count1 = 0;int count2 = 0;for (i = 0; i < row; i++){if (board[i][i] == '*'){count1++;}if (board[i][i] == '#'){count2++;}if (count1 == col)return '*';//玩家赢if (count2 == col)return '#';//电脑赢}int count3 = 0;int count4 = 0;for (i = 0; i < row; i++){if (board[row - 1 - i][i] == '*'){count3++;}if (board[row - 1 - i][i] == '#'){count4++;}if (count3 == col)return '*';//玩家赢if (count4 == col)return '#';//电脑赢}//判断是否平局int count = 0;for (i = 0; i < row; i++){int j = 0;for (j = 0; j < col; j++){if (board[i][j] == ' '){count++;}}}if (count == 0){return 'q';//平局}return 'c';//都不满足,继续游戏}

test.c

#include"game.h"void menu(){printf("*********************************\n");printf("********1. play********\n");printf("********0. exit********\n");printf("*********************************\n");}void game(){char ret = 0;//数据的存储需要一个3*3的二维数组char board[ROW][COL] = { 0 };init_board(board,ROW,COL);display_board(board,ROW,COL);//玩游戏while (1){player_move(board, ROW, COL);display_board(board, ROW, COL);ret = is_win(board, ROW, COL);if (ret != 'C')break;computer_move(board, ROW, COL);display_board(board, ROW, COL);ret = is_win(board, ROW, COL);if (ret != 'C')break;}if (ret == '*'){printf("玩家赢\n");}else if (ret == '#'){printf("电脑赢\n");}else if (ret == 'Q'){printf("平局\n");}display_board(board, ROW, COL);}#includeint main(){int input = 0;//设置随机数的生成器srand((unsigned int)time(NULL));//调用srand函数确定一个生成随机数的起始位置//(srand函数里面有个变化值即可,因为括号里随机输入一个数,结果都不一样)//srand函数需要一个unsigned int类型的变动的值。//我们要产生一个随机值,而这个随机值的生成需要一个随机值//我们似乎进入了一个先有鸡先有蛋的问题,这时就需要引入一个概念叫做时间戳//时间戳表示一个时间,//这个时间就是相对于1970年1月1日到现在的时刻我们经过了多少秒,然后将它转化为一个数字,//其实这个数字指的是://电脑此时此刻运行这个代码的时间和计算机的起始时间的一个差值,//单位为s,就是一个时间戳//由于时间是不断变动的,它就可以满足我们的要求,//time函数可以实现当前的这个时间戳,可以返回这个数字,//time函数需要一个指针类型的参数,//如果不想要那个参数,那我们就给它一个空指针.//它的返回类型是time_t,需要强制类型转换为unsigned int//让我们回到rand函数那里do{menu();printf("请选择:>");scanf("%d", &input);switch (input){case 1:game();break;case 0:printf("退出游戏\n");break;default:printf("选择错误,请重新选择!\n");break;}} while (input);return 0;}

2.扫雷游戏

(1):文件的编排

我们同样需要创建三个文件:
play.c:放置实现/测试该游戏程序逻辑的内部细节函数
game.h:放置游戏函数的声明,头文件的引用和标识符常量的定义
game.c:放置主程序的实现,包括进入、退出游戏的程序和游戏的整体实现的函数

game的模块被play.c声明引用,最终形成了一个游戏。

(2):实现游戏的进入与退出

分析逻辑:

1:首先我们需要设计程序的菜单和界面,要让玩家知道怎么选择,从而我们设计一个menu函数
2.为了让这个游戏至少执行一次,我们运用do....while循环.
3.这个时候我们要定义一个input变量(在do…while上面定义),需要用scanf来输入这个变量,用switch语句来匹配输入input的值的想法,输入1开始游戏,0退出游戏,输入其他数值就重新输入。
4.判断条件为input,input输入值为0时,判断为假,跳出循环。
5.如果我们选择1,开始玩游戏,这个时候可以设置一个game函数,在game函数里进行简单的扫雷游戏。
6.我们可以把需要使用的头文件的引用放在game.h这个文件中,在每个源文件中再引用这个头文件即可。

代码和上面三子棋的一样。

(3):对棋盘的设计与打印

分析逻辑:

我们以这个9*9的格子为例:

由图我们可以看出,1表示周围的8个坐标里面有一个雷,同理2,3表示也一样。

1.我们可以根据9×9的棋盘,创建一个9×9的二维数组
2.接下来我们来排雷,当你点完一个键后,出来1个/多个数字,这个时候我们就要排查数字周围的空格(坐标)有几个地雷,最多会有8个空格给你排查,但如果你点击最边上的键的时候,就会发现我们如果在程序中遍历排查8个空格的话,会存在数组越界的情况,如果用if语句判断这些特殊的情况,未免太麻烦了。所以我们把数组的范围扩大,把9×9的数组换成11×11的数组。
3.如果想增大难度,那就没必要把数组的下标规定死,所以我们在game.h里面利用#define来定义ROW(9)与COL(9),然后再用#define定义一个ROWS ROW+2,COLS, COL+2。 这样就可以使用两种不同类型的二维数组了。
4.我们这个时候我们开始对棋盘打印,我们设定一个打印棋盘的函数display_board(),但实际我们棋盘用的行列数是ROW和COL,在game文件里,char board数组里传参的还是ROWS,COLS(游戏运行时防止数组越界),这个时候打印的不好看,所以我们一开始先打印列号(j)(j=0开始循环(数组下标开始为0),防止数组列号错位)

(4):设置地雷

分析逻辑

1.将地雷设置成1,非雷设置成0。但会出现数字1与地雷1无法区分的情况。所以这个时候我们再创建一个棋盘(二维数组),棋盘1/数组mine用来放布置好的地雷的信息,棋盘2/数组show用来放排查出的地雷的信息.

2.mine数组的初始化全为0,布置地雷的时候改为1。show数组初始化为‘*’,排查出地雷后,具体位置改为数字字符,比如:‘3’,这个时候我们设置init_board()函数,对mine和show数组初始化(本例子中要初始化到11行11列)

3.对二维数组初始化我们用遍历,由于两个数组初始化的内容不一样,我们对init_board()函数的形参定义一个字符变量set接收这两个初始化内容

4.打印完棋盘后,我们就开始布置地雷函数set_mine()(对地雷初始化),我们只在9×9的格子里布置地雷,因此我们在play.c里面将ROW和COL放到地雷函数里面去,在game文件里面,char board数组里传参的还是ROWS,COLS,在game.c文件里面,我们要思考布置几个地雷,假设我们布置10个地雷,我们可以用#define定义一个变量可以让我们改变布置地雷数,我们利用while(count)循环来布置,我们在9×9格子里的81个坐标找10个坐标来放置地雷,我们先设置横纵坐标x,y,生成随机的下标,我们利用rand函数(rand函数可以生成一个随机数,这个数字的范围是0~RAND_MAX(32767)) 但想调用rand需要先在play.c主函数里调用srand函数,调用完后,由于我们发现数组下标范围[0,8],但真正在棋盘上布置地雷的时候,行列分别是从1~9,所以我们利用rand函数分别对ROW,COL取余再+1,再来用if语句判断这个位置有没有布置过雷。

5.上述步骤4打印棋盘后,其实玩扫雷游戏的时候我们不需要打印mine数组的棋盘,布置完雷打印后,发现雷的位置显示了出来,我们将play.c里面的display_board函数先屏蔽掉,布置完雷后我们就开始排雷。

6.我们设定一个排雷函数find_mine(),我们将mine,show,ROW,COL设置为实参(mine数组里面找,然后放到show数组里面)(从9×9的mine数组传到9×9的show数组里面去),形参里面的数组是ROWS和COLS。

7.我们再次运用while循环来排雷,然后用if语句圈定雷的坐标范围,假如你输入的坐标合法,假如你坐标输入为(3,3),我们得统计一下mine数组里面(3,3)这个坐标附近究竟有多少个雷,所以我们再令int count 变量来接收地雷计数器函数get_mine_count(),我们将mine,x,y列为实际参数(因为我们要去mine数组里统计雷的个数),统计结束后还得放到show数组里面去,所以让show数组来接收count,假设count是3,但我们实际show数组接收的是字符’3’,所以我们在count后面加'0'(数字字符减去'0'得到对应的数字,所以反过来看对应的数字+'0'就能得到相应的字符)即可。

8.那么地雷计数器函数get_mine_count()怎么来统计地雷的个数呢?我们把周围的(挨着你输入的坐标数)坐标给遍历一圈,如果这个坐标叫(x,y),周围的坐标分别是(x-1,y),(x-1,y-1)…我们要判断周围的坐标是否等于’1’,然后count++ 但这样做要判断8个坐标,实在是太麻烦了。我们发现,非雷里面如果放的是数字0的话,假设你的坐标输入,得到的反馈是那个空格显示了1(周围8个坐标里面有一个是地雷),我们把这9个坐标加起来得到了数字1,即1个地雷,但实际上非雷放的是字符'0',上面我们介绍了字符转数字的方法,我们就减去里面的8个坐标再×上字符'0’,就能成功统计出地雷个数了,我们直接返回这个值优化代码。

(5):判断胜利

分析逻辑

1.假如我们布置了10个雷,一共有81个坐标,那么我们找到71个位置,游戏就结束了,这个时候我们要在圈定排雷范围的if语句下面再用一个if…else语句在mine数组里面判断你是赢还是输,在分支语句中一边是被炸死了 一边是还在用count来接收地雷计数函数(排雷),排完雷游戏也就结束了,此时定义一个win变量,我们还是要整体定义一个while循环来包住整个if语句,循环的条件我们可以通过第一行说的规律=>(ROW×COL-地雷数),只要排除掉一个雷,我们离胜利越来越近了,就win++,循环条件不满足后,跳出循环,循环外面我们再利用if语句来判断当win变量等于无雷的坐标的时候,就判断出游戏胜利了。

2.但代码还是有缺陷,我们一直输入同一个坐标,游戏居然能够胜利
所以我们在坐标合法的前提下(if语句下面)再用个if…else语句来套住上面的if…else语句,我们通过show数组的坐标是否等于’*’来排查这个bug

说明:

上述逻辑是点进去一次只排查了一个坐标,但真正的扫雷游戏,一次可能展开一片,比如下图

一次展开一片运用到了递归的思想

递归函数的思路为只要找到满足

  • 该地址合法
  • 该地址不是雷
  • 该地址周围没有雷
  • 该地址没有被排查过
    满足上面的条件,就可以递归去看周围的8个坐标

这里展开一片的代码我以后再补充,先给大家推送一个我觉得写的特别好的完整的扫雷程序的文章
扫雷程序完整版

代码:

game.h

#pragma once#include#include #include #define ROW 9#define COL 9#define ROWS ROW+2#define COLS COL+2#define EASY_COUNT 10//初始化棋盘void init_board(char board[ROWS][COLS], int rows, int cols, char set);//打印棋盘void display_board(char board[ROWS][COLS], int row, int col);//布置地雷void set_mine(char mine[ROWS][COLS], int row, int col);//排查雷void find_mine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);

game.c

#include "game.h"void init_board(char board[ROWS][COLS], int rows, int cols, char set){int i = 0;int j = 0;for (i = 0; i < rows; i++){for (j = 0; j < cols; j++){board[i][j] = set;}}}void display_board(char board[ROWS][COLS], int row, int col){//我们只打印11*11数组中的9*9,行列从1开始int i = 0;int j = 0;//列号for (j = 0; j <= col; j++){printf("%d ", j);}printf("\n");for (i = 1; i <= row; i++){printf("%d ", i);for (j = 1; j <= col; j++){printf("%c ", board[i][j]);}printf("\n");}}void set_mine(char mine[ROWS][COLS], int row, int col){//假设我们布置10个地雷int count = EASY_COUNT;//用计数器来统计地雷的个数while (count){int x = rand() % row + 1;//0~8+1=1~9int y = rand() % col + 1;//判断这个位置有没有布置雷,//没有雷为'0',有雷为'1'.if (mine[x][y] == '0'){mine[x][y] = '1';count--;//每布置一个地雷,计数器递减,//直到count减为0跳出循环}}}int get_mine_count(char mine[ROWS][COLS], int x, int y){//非雷里面如果放的是数字0的话,//假设你的坐标输入,//得到的反馈是那个空格显示了1(周围8个坐标里面有一个是地雷),//我们把这9个坐标加起来得到了数字1,即1个地雷,//但实际上非雷放的是字符'0',//我们介绍过字符转数字的方法—数字字符减去'0'得到对应的数字,//我们就减去里面的8个坐标再×上字符'0'//就能成功统计出地雷个数了return (mine[x - 1][y] +mine[x - 1][y - 1] +mine[x][y - 1] +mine[x + 1][y - 1] +mine[x + 1][y] +mine[x + 1][y + 1] +mine[x][y + 1] +mine[x - 1][y + 1] - 8 * '0');//直接返回这个值优化代码}void find_mine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col){//怀疑雷是xx坐标int x = 0;int y = 0;int win = 0;while (win < row * col - EASY_COUNT){printf("请输入要排查雷的坐标:>");scanf("%d %d", &x, &y);if (x >= 1 && x <= row && y >= 1 && y <= col){//坐标被排查过if (show[x][y] == '*'){if (mine[x][y] == '1'){printf("很遗憾,你被炸死了\n");//每一次排完雷后重新打印棋盘display_board(mine, ROW, COL);break;}else{//统计mine数组里面有多少个雷,//然后传到show数组里面去int count = get_mine_count(mine, x, y);//接收的是字符//数字字符减去'0'得到对应的数字,//所以反过来看对应的数字+'0'就能得到相应的字符show[x][y] = count + '0';display_board(show, ROW, COL);//重新打印棋盘win++;}}else{printf("该坐标已经被排查过了\n");}}else{printf("坐标非法,请重新输入\n");}}if (win == row * col - EASY_COUNT){printf("恭喜你,排雷成功\n");display_board(mine, ROW, COL);}}

play.c

#include"game.h"void menu(){printf("*********************************\n");printf("********1. play********\n");printf("********0. exit********\n");printf("*********************************\n");}void game(){//设计两个数组存储信息char mine[ROW][COL] = { 0 };char show[ROW][COL] = { 0 };//初始化棋盘//mine初始化为全‘0’//show初始化为全‘*’init_board(mine, ROWS, COLS, '0');init_board(show, ROWS, COLS, '*');//打印棋盘//玩扫雷游戏的时候我们不需要打印mine数组的棋盘,//所以我们将下面这一行代码屏蔽掉 //display_board(mine, ROW, COL);//布置好的雷显示了出来,//所以我们将这一行代码屏蔽掉 //display_board(show, ROW, COL);//布置雷set_mine(mine, ROW, COL);//排雷//display_board(mine, ROW, COL);//不能开透视模式display_board(show, ROW, COL);find_mine(mine, show, ROW, COL);}#includeint main(){int input = 0;//设置随机数的生成器srand((unsigned int)time(NULL));//调用srand函数确定一个生成随机数的起始位置//(srand函数里面有个变化值即可,因为括号里随机输入一个数,结果都不一样)//srand函数需要一个unsigned int类型的变动的值。//我们要产生一个随机值,而这个随机值的生成需要一个随机值//我们似乎进入了一个先有鸡先有蛋的问题,//这时就需要引入一个概念叫做时间戳//时间戳表示一个时间,//这个时间就是相对于1970年1月1日到现在的时刻我们经过了多少秒,//然后将它转化为一个数字,//其实这个数字指的是://电脑此时此刻运行这个代码的时间和计算机的起始时间的一个差值,//单位为s,就是一个时间戳//由于时间是不断变动的,它就可以满足我们的要求,//time函数可以实现当前的这个时间戳,可以返回这个数字,//time函数需要一个指针类型的参数,//如果不想要那个参数,那我们就给它一个空指针.//它的返回类型是time_t,需要强制类型转换为unsigned int//让我们回到rand函数那里do{menu();printf("请选择:>");scanf("%d", &input);switch (input){case 1:game();break;case 0:printf("退出游戏\n");break;default:printf("选择错误,请重新选择!\n");break;}} while (input);return 0;}

这里展开一片的代码我以后再补充,先给大家推送一个我觉得写的特别好的完整的扫雷程序的文章
扫雷程序完整版