- 前言
- 一维数组
- 冒泡排序
- 二维数组
- 三子棋
- 扫雷游戏
- 结语
本篇文章将对数组相关的内容做一个详解介绍,会介绍一维数组和二维数组相关基本知识,随后介绍冒泡排序算法,最后完成两个小游戏。
一维数组数组是一组相同类型元素的集合,用来存储数据,请看数组的创建,int类型指定存放数据的类型,arr1表示数组名,10表示数组内元素的个数,只能存放常量。当然其他类型的数组创建类似。
#includeint main() { int arr1[10]; return 0; }
对于C99标准以后,允许使用变量指定数组元素的个数,请看代码。
#includeint main() { int n = 10; int arr2[n]; return 0; }
下面介绍一维数组的初始化,第一种就是完全初始化,请看代码。指定数组元素为5个,又确定5个元素,将这些数据存放到数组里面,这就是完全初始化。
int arr1[5] = { 1,2,3,4,5 };
第二种是不完全初始化,数据元素个数少于数组指定大小,默认将数据依次存放,在没有指定数据内容存放的位置补0,请看代码。
int arr1[10] = { 1,2,3,4 };
我们可以验证一下,打开VS的调试,在监视窗口中查看数组元素,请看下图,验证了后面默认补0。
当然,如果不进行初始化,那么数组里面存放的是随机值(根据不同编译器决定),请看下图。
第三种是根据指定元素内容进行创建并初始化,请看下面代码。指定char类型数组中存放指定的三个元素,那么数组就开辟三个位置(只要别越界,char数组可以存放int类型,根据ASCII码表)。
char arr2[] = { 'a',98,'c' };
字符数组比较特殊,也可以存字符串,请看下面代码,字符串结束标识是 ,如果用双引号引起来的字符串,默认后面跟着 ,而字符数组后面没有 ,所以编译器把它当做字符串处理的时候会在内存中去寻找 ,直到找到为止,所以输出结果如下。
#include#include int main() { char ch1[] = "abcd"; char ch2[] = {'a','b','c','d'}; char ch3[4] = "abcd"; printf("%dn", sizeof(ch1));//5 printf("%dn", sizeof(ch2));//4 printf("%dn", sizeof(ch3));//4 printf("%dn", strlen(ch1));//4 printf("%dn", strlen(ch2));//65 printf("%dn", strlen(ch3));//33 return 0; }
对于一维数组的使用,我们使用的是之前介绍过的一个操作符: [ ] ,下标引用操作符。它其实就数组访问的操作符。数组是使用下标来访问的,下标是从0开始。数组的大小可以通过 sizeof 计算得到。
请看下面代码,我们使用循环对数组进行初始化,然后又用循环遍历了数组。
#includeint main() { //数组初始化 int arr[100] = { 0 }; int sz = sizeof(arr) / sizeof(arr[0]); for (int i = 0; i < sz; i++) { arr[i] = i + 1; } for (int i = 0; i < sz; i++) { printf("%d ", arr[i]); } return 0; }
下面我们来介绍一下数组在内存中的存储,请看代码,使用%p可以打印地址(十六进制)。
#includeint main() { int arr[5] = { 1,2,3,4,5 }; //连续存放 for (int i = 0; i < 5; i++) { printf("arr[%d] = %pn",i,&arr[i]); } return 0; }
最后输出结果如下,很明显每个元素的地址之间差4个字节,也刚好是一个 int 类型所占的字节,所以我们能说明,数组在内存中是连续存储的。
arr[0] = 000000A28135F7F8
arr[1] = 000000A28135F7FC
arr[2] = 000000A28135F800
arr[3] = 000000A28135F804
arr[4] = 000000A28135F808
同样,我们使用指针去遍历数组,在遍历之前,我们看一个 指针+i 的操作,下面代码用一个指针类型数据接收数组名,此时数组名代表数组首元素的地址,我们打印出 指针+i 的地址与数组元素的地址对比一下,请看代码。
#includeint main() { int arr[5] = { 1,2,3,4,5 }; int* p = arr; for (int i = 0; i < 5; i++) { //p+1跳过四个字节 printf("arr[%d] = %p === %pn", i, &arr[i],p+i); } return 0; }
输出结果如下,我们发现 指针 + i 刚好跳过四个字节,也就是一个 int 数据类型所占的大小(这个具体会在后面指针的章节讲解),也就是说可以用指针找到每个数组元素的地址,当然,完全是因为数组在内存中连续存储,指针才能有这样的效果。
arr[0] = 000000F50D8FF618 === 000000F50D8FF618
arr[1] = 000000F50D8FF61C === 000000F50D8FF61C
arr[2] = 000000F50D8FF620 === 000000F50D8FF620
arr[3] = 000000F50D8FF624 === 000000F50D8FF624
arr[4] = 000000F50D8FF628 === 000000F50D8FF628
既然可以用指针找到每个数组元素的地址,那么我们就可以用指针遍历数组(找到每个数组元素地址,对其进行解引用操作),请看代码。
int main() {
int arr[5] = { 1,2,3,4,5 };
int* p = arr;
for (int i = 0; i < 5; i++) {
printf("%d ",*(p+i));
}
return 0;
}
下面是介绍一下数组名到底是什么,数组名在大多数情况下表示首元素的地址,只有两种情况下不是,第一种是 sizeof(arr) ,计算的不是指针类型变量的大小,计算的是整个数组所占的字节数(注意,在函数传参传的是数组名,此时数组名已经作为首元素地址传到函数里, 也就是变成指针变量了,所以在函数里使用 sizeof(arr) 会出逻辑上的问题,不要搞混了!!!),第二种情况是 &arr ,其实输出这个地址,发现跟首元素地址一样,但请看下面代码,此时当 指针 + 1 时,arr指针跳过4个字节,也就是一个整形变量大小,&arr跳过的是20个字节,也就是整个数组所占的大小,这就是区别。
#include冒泡排序int main() { int arr[] = { 1,2,3,4,5 }; printf("%pn",arr); //000000F2162FF988 printf("%pn",&arr[0]);//000000F2162FF988 printf("%pn",&arr); //000000F2162FF988 printf("%pn",arr+1); //000000F2162FF98C printf("%pn",&arr+1); //000000F2162FF99C return 0; }
冒泡排序(Bubble Sort)是一种简单的排序算法,假设数组想由小到大排序,那么冒泡排序将依次比较两个相邻的元素,如果此时左边元素大于右边元素,那么就交换这两个数组元素,进行第一遍排序后,数组最大的元素将会位于数组的最右边,然后执行下一轮排序,直到数组变为有序。请看代码,外层循环表示排序了几轮,当然假设数组三个元素,每一轮最大的到数组最后边,那么只需要排序两轮即可,所以我们算法外层只需要 size - 1 轮,内层循环是判断是否进行交换(先不管换不换),假设还是数组三个元素,第一轮要判断交换两次,当第二轮排序时,只需要判断交换一轮,所以每次判断交换几轮,是根据外面 i 的值来决定的,即为 size - 1 - i ,这样既少去了多余的判断,又保证数组不会越界。
void bubble_sort(int arr[],int size){
for(int i = 0; i < size - 1; i++){
for(int j = 0; j < size - 1 - i; j++){
if(arr[j] > arr[j+1]){
int temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
}
二维数组
二维数组的本质是一个一维数组,它的一维数组里面的所有元素都是数组类型,这就是二维数组,我们通常将二维数组与线性代数中的矩阵联系起来,想象二维数组是一个矩阵, 具有行和列。请看二维数组的初始化。下面是二维数组不完全初始化,创建一个三行五列的二维数组(这么叫是为了方便),根据上述所说与自己的推荐,可以猜想前五个元素在一行(第一个数组里面),6在第二行(第二个数组里面),第三行(第三个数组)全存放0。
int arr[3][5] = {1,2,3,4,5,6};
我们使用VS进行调试监视看一下,请看下图,跟我们猜想完全一致。
当然我们也可以这样初始化,请看代码,指定了每个数组的元素,如果还有位置,那么在该位置补0,这是数组的另一种不完全初始化。
int arr[3][5] = { {1,2},{3,4},{5,6} };
第三种是省略行的初始化,也就是说二维数组第一个位置可以不写,根据后面的元素以及指定的列(第一个数组的大小)来进行初始化。
int arr[][5] = {1,2,3,4,5,6};
下面我们来遍历一下二维数组,首先遍历行,也就是计算出二维数组里面有几个一维数组,然后遍历列,计算每一个一维数组里面有几个元素,打印出每一个元素(每遍历完一个一维数组后进行换行),这样就完成遍历了。
#includeint main() { int arr[3][5] = { 1,2,3,4,5,6,7,8,9 }; for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++) { for (int j = 0; j < sizeof(arr[0]) / sizeof(arr[0][0]); j++) { printf("%d ",arr[i][j]); } printf("n"); } return 0; }
这样遍历出来就是有行有列的矩阵了,通常二维数组与矩阵进行联系,请看下面运行结果。
下面介绍一个二维数组在内存中存储的情况,请看下面代码,打印出所有元素中的地址。
#includeint main() { int arr[3][5] = { 1,2,3,4,5,6,7,8,9 }; for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++) { for (int j = 0; j < sizeof(arr[0]) / sizeof(arr[0][0]); j++) { printf("arr[%d][%d] == %pn", i,j,&arr[i][j]); } } return 0; }
运行结果如下,不难看出,每个元素之间相差4个字节,即一个整形变量大小,也就是说,二维数组在内存中是连续存储的。这样就可以说明,为什么二维数组初始化的时候可以省略行,而不能省略列,试想一下,如果省略列,那么一片连续存储的空间,并不知道里面每个一维数组里面存放多少元素,也就是无法进行开辟,而省略行不省略列,指定了每一个一维数组里面存放多少元素,根据后面给定的元素来进行存放,这样是可以开辟空间的。
三子棋arr[0][0] == 000000FECDEFF7A8
arr[0][1] == 000000FECDEFF7AC
arr[0][2] == 000000FECDEFF7B0
arr[0][3] == 000000FECDEFF7B4
arr[0][4] == 000000FECDEFF7B8
arr[1][0] == 000000FECDEFF7BC
arr[1][1] == 000000FECDEFF7C0
arr[1][2] == 000000FECDEFF7C4
arr[1][3] == 000000FECDEFF7C8
arr[1][4] == 000000FECDEFF7CC
arr[2][0] == 000000FECDEFF7D0
arr[2][1] == 000000FECDEFF7D4
arr[2][2] == 000000FECDEFF7D8
arr[2][3] == 000000FECDEFF7DC
arr[2][4] == 000000FECDEFF7E0
C语言详解之三子棋
扫雷游戏C语言详解之扫雷游戏
结语本篇文章对数组进行了一个详细介绍,下一章将会详细介绍操作符,请大家多多点赞关注!!!



