勤时当勉励 岁月不待人
C/C++ 游戏开发
数组的使用
- 前言
- 一维数组
- 1.一维数组的定义
- 数组的分类
- 2.数组的初始化
- 第一种越界情况
- 3.数组的使用
- 数组的下标:
- 第二种越界情况
- 4.数组在内存中的存储
- 二维数组
- 1.二维数组的创建
- 2.二维数组的初始化
- 3.二维数组的使用
- 4.二维数组在内存中的存储
- 数组作为函数参数的实际应用
- 1.冒泡排序
- 错误设计
- 数组名是什么?
- 小小总结一下
- 正确做法
- 2.三子棋游戏
- 3.扫雷游戏
- 总结
前言
Hello,这里是君兮_,今天为大家带来的是在C语言对数组的详解,废话不多说我们直接开始吧。
一维数组
1.一维数组的定义
- C语言中给了数组的定义:一组相同类型元素的集合
- 数组的创建:
type_t arr_name [const_n];
type_t 是指数组的元素类型
const_n 是一个常量表达式,用来指定数组的大小
我们有时可能会看到这种数组:
int count = 10;
int arr2[count];//数组时候可以正常创建吗?
- 注:数组创建,在C99标准之前, [ ] 中要给一个常量才可以,不能使用变量。在C99标准支持了变长数组的概念,数组的大小可以使用变量指定,但是数组不能初始化。
- 简单来说就是要看你的编译器,为了防止写出bug,我建议一般创建数组是还是不要这么写了。
数组的分类
- 根据元素类型的不同,数组大致可以分为三类:
- 整型数组
int arr1[10]
- 字符数组
char arr2[10]
- 浮点型数组
double arr3[10]
2.数组的初始化
- 数组的初始化是指,在创建数组的同时给数组的内容一些合理初始值(初始化)。
- 话不多说,直接看代码:
int arr1[10] = {1,2,3,4,5,6,7,8,9,10};//完全初始化
int arr2[10] = { 1,2,3 };//不完全初始化,剩余的元素默认都是0
int arr3[10] = { 0 };//不完全初始化,剩余的元素默认都是0
int arr4[] = { 0 };//省略数组的大小,数组必须初始化,数组的大小是根据初始化的内容来确定
char arr5[] = {'a','b','c'};
char arr6[] = "abcdef";
char arr7[]={"abcdef"}//与arr6一个意思,arr6是它的缩写
- 数组在创建的时候如果想不指定数组的确定的大小就得初始化。数组的元素个数根据初始化的内容来确定。
第一种越界情况
但是对于下面的代码要区分,内存中如何分配
int main()
{
char arr1[] = {"abc"};
char arr2[] = {'a','b','c'};
printf("%s\n",arr1);
printf("%s", arr2);
}
- 代码结果如图:
- 为啥会出现这种情况?
注意:当我们以%s形式打印上面数组内容时,遇到’\0’才会停止。第一个数组中的“abc”是一个字符串,在后面有一个我们看不见的’\0’,而第二个数组中是三个字符‘a’ ‘b’ ‘c’,后面是没有’\0’的,此时我想告诉你的是,你的第二个数组越界了!!此时这个数组后面存放的是随机值,打印出来就是随机的乱码,而直至它越界访问的地方出现0即’\0’,才会停止打印。 - 证明一下:
- 说明我们上面的说法是没错的。
3.数组的使用
对于数组的使用我们之前介绍了一个操作符: [] ,下标引用操作符。它其实就数组访问的操作符。
数组的下标:
1.数组是使用下标来访问的,下标是从0开始。
2. 数组的大小可以通过计算得到。
int arr[10];
int sz = sizeof(arr)/sizeof(arr[0]);//用整个数组的大小除以第一个元素的大小得到的就是数组中元素的个数
第二种越界情况
-
数组的下标是有范围限制的。
-
数组的下规定是从0开始的,如果数组有n个元素,最后一个元素的下标就是n-1。
-
所以数组的下标如果小于0,或者大于n-1,就是数组越界访问了,超出了数组合法空间的访问。
-
C语言本身是不做数组下标的越界检查,编译器也不一定报错,但是编译器不报错,并不意味着程序就是正确的,
-
代码如下:
int main()
{
int arr[10] = { 0 };//数组的不完全初始化
//计算数组的元素个数
int sz = sizeof(arr) / sizeof(arr[0]);
//对数组内容赋值,数组是使用下标来访问的,下标从0开始
int i = 0;
for (i = 0; i < 10; i++)//这里写10,可以吗?
{
arr[i] = i;
}
//输出数组的内容
for (i = 0; i < 10; ++i)
{
printf("%d ", arr[i]);
}
return 0;
}
- 这样改可以吗?
for (i = 0; i <=10; i++)
- 改完结果:
- 这里连编译都走不过去,为什么?
- 越界访问!
- 注意:
- 1 当你写完上面的代码后,我想问问你,你的数组初始化时有几个元素呢?
int arr[10] = { 0 };//最多10个
- 2 数组的下标是从0开始的,也就是说最后一个元素的下标应该是9,即arr[9]。
- 3 当你把i的条件改为i<=10后,你会惊奇的发现,你的下标竟然能出现arr[10]!你数组里根本没有这个元素,这不是又越界吗了?
- 总结:
- 切记切记,上面两种越界情况是经常出现的,尤其是第二种。
- 我们经常在循环时因条件设置有误而导致越界,当代码多了之后,这种错误是非常难以发现的。我费这么大篇幅讲这两种越界情况就是为了提醒你们在以后使用数组的时候一定要小心谨慎,要多考虑考虑是否会出现越界情况,避免一个bug要找半天(博主的切身经历)
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] = %p\n", i, &arr[i]);//打印每个元素的地址
}
return 0;
}
- 由于该数组为int型数组,每个元素占四个字节,即每一元素地址对应+4,实际还是从低地址到高地址连续存放
- 仔细观察输出的结果,我们知道,随着数组下标的增长,元素的地址,也在有规律的递增。
- 由此可以得出结论:数组在内存中是连续存放的。
二维数组
1.二维数组的创建
int arr[3][4];
char arr[3][5];
double arr[2][4];
- 就像棋盘的行与列一样,我们也可以把二维数组理解为有几行,而每一行又能放多少元素,即多少列。
2.二维数组的初始化
//数组初始化
int arr[3][4] = {1,2,3,4};
int arr[3][4] = {{1,2},{4,5}};
int arr[][4] = {{2,3},{4,5}};//二维数组如果有初始化,行可以省略,列不能省略
- 和一维数组的初始化差不多,但是需要注意的是行的大小在初始化时可以省略,但是列的则不行。
- 就像棋盘,你可以不知道这个棋盘有几行,但是你必须得知道一行中会有几个元素,,否则你怎么确定下一行该从哪开始呢?
3.二维数组的使用
- 与一维数组类似,这里不做过多展开,举例说明
int main()
{
int arr[4][5] = { {1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7},{5,6,7,8,9} };
//printf("%d\n", arr[2][3]);
int i = 0;
//行号
for (i = 0; i < 4; i++)
{
//每一行有五列
int j = 0;
for (j = 0; j < 5; j++)
{
printf("%d ", arr[i][j]);//0 1 2 3 4
}
printf("\n");
}
return 0;
}
4.二维数组在内存中的存储
咱们上面拿棋盘比喻二维数组是为了方便大家理解,当然你平时使用时就把它当棋盘想其实也没啥毛病,下面我们来讲讲二维数组在内存中真正的存储方式。
- 像一维数组一样,这里我们尝试打印一下二维数组的每个元素
- 代码如下:
int main()
{
int arr[4][5] = { 0 };
int i = 0;
//行号
for (i = 0; i < 4; i++)
{
int j = 0;
for (j = 0; j < 5; j++)
{
printf("&arr[%d][%d] = %p\n",i,j, &arr[i][j]);
}
}
return 0;
}
- 结果如下:
- 和一维数组类似,但是又和我们刚才所说的棋盘不太一样
- 来画图理解一下:
- 也就是说,二维数组在内存中也是连续存储的。
另外,注意:
- 二维数组的行与列也是有可能发生越界的,在写代码时千万要注意。
数组作为函数参数的实际应用
1.冒泡排序
- 什么是冒泡排序?
- 把一个无序数组的元素从左向右比较,如果左边元素比右边的元素大,就交换这两个元素的位置,继续与下一个右边元素比较直至把该无序数组排成一个元素由小到大的数组(也就是升序数组)。因此冒泡排序也叫升序排序法。
错误设计
void bubble_sort(int arr[])
{
int sz = sizeof(arr) / sizeof(arr[0]);//这样对吗?
int i = 0;
for (i = 0; i < sz - 1; i++)
{
int j = 0;
for (j = 0; j < sz - i - 1; j++)
{
if (arr[j] > arr[j + 1])
{
int tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
}
}
}
}
int main()
{
int arr[] = { 3,1,7,5,8,9,0,2,4,6 };
bubble_sort(arr);//是否可以正常排序?
int i = 0;
for (i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
{
printf("%d ", arr[i]);
}
return 0;
}
- 以上代码能实现我们想要的效果吗?
- 不妨试试:
- 我们从结果上可以发现它只交换了一次元素就停止了,这是为什么?
- 别急,想明白上面的代码,我们得先看懂下面的这段代码。
数组名是什么?
int main()
{
int arr[10] = { 1,2,3,4,5,6 };
printf("%p\n", arr);
printf("%p\n", arr+1);
printf("%p\n", &arr[0]);
printf("%p\n", &arr[0]+1);
printf("%p\n", &arr);//数组的地址
printf("%p\n", &arr+1);//+1,跳过整个数组
printf("%d\n", sizeof(arr));
return 0;
}
-
你现在能分别告诉我上面这些打印的数组的含义吗?
-
这里就不卖关子了,我们来看下结果:
-
先分析上面四行代码的结果
-
我们可以看到,上面四行代码中打印出来地址的结果竟然一模一样,这是为什么呢?
-
上面两行可能不太好理解,但是中间的两行我们知道呀。
printf("%p\n", &arr[0]);//打印该数组首元素地址
printf("%p\n", &arr[0]+1);//首元素地址+1
- 也就是说,在这里的arr表示的是首元素的地址!!!
- 那这两行呢?
printf("%p\n", &arr);
printf("%p\n", &arr+1);
- 取arr的地址,那么取的到底是整个数组的地址还是首元素的地址呢?
- 我们来分析一下:
- 我们发现&arr与&arr+1只有后两位有差异,那么它们差多少呢?
- 注意咯,此时在我们屏幕上打印的地址是16进制的,可千万不敢把它们的差值当成60-38了。
正确的算法:
6 * 16^ 1-3 * 16^ 1- 8 * 16 ^0=40
由初始化可知这是一个有10个元素的整型数组,也就是说&arr+1跳过了整个数组。
- 那么我们就知道了,&arr其实表示的是该数组的地址。
- 同理,下面的sizeof(arr)=40计算整个数组的大小也可知此时也是整个数组的地址
小小总结一下
- 1. sizeof(数组名),计算整个数组的大小,sizeof内部单独放一个数组名,数组名表示整个数组。
- 2. &数组名,取出的是数组的地址。&数组名,数组名表示整个数组。
- 除此1,2两种情况之外,所有的数组名都表示数组首元素的地址。
- 弄明白数组名的含义,我们再回到我们的错误设计中,再来想想,咱们到底错在哪里呢?
int sz = sizeof(arr) / sizeof(arr[0])
- 把这个放进冒泡函数中,真的对吗?
- 根据我们上面的分析,我们可以知道此时传进冒泡函数的arr只是首元素的地址,那当我们用sizeof计算该数组中元素时,结果只能是1。因此在冒泡函数眼里,该数组只有一个元素需要交换,因此就出现了上面的那一幕,只改变了1,3的顺序。
正确做法
当数组传参的时候,实际上只是把数组的首元素的地址传递过去了。
所以即使在函数参数部分写成数组的形式: int arr[] 表示的依然是一个指针: int *arr 。
那么,函数内部的 sizeof(arr) 结果是4。
如果第一种方法错了,该怎么设计?
- 代码如下:
void bubble_sort(int arr[], int sz)//参数接收数组元素个数
{
int i = 0;
for (i = 0; i < sz - 1; i++)
{
int j = 0;
for (j = 0; j < sz - i - 1; j++)
{
if (arr[j] > arr[j + 1])
{
int tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
}
}
}
}
int main()
{
int i;
int arr[] = { 3,1,7,5,8,9,0,2,4,6 };
int sz = sizeof(arr) / sizeof(arr[0]);//直接把元素个数传进去
bubble_sort(arr, sz);
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
- 成功!!!
2.三子棋游戏
- 写过一篇相关博客具体介绍该怎么实现,链接如下:
【C语言】三子棋详解(包教包会的那种)
3.扫雷游戏
- 也有一篇相关博客具体介绍,感兴趣的可以去看看!
- 链接如下:
【C语言】万字教学,带你分步实现扫雷游戏(内含递归函数解析),剑指扫雷,一篇足矣
总结
- 今天的内容就到此结束啦,数组中我见过以及遇到过的所有知识以及易错点都在里面了。
- 如果你有任何疑问欢迎在评论区指出或者私信我,咱们下次再见啦!
新人创作不易,如果今天的内容对你有所帮助的话,请点个三连再走吧,你们的支持就是我更新的动力,再次感谢大家的支持!!!