C语言基础4:数组(一、二维数组的初识化、创建与存储、数组越界、冒泡排序、三子棋、扫雷实现)
1. 一维数组
1.1 一维数组的创建和初始化
??数组是一组相同类型元素的集合。
1.1.1 一维数组的创建
type_t arr_name [const_n]; ①type_t 是指数组的元素类型 ②const_n 是一个常量表达式,用来指定数组的大小
例如:
int arr1[10];
int count = 10;
int arr2[count];
char arr3[10];
float arr4[1];
double arr5[20];
注:数组创建,在C99标准之前, [] 中要给一个常量才可以,不能使用变量。在C99标准支持了变长数组的概念。
1.1.2 一维数组的初始化
??数组的初始化是指,在创建数组的同时给数组的内容一些合理初始值(初始化)。
不完全初始化:剩下的元素默认为0
int arr1[10] = {1,2,3};
其他初始化的方式:
int arr2[] = {1,2,3,4};
int arr3[5] = {1,2,3,4,5};
char arr4[3] = {'a',98, 'c'};
数组中存放了:a、b、c
char arr5[] = {'a','b','c'};
数组中存放了:a、b、c、d、e、f、‘/0’
char arr6[] = "abcdef";
??sizeof 用于计算 数组所占空间的大小,用于计算变量、数组、类型的大小(单位:字节),sizeof是操作符。 ??strlen 用于求字符串的长度,也就是 ‘\0’ 之前的字符个数,只能用于求字符串长度。它是库函数,使用得引用头文件。
??这里可以简单理解为:sizeof(arr) = sterlen(arr) + ‘\0’
#include<stdio.h>
#include<string>
int main()
{
char arr4[] = "abcdef";
printf("%d\n", sizeof(arr4));
printf("%d\n", strlen(arr4));
}
??数组在创建的时候如果想不指定数组的确定的大小就得初始化。数组的元素个数根据初始化的内容来确定。
#include<stdio.h>
#include<string>
int main()
{
char arr1[] = "abcd";
char arr2[] = { 'a','b','c','d' };
printf("%d\n", sizeof(arr1));
printf("%d\n", strlen(arr1));
printf("%d\n", sizeof(arr2));
printf("%d\n", strlen(arr2));
}
1.2 一维数组的使用
??[] 为下标引用操作符,其实就是数组访问的操作符。 总结: (1)数组是使用下标来访问的,下标是从0开始。 (2)数组的大小可以通过计算得到。
1.3 一维数组在内存中的存储
??数组在内存中是连续存放的,通过下面的例子,可以看出:随着数组下标的增长,元素的地址,也在有规律的递增。
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main()
{
int i = 0;
int sz = 0;
int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
sz = sizeof(arr) / sizeof(arr[0]);
for (i = 0; i < sz; i++)
{
printf("&arr[%d] = %p\n", i,&arr[i]);
}
return 0;
}
2. 二维数组
2.1 二维数组的创建和初始化
数组创建
int arr[3][4];
char arr[3][5];
double arr[2][4];
创建一个3行4列的数组,创建后我们发现他们的数据是如下存储的。
#include<stdio.h>
#include<string>
int main()
{
int arr[3][4] = { 1,2,3,4,5 };
return 0;
}
修改代码如下,将4、5放在第二行中,
#include<stdio.h>
#include<string>
int main()
{
int arr[3][4] = { {1,2,3},{4,5} };
return 0;
}
??二维数组在创建的时候,不可以采用一维数组arr[] = {1,2,3,4} 这样的方式,否则会报错: ??int arr[2][] = { {1,2,3},{4,5} }; 提示报错 ??int arr[][3] = { {1,2,3},{4,5} }; 无报错 ??也就是说,二维数组如果有初始化,行可以省略,但是列不能省略。
int arr[3][4] = {1,2,3,4};
int arr[3][4] = {{1,2},{4,5}};
int arr[][4] = {{2,3},{4,5}};
2.2 二维数组的使用
??int arr[3][4] = {{1,2,3},{4,5}},二维数组在内存中是连续存放的。他们的下标分别对应以下的行和列。
行\列 | 0 | 1 | 2 | 3 |
---|
0 | arr[0][0] | arr[0][1] | arr[0][2] | arr[0][3] | 1 | arr[1][0] | arr[1][1] | arr[1][2] | arr[1][3] | 2 | arr[2][0] | arr[2][1] | arr[2][2] | arr[2][3] |
#include<stdio.h>
#include<string>
int main()
{
int arr[3][4] = { {1,2,3},{4,5} };
int i = 0;
int j = 0;
for (i = 0;i < 3;i++)
{
for (j = 0;j < 4;j++)
{
printf("%d\t",arr[i][j]);
}
printf("\n");
}
return 0;
}
2.3 二维数组在内存中的存储
??修改代码如下,打印二维数组的存放的地址,观察发现二维数组arr[3][4] 数组相当于存放了3个一维数组,并且每个数组所占空间为4个字节,在第1个一维数组和第2个一维数组过度的时候,他们的存储空间是连续的。
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main()
{
int arr[3][4] = { {1,2,3},{4,5} };
int i = 0;
int j = 0;
for (i = 0; i < 3; i++)
{
for (j = 0; j < 4; j++)
{
printf("&arr[%d][%d]=%p\t",i,j, &arr[i][j]);
}
printf("\n");
}
return 0;
}
3. 数组越界
??数组的下标是有范围限制的。 ??数组的下规定是从0开始的,如果数组有n个元素,最后一个元素的下标就是n-1。 所有数组的下标如果小于0,或者大于n-1,就是数组越界访问了,超出了数组合法空间的访问。 ??C语言本身是不做数组下标的越界检查,编译器也不一定报错,但是编译器不报错,并不意味着程序就是正确的,所以在检查代码时,需要做好越界的检查。
#include <stdio.h>
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]);
}
return 0;
}
??二维数组的行和列也可能存在越界。
4. 数组作为函数参数
4.1 冒泡排序
??冒泡排序核心思想:两两相比,比较后交换,例如10、9、8、7、6、5、4、3、2、1个数,需要比较9趟,每一趟需要计算相应的次数。元素个数=第n趟冒泡排序次数+该趟排序计算次数,我们通过设置一个数字n,来计算交换的次数,统计总共计算了多少次。 ??注意:数组在传参的时候,实际上传递过去的是数组 arr 首元素的地址 &arr[0]
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
void bubble_sort(int arr[],int sz)
{
int i = 0;
int j = 0;
int tmp = 0;
int n = 0;
for (i = 0; i < sz - 1; i++)
{
for (j = 0; j < sz - i - 1; j++)
{
if (arr[j] > arr[j + 1])
{
tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
n++;
}
}
}
printf("本次冒泡排序共计算了:%d次\n", n);
}
int main()
{
int i = 0;
int arr[] = { 10,9,8,7,6,5,4,3,2,1 };
int sz = sizeof(arr) / sizeof(arr[0]);
bubble_sort(arr,sz);
for (i = 0; i < sz; i++)
{
printf("%d ",arr[i]);
}
return 0;
}
4.2 冒泡排序(优化)
??上述的冒泡排序,如果遇到一个1、2、3、4、5、6、7、8、9、10的数组,那么计算的时候也需要从头到尾再计算一遍,如何优化一下代码,避免将已经正确排序的数组再排序了呢。
??可以设置一个flag,用于标志数组是否已经正确排序。假设我们需要排序的数组是:1、10、2、3、4、5、6、7、8、9,那么用优化后的冒泡排序,总共算1趟,1趟计算9次,第2趟识别为有序的数组时就不再排序。
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
void bubble_sort(int arr[],int sz)
{
int i = 0;
int j = 0;
int tmp = 0;
int n = 0;
for (i = 0; i < sz - 1; i++)
{
int flag = 1;
for (j = 0; j < sz - i - 1; j++)
{
if (arr[j] > arr[j + 1])
{
tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
n++;
flag = 0;
}
}
if (flag == 1)
{
break;
}
}
printf("本次冒泡排序共计算了:%d次\n", n);
}
int main()
{
int i = 0;
int arr[] = { 1,10,2,3,4,5,6,7,8,9};
int sz = sizeof(arr) / sizeof(arr[0]);
bubble_sort(arr,sz);
for (i = 0; i < sz; i++)
{
printf("%d ",arr[i]);
}
return 0;
}
??注意,上述用到的break是跳出外层for循环的意思,若没有循环,if 语句单独使用break 会报错。
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main()
{
int arr[] = { 10,9,8,7,6,5,4,3,2,1 };
printf("%p\n", arr);
printf("%p\n", &arr[0]);
printf("%d\n", *arr);
return 0;
}
??从010FF748到1010FF74C,是因为10个元素总共40个字节,一个内存单元为8个字节,那么40÷8=5,也就是这10个元素总共占用5个内存单元,所以48+5=4C。
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main()
{
int arr[] = { 10,9,8,7,6,5,4,3,2,1 };
printf("%p\n", arr);
printf("%p\n", &arr[0]);
printf("%p\n", &arr);
printf("%d\n\n", *arr);
printf("%p\n", arr+1);
printf("%p\n", &arr[0] + 1);
printf("%p\n", &arr+1);
printf("%d\n", *arr+1);
return 0;
}
9. 数组的应用实例1:三子棋
9.1 三子棋运行逻辑
??三子棋游戏规则:在 3 x 3 的九宫格中,双方摆上自己的棋子,当自己的3个棋子连成一条线,就算获胜,很多时候会出现和棋的情况。 ??下面用程序模拟玩家和电脑进行下棋的过程,电脑的AI设置使用time 和 随机函数组合,实现电脑的随机下棋。 ??整个程序分为三个部分组成: ①test.c:作为游戏测试逻辑的程序,主要明确游戏运行的整体框架。 ②game.h:游戏程序的头文件,用于相关的函数声明,符合声明。 ③game.c:游戏中个模块功能通过相关函数的实现,均存放于此。 ??先看下游戏运行的整体框架逻辑:main()函数中,只有test()函数: (1)test()函数用于测试整个游戏的运行过程。 (2)test()函数中,有两个函数:menu()函数、game()函数 ①menu()函数 负责提示玩家输入键值控制游戏的开始和结束。 ②game()函数则包含整个游戏的运行框架,其中又有各类子函数负责各相应模块的功能。
9.2 头文件 game.h
#define _CRT_SECURE_NO_WARNINGS 1
#define ROW 3
#define COL 3
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#include<Windows.h>
void InitBoard(char board[ROW][COL], int row, int col);
void DisplayBoard(char board[ROW][COL], int row, int col);
void PlayerMove(char board[ROW][COL], int row,int col);
void ComputerMove(char board[ROW][COL], int row, int col);
char IsWin(char board[ROW][COL], int row, int col);
9.3 测试函数 test()
#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"
void menu()
{
printf("*****************************************\n");
printf("**** 1.play 0.exit ***\n");
printf("*****************************************\n");
}
void game()
{
char ret = 0;
char board[ROW][COL] = {0};
InitBoard(board, ROW, COL);
DisplayBoard(board,ROW,COL);
while (1)
{
PlayerMove(board,ROW,COL);
DisplayBoard(board, ROW, COL);
ret = IsWin(board, ROW, COL);
if (ret != 'C')
{
break;
}
ComputerMove(board,ROW,COL);
DisplayBoard(board, ROW, COL);
ret = IsWin(board, ROW, COL);
if (ret != 'C')
{
break;
}
}
if (ret == '*')
{
printf("玩家赢\n");
}
else if (ret == '#')
{
printf("电脑赢\n");
}
else
{
printf("平局\n");
}
}
void test()
{
int input = 0;
srand((unsigned int)time(NULL));
do
{
menu();
printf("请选择:>");
scanf("%d", &input);
switch (input)
{
case 1:
game();
printf("三子棋\n");
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("选择错误,请重新选择!\n");
break;
}
} while (input);
}
int main()
{
test();
return 0;
}
9.4 游戏运行函数 game.cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"
void InitBoard(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 DisplayBoard(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++)
{
printf(" %c ", board[i][j]);
if (j < col - 1)
printf("|");
}
printf("\n");
if (i < row - 1)
{
for (j = 0; j < col; j++)
{
printf("---");
if (j < col - 1)
printf("|");
}
printf("\n");
}
}
}
void PlayerMove(char board[ROW][COL], int row, int col)
{
int x = 0;
int y = 0;
printf("玩家走:>\n");
while (1)
{
printf("请输入要下的坐标:>");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (board[x - 1][y - 1] == ' ')
{
board[x - 1][y - 1] = '*';
break;
}
else
{
printf("该坐标已被占用");
}
}
else
{
printf("坐标非法,请重新输入!\n");
}
}
}
void ComputerMove(char board[ROW][COL], int row, int col)
{
int x = 0;
int y = 0;
printf("电脑走:>\n");
while (1)
{
x = rand() % ROW;
y = rand() % COL;
if (board[x][y] == ' ')
{
board[x][y] = '#';
break;
}
}
}
int IsFull(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++)
{
if (board[i][j] == ' ')
{
return 0;
}
}
}
return 1;
}
char IsWin(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][0] == board[i][2] && board[i][0] != ' ')
{
return board[i][0];
}
}
for (i = 0; i < col; i++)
{
if (board[0][i] == board[1][i] && board[0][i] == board[2][i] && board[0][i] != ' ')
{
return board[0][i];
}
}
if (board[0][0] == board[1][1] && board[0][0] == board[2][2] && board[0][0] != ' ')
return board[0][0];
if (board[2][0] == board[1][1] && board[2][0] == board[0][2] && board[2][0] != ' ')
return board[2][0];
if (1 == IsFull(board, ROW, COL))
{
return 'Q';
}
return 'C';
}
10. 数组的应用实例2:扫雷游戏
10.1 扫雷运行逻辑
??一个 n x n 的格子中,有随机布防的雷,而扫雷需要做的是,在不踩到雷的前提下,将其余非雷区的空白格子点掉,当最后一个非雷区的空白格子被点掉后,则游戏胜利。否则踩到雷则游戏失败。 ??我们设计扫雷的具体规则:使用鼠标,随机点击空白格子,若有雷,则游戏结束,若没有雷,则以自身为中心,提示周边 3 x 3 中所有雷的数量,直到最后一个雷被排掉则游戏胜利。 ??整个程序分为三个部分组成: ①test.c:作为游戏测试逻辑的程序,主要明确游戏运行的整体框架。 ②game.h:游戏程序的头文件,用于相关的函数声明,符合声明。 ③game.c:游戏中个模块功能通过相关函数的实现,均存放于此。 ??先看下游戏运行的整体框架逻辑:main()函数中,只有test()函数: (1)test()函数用于测试整个游戏的运行过程。 (2)test()函数中,有两个函数:menu()函数、game()函数 ①menu()函数 负责提示玩家输入键值控制游戏的开始和结束。 ②game()函数则包含整个游戏的运行框架,其中又有各类子函数负责各相应模块的功能
??设计思路,如果设计一个 9 x 9 的棋盘,用于扫雷,那么玩家会点到 [0][0] 这种边界坐标,而我们判断坐标一圈是否有雷的情况,则需要统计 [-1][0] 这种不存在数组中的坐标,因此我们需要设计的棋盘实际应该要大一圈,应该是 11 x 11 的棋盘。 ??也就是我们实际埋雷的棋盘是 11 x 11 的,而玩家看到的棋盘则是 9 x 9 的,因此我们需要有两个棋盘,一个是用于计算埋雷计算周边有多少雷的棋盘(11 x 11),而另一个则是用于展示当前排雷进展的棋盘(9 x 9)。 ??经过辛苦的排雷,终于排雷成功: ??出师不利,被炸:
9.2 头文件 game.h
#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL+2
#define EASY_COUNT 10
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
void InitBoard(char board[ROWS][COLS], int row, int rol, char set);
void DisPlayBoard(char board[ROWS][COLS], int row, int col);
void SetMine(char board[ROWS][COLS], int row, int col);
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);
9.3 测试函数 test()
#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"
void menu()
{
printf("**********************************\n");
printf("** 1.play *** 0.exit **\n");
printf("**********************************\n");
}
void game()
{
char mine[ROWS][COLS] = { 0 };
char show[ROWS][COLS] = { 0 };
InitBoard(mine, ROWS, COLS, '0');
InitBoard(show, ROWS, COLS, '*');
DisPlayBoard(show, ROW, COL);
SetMine(mine, ROW, COL);
FindMine(mine, show, ROW, COL);
}
int main()
{
int input = 0;
srand((unsigned int)time(NULL));
do
{
menu();
printf("请输入1或0开始游戏:>\n");
scanf("%d", &input);
switch (input)
{
case 1:
game();
break;
case 0:
printf("游戏退出:>\n");
break;
default:
printf("请重新输入:>\n");
break;
}
} while (input);
printf("扫雷结束:\n");
return 0;
}
9.4 游戏运行函数 game.cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"
void InitBoard(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 DisPlayBoard(char board[ROWS][COLS], int rows, int cols)
{
int i = 0;
int j = 0;
printf("——扫雷——:\n");
for ( i = 0; i <= cols; i++)
{
printf("%d", i);
}
printf("\n");
for (i = 1; i <= rows; i++)
{
printf("%d", i);
for (j = 1; j <= cols; j++)
{
printf("%c", board[i][j]);
}
printf("\n");
}
printf("——扫雷——:");
}
void SetMine(char mine[ROWS][COLS], int row, int col)
{
int count = EASY_COUNT;
while (count)
{
int x = rand() % row + 1;
int y = rand() % col + 1;
if (mine[x][y] == '0')
{
mine[x][y] = '1';
count--;
}
}
}
int get_mine_count(char mine[ROWS][COLS], int x, int y)
{
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 FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
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 (mine[x][y] == '0')
{
int count = get_mine_count(mine, x, y);
show[x][y] = count + '0';
DisPlayBoard(show, ROW, COL);
win++;
}
else
{
printf("被雷炸了:\n");
DisPlayBoard(mine, row, col);
break;
}
}
else
printf("坐标有误,请重新输入:\n");
}
if (win == ROW * COL - EASY_COUNT)
{
printf("排雷成功\n");
}
}
|