二维数组
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语言】万字教学,带你分步实现扫雷游戏(内含递归函数解析),剑指扫雷,一篇足矣
总结
今天的内容就到此结束啦,数组中我见过以及遇到过的所有知识以及易错点都在里面了。
如果你有任何疑问欢迎在评论区指出或者私信我,咱们下次再见啦!
新人创作不易,如果今天的内容对你有所帮助的话,请点个三连再走吧,你们的支持就是我更新的动力,再次感谢大家的支持!!!