作者主页:
lovewolrd_的博客主页
本章简介:
简述数组相关方面知识点,总结归纳。由浅入深的学习数组知识
图文解释数组冒泡排序:
图文冒泡排序
目录
前言:
1、一维数组
1、1什么是数组
1、2数组的创建
1、3数组的访问
2、二维数组
2、1二维数组的声明
2、2二维数组的初始化
2、3数组的访问
3、数组与指针
3、1指针和数组的区别
4、数组名作函数参数
5、三字棋实例
5、1逻辑过程以及游戏框架搭建
5、1、1菜单选择
5、1、2创建棋盘
5、1、3玩家下棋
5、1、4电脑下棋
5、1、5输赢判断
完整源代码:
头文件
主程序<三字棋.c>
游戏程序
总结:
在前面的学习过程中,大家一定或多或少的接触了数组的使用。本章在了解的基础之上,深入讨论数组,探索数组的细节内容。
C语言支持数组数据结构,它存在的意义上储存相同类型元素的顺序集合。
数组在内存空间的存储是连续的,不同元素之间相差的内存空间取决于存储的类型。数组的最低地址对应数组第一个元素,最高地址为最后一个元素。
看这个声明:
int a;
nt b[3];
前者代表的是一个单一整形变量,后者就是整形变量的集合;因此,数组下标配合名字的声明就构成了最基本的数组名。
数组是一组相同类型元素的集合。
数组的创建方式:
int arr[10];
type arrayName [ arraySize ];数组的声明由数组类型,数组名,数组元素个数组成。
现在数组创建好以后,可以存放int类型的元素
数组创建实例:
int arr[10]; char arr1[10]; float arr2[2]; double arr3[3]; //有待考量的声明方式 int count = 0; int arr4[count]; //执行标准,c99之前,括号类一定要给定常量,不能使用变量,因此在vs或者dev等支持c89标准的编译 //器会出现错误
c注:C99标准之前的数组[ ]内要给定一个常量或者常量表达式,不能使用变量来创建数组,这是因为数组在一声明的过程中就已经向内存获取了空间,是作为一个定值,不能随之变量值而改变。当然C99标准支持这种变长数组的概念,数组的大小可以使用变量,但是数组不能初始化。
对数组的单个元素访问是利用其索引值,例如一个长度为10的整形数组,它的下标0-9对标其每一个值。元素的索引是放在方括号内,也就是下标引用操作符[ ]。
int arr[5]={1,2,3,4,5};
int c = arr[4];
这个语句就是将数组的第五个元素放入int类型的变量当中。同时进行了上述的三个概念操作,声明数组,数组初始化,数组访问。
实例:
#include
int main() { int arr[10];//数组的声明 int i = 0; for (i = 0; i < 10; i++)//利用循环对数组初始化 { arr[i] = i + 1;//通过下标访问元素 } for (i = 0; i < 10; i++)//循环输出数组 { printf("arr[%d]=%d ", i,arr[i]); } return 0; } 编译结果:
当然,c语言不单支持二维数组,这里主要是利用二维数组来概述多维数组的使用。
数据类型 数组名[数组长度][数组长度];
int arr[3][4];
这里就声明了一个行为3,列为4的二维数组;
int arr[3][4]={{1,2,3,4},{5,6,7,8},{9,10,11,12}}; 这里内部括号对每一行进行了初始化。
int arr[3][4]={1,2,3,4,5,6,7,8,9,10,11,12}; 这种初识化也是与上方相等的,内部括号的嵌套可以选择性使用;
int arr[][4]={1,2,3,4,5,6,7,8,9,10,11,12}; 这种声明方式可以不标注有几行,会根据列的长度对括号内部元素自动排序;
int arr[][4]={1,2,3,4,5,6,7,8,9,10}; 这种方式就对数组的不完全初始化,先对已有元素按照列的长度对每一个数组进行初始化,没有初始化的元素会自动初始化为0;
数组的访问总是通过其索引,也就是下标进行访问。
同样使用上述arr[3][4]为例子。
实例:
#include
int main() { int arr[3][4] = { 1 };//不完全初始化 int i = 0; int count = 0; for (i = 0; i < 3; i++) { int j = 0; for (j = 0; j < 4; j++) { arr[i][j] = ++count;//遍历依次存放1-12; } } for (i = 0; i < 3; i++) { int j = 0; for (j = 0; j < 4; j++) { printf(" arr[%d][%d]=%-2d ",i,j, arr[i][j]);//循环打印,-2d对齐便于观察; } printf("\n"); } return 0; } 编译结果:
对于第一步声明时候都初始化元素并没有达到指定数组长度,也就是说数组不能用单个元素对所有数组元素初识化,应当利用循环和下标操作符依次操作每一个数组元素。在不完全初始化情况下,会自动初始化为0;
int arr[3][4]={1};
数组是一个相同类型元素的集合,数组名是首元素的起始地址,整个数组所占空间就是第一个元素地址到最后一个元素地址,一个数组元素之间内存空间是连续的。
同上述例子结合起来,打印每一个数组元素地址,可以观察到实际上每一个元素地址(十六进制表示)的差值都是一个int整形字节的长度
但是虽然数组和地址的关系,那么能说指针和数组是相等的吗?
int arr[3];
int *b;
以上是两个声明,分别声明了数组和指针变量;
数组在声明过后,系统即可在运行过程中为声明的数组类型和长度划分内存空间,再创建数组名。也就是说数组声明以后,在编译过程中是一个常量,数组名指向首元素起始地址。 指针变量声明后,并没有为其初识化任何地址信息,编译器只为指针本身划分了内存空间。
因此在使用过程中*arr是合法的,表示对首元素进行解引用。而*b是不合法的,因为指针变量会随机访问内存中的地址。并且arr++是不合法的,因为数组首元素的值是一个常量,相反b++是合法的,表示对其指针地址指向下一个地址。
数组名作为一个常量指针的理解方式可以会让你更加能理解两者的关系
int arr[3];
int *b;
b=arr;//将首元素地址传递给b指针
因此*(b+1)等同与arr[1]的下标访问操作;
数组名作为首元素的地址大家应该都能理解,在函数调用中有两个例外:
sizeof( arr );//计算数组长度
&arr;//取数组一个元素的指针,并非是首元素。
以上例子传递数组名的时候就不只是传递了首元素地址,传递的是整个数组地址。
数组名作为函数参数传参实际上是传递的一份实参的临时拷贝,函数对其操作不会影响实际参数,但是通过指针间接访问是可以修改原先数组元素。在函数参数中声明数组可以声明为数组,也可以声明为指针,但是只在函数参数传递中才存在这种相等关系。
三字棋玩法:在九宫格内部,当三个连成线的时候即可为胜利。
对与这个游戏,如何用电脑来进行基本原理开发?
游戏环境——基于windows控制台对exe文件进行运行。
显示风格:控制台键盘字符表示
输入输出方式:键盘操作
代码实现策略:模块化封装。采用game.h对函数声明进行封装,便于维护和查找功能。使用三字棋.c对基本的主体函数进行调用。使用game.c封装整个游戏逻辑。
打印一个可视化操作界面,具有简单的选择开始游戏或者退出游戏功能。
对与选择的不同状态需要有不同的响应状况。
对于输入1我们要进行游戏;
对于输入0我们要结束游戏程序;
对于其他输入我们判定为输入错位;
这种多选择语句我们就可以使用switch进行程序设计
菜单函数:
void menu() { printf(" ***************************\n"); printf(" -----------三字棋 ---------\n"); printf(" |***************************|\n"); printf(" |***************************|\n"); printf(" |******* 1.开始游戏 *******|\n"); printf(" |******* 0.退出游戏 *******|\n"); printf(" |***************************|\n"); }
多分支结构选择结构:
do { menu();//菜单 printf("\n"); printf("请输入>:\n"); scanf("%d", &input); switch (input) { case 1: game(); break; case 0: printf("退出游戏\n"); break; default: printf("请重新输入:\n"); break; } }while(input);//这里input的值设计的很巧妙,对于菜单0,实现了对非零的循环结束而终止游戏
输入错误或者不输入都应该进行判断,且支持重复输入和游戏,因此要采用do while循环结构。
棋盘为方格,因此我们采用二维数组,建立3*3的棋盘,并对二维数组的内容初始化为空格,因此我们采用字符数组。对于一个数组的打印,因为要体现其可视化,因此我们要建立表格线。
棋盘的大小应该支持自定义,因此我们在头文件可以使用宏定义常量对行和列进行自定义。
#define ROW 3
#define COL 3
/棋盘初始化 void real(char arr[ROW][COL], int row, int col) { int i = 0; for (i = 0; i < ROW; i++) { int j = 0; for (j = 0; j < COL;j++) { arr[i][j] = ' '; } }
//打印棋盘 void Printf_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"); for (j = 0; j < COL; j++) { printf("---"); if(j
对于棋盘的下棋,首先是输入输出判断,二维数组是用下标进行访问,但游戏者并不知道数组的概念,因此我们在下标引用的实际操作应该是1 1对应数组arr[0][0];再者要对玩家检查是否在规定棋盘中,如果没有应该重新输入,所以需要一个循环。
//玩家下棋 void playergame(char board[ROW][COL]) { printf("玩家下棋\n"); while (1) { int row = 0; int col = 0; scanf("%d %d", &row, &col); if (0 < col && col <= COL && row > 0 && row <= ROW) { if (board[row - 1][col - 1] == ' ') { board[row - 1][col - 1] = '*'; break; } else printf("该位置已经被占用,请重新输入:\n"); } else printf("坐标输入有误,请重新输入:\n"); } }
电脑下棋并不需要输入,因此我们预先设定它的下棋逻辑,既随机下棋,因此我们要使用随机数的生成对应数组的下标。3*3的数组我们应该生成0-2的数字对下标随机访问。
因此需要#incldue
的时间戳函数。以及随机值函数srand((unsigned int)time(NULL)); 以及rand使用随机数通过srand%3创造随机0-2的数。
//电脑下棋 void computer(char board[ROW][COL]) { printf("电脑下棋\n"); while (1) { int row = rand() % 3; int col = rand() % 3; if (board[row][col] == ' ') { board[row][col] = '#'; break; } } }
对于每一次下棋,我们应对双方都棋局进行判断,三字成线就应该结束游戏并判断输赢。
当'*'成线是玩家赢,当'#'成线是电脑赢。当棋盘满了却还没有实现三字成线就应该算平局。
/判断棋盘是否满了 char full_board(char board[ROW][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; }//下方函数根据本函数返回值进行是否平局判断。
//输赢判断 char is_win(char board[ROW][COL]) { int i = 0; int j = 0; char row = 0; char col = 0; int a = 0;//对角线判断计量数 int b = 0; for (i = 0,j=0; i < ROW; i++,j++) { int count = 0; //三行判断 row = board[i][j]; for (j = 0; j < COL; j++) { if ((board[i][j] == row)&&row!=' ') { count++; if (count == 3) { return row;//三行有一行相等返回其一行首元素 } } } count = 0; //三列判断 col = board[i][0]; for (j = 0; j < COL; j++) { if ((board[j][i] == col)&&col!=' ') { count++; if (count == COL) { return col; } } } char temp = board[1][1]; //对角线判断; for (i = 0, j = 0; i < ROW; i++, j++) { if ((board[i][j] == temp) && temp != ' ') { a++; if (a == COL) { return temp; } } if (board[ROW - i - 1][COL - j - 1] == temp && temp != ' ') { b++; if (b == ROW) { return temp; } } } } //调用判断棋盘是否满函数进行平局判断 if (full_board(board) == 0) { return 'c'; } return 'o'; }
以上函数提供输赢后的返回值,因此我们需要接受返回值,并根据不同状况进行输赢判断
//分支语句进行判断 if (ret == '*') { printf("恭喜你赢了\n"); } else if (ret == '#') { printf("电脑赢了\n"); } else if (ret == 'o') { printf("平局\n"); }
同时以上返回值还提供了是否进行游戏的判断,如若没有结束,应当循环的输入和输出。
所以我们要对以上函数进行组装。
void game() { char board[ROW][COL]; real(board, ROW, COL);//初始化棋盘 char ret = 0; Printf_board(board, ROW, COL);//棋盘打印 while (1) { //打印棋盘 //玩家下棋 playergame(board); ret = is_win(board);//输赢判断 if (ret != 'c') { Printf_board(board, ROW, COL);//打印 break; } //电脑下棋 computer(board); Printf_board(board, ROW, COL); if (ret != 'c') { Printf_board(board, ROW, COL); break; } //输赢判断 //返回*人赢 //返回#电脑赢 //返回c继续 //返回o平局 } if (ret == '*') { printf("恭喜你赢了\n"); } else if (ret == '#') { printf("电脑赢了\n"); } else if (ret == 'o') { printf("平局\n"); } }
以上就是对整个游戏进行拆分
//函数声明 #define ROW 3 #define COL 3 #include
#include #include //棋盘初始化 void real(char arr[ROW][COL],int row,int col); //打印棋盘 void Printf_board(char board[ROW][COL]); //玩家下棋 void playergame(char board[ROW][COL]); //电脑下棋 void computer(char board[ROW][COL]); //输赢判断 char is_win(char board[ROW][COL]); char full_board(char board[ROW][COL]); void game();
#define _CRT_SECURE_NO_WARNINGS 1 #include"game.h" //主函数 void menu() { printf(" ***************************\n"); printf(" -----------三字棋 ---------\n"); printf(" |***************************|\n"); printf(" |***************************|\n"); printf(" |******* 1.开始游戏 *******|\n"); printf(" |******* 0.退出游戏 *******|\n"); printf(" |***************************|\n"); } void game() { char board[ROW][COL]; real(board, ROW, COL); char ret = 0; Printf_board(board, ROW, COL); //棋盘元素初始化; while (1) { //打印棋盘 //玩家下棋 playergame(board); ret = is_win(board); if (ret != 'c') { Printf_board(board, ROW, COL); break; } //电脑下棋 computer(board); Printf_board(board, ROW, COL); if (ret != 'c') { Printf_board(board, ROW, COL); break; } //输赢判断 //返回*人赢 //返回#电脑赢 //返回c继续 //返回o平局 } if (ret == '*') { printf("恭喜你赢了\n"); } else if (ret == '#') { printf("电脑赢了\n"); } else if (ret == 'o') { printf("平局\n"); } } int main() { int input= 0; do { menu();//菜单 printf("\n"); printf("请输入>:\n"); scanf("%d", &input); switch (input) { case 1: game(); break; case 0: printf("退出游戏\n"); break; default: printf("请重新输入:\n"); break; } }while(input); return 0; }
#define _CRT_SECURE_NO_WARNINGS 1 #include"game.h" //函数封装 //棋盘初始化 void real(char arr[ROW][COL], int row, int col) { int i = 0; for (i = 0; i < ROW; i++) { int j = 0; for (j = 0; j < COL;j++) { arr[i][j] = ' '; } } } //打印棋盘 void Printf_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"); for (j = 0; j < COL; j++) { printf("---"); if(j
0 && row <= ROW) { if (board[row - 1][col - 1] == ' ') { board[row - 1][col - 1] = '*'; break; } else printf("该位置已经被占用,请重新输入:\n"); } else printf("坐标输入有误,请重新输入:\n"); } } //电脑下棋 void computer(char board[ROW][COL]) { printf("电脑下棋\n"); while (1) { int row = rand() % 3; int col = rand() % 3; if (board[row][col] == ' ') { board[row][col] = '#'; break; } } } //判断棋盘是否满了 char full_board(char board[ROW][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; } char is_win(char board[ROW][COL]) { int i = 0; int j = 0; char row = 0; char col = 0; int a = 0;//对角线判断计量数 int b = 0; for (i = 0,j=0; i < ROW; i++,j++) { int count = 0; //三行判断 row = board[i][j]; for (j = 0; j < COL; j++) { if ((board[i][j] == row)&&row!=' ') { count++; if (count == 3) { return row;//三行有一行相等返回其一行首元素 } } } count = 0; //三列判断 col = board[i][0]; for (j = 0; j < COL; j++) { if ((board[j][i] == col)&&col!=' ') { count++; if (count == COL) { return col; } } } char temp = board[1][1]; //对角线判断; for (i = 0, j = 0; i < ROW; i++, j++) { if ((board[i][j] == temp) && temp != ' ') { a++; if (a == COL) { return temp; } } if (board[ROW - i - 1][COL - j - 1] == temp && temp != ' ') { b++; if (b == ROW) { return temp; } } } } if (full_board(board) == 0) { return 'c'; } return 'o'; }
执行效果:
本章我们主要讲述了数组相关方面知识点并对其加以实际应用,希望对大家有所帮助。
三字棋代码还有很多优化的地方,这个作为基础程序小设计,更看重对数组和函数封装的使用。而三字棋要让电脑变聪明还需要研究博弈算法,对下棋的玩家可能性作出分析和执行,之中需要理解的内容就更加复杂了。还有棋盘扩展后,如何去优化代码让其可以对输赢进行判断。
下一期预先预定——扫雷游戏的封装和实现。