5.二维数组的使用
二维数组的使用也是通过下标的方式
现创建一个三行四列的二维数组:int arr[3][4];
我们可以想象它是这样存放的
![](https://i-blog.csdnimg.cn/blog_migrate/bb88476f123838ecf482c6b91e8e3ee5.png)
-- -- -- -- -- -- -- -- -- -- --
打印数组中所有元素
#include <stdio.h>
int main()
{
int arr[4][4] = { {1,2,3},{4,5} };
//用两个for循环完成对每一个下标的寻找——遍历
int i = 0;
for (i = 0; i < 4; i++)
{
int j = 0;
for (j = 0; j < 4; j++)
{
printf("%d ", arr[i][j]);
}
printf("\n");//打印完一行就换行
}
return 0;
}
运行结果:
1 2 3 0
4 5 0 0
0 0 0 0
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
6.二维数组在内存中的存储
先来看看二维数组的各个元素在内存中是怎么存放的
#include <stdio.h>
int main()
{
int arr[4][4] = { {1,2,3},{4,5} };
int i = 0;
for (i = 0; i < 4; i++)
{
int j = 0;
for (j = 0; j < 4; j++)
{
printf("&arr[%d][%d]=%p\n",i,j,& arr[i][j]);
}
}
return 0;
}
运行结果:
&arr[0][0]=000000896D2FF4C0
&arr[0][1]=000000896D2FF4C4
&arr[0][2]=000000896D2FF4C8
&arr[0][3]=000000896D2FF4CC
&arr[1][0]=000000896D2FF4D0
&arr[1][1]=000000896D2FF4D4
&arr[1][2]=000000896D2FF4D8
&arr[1][3]=000000896D2FF4DC
&arr[2][0]=000000896D2FF4E0
&arr[2][1]=000000896D2FF4E4
&arr[2][2]=000000896D2FF4E8
&arr[2][3]=000000896D2FF4EC
&arr[3][0]=000000896D2FF4F0
&arr[3][1]=000000896D2FF4F4
&arr[3][2]=000000896D2FF4F8
&arr[3][3]=000000896D2FF4FC
观察发现:表示地址的十六进制数之间都差4(因为是整型数组),说明二维数组在内存中的存放并不是这样的:
![](https://i-blog.csdnimg.cn/blog_migrate/bb88476f123838ecf482c6b91e8e3ee5.png)
而是像一维数组那样是连续存放的:
![](https://i-blog.csdnimg.cn/blog_migrate/3812b1f1bdd75a3aab43ba400302d55a.png)
把arr[i][j]看成:一个叫做arr[i]的一维数组,长度是j
其中arr[i]是数组名,j是下标
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
7.数组作为函数参数
往往我们在写代码的时候,会将数组作为参数传给函数,比如我们要实现一个冒泡排序函数,将一个整型数组进行排序
冒泡排序(升序)
基本排序方法:那两个数进行比较,大的数在前就交换位置放在后面(相邻)
![](https://i-blog.csdnimg.cn/blog_migrate/ee0f9e85ba454cd740311265df2c6984.png)
10个元素,最多排9趟
第一次9对元素进行比较
第二次8对,第三次7对...
下面开始实现
#include <stdio.h>
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 - 1 - i; j++)//用sz-1-i的方式实现,一趟冒泡排序中“交换的最大次数会越来越少”
{
if (arr[j] > arr[j + 1])
{
int tmp = arr[j];//前后交换
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
}
}
}
}
int main()
{
int arr[] = { 10,9,8,7,6,5,4,3,2,1 };
int i = 0;
int sz = sizeof(arr) / sizeof(arr[0]);//之前已经讲过为什么要在main函数里面算sz
//因为数组名是首元素地址,计算后大小是4/8
//对arr进行排序,排成升序
//arr是数组,我们对arr进行传参时,实际上传递过去的是数组arr的首元素地址:&arr[0]
bubble_sort(arr, sz);
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
运行结果:1 2 3 4 5 6 7 8 9 10
-- -- -- -- -- -- -- -- -- --
数组名
数组名是什么
数组名真的是首元素地址而不是别的什么东西吗,可以写一段代码来看看
#include <stdio.h>
int main()
{
int arr[] = { 1,2,3,4,5 };
printf("%p\n", arr);//数组名
printf("%p\n", &arr[0]);//把数组中下标为0的元素的地址取出来,即首元素地址
printf("%d\n", *arr);//对数组名解引用
printf("%d\n", arr[0]);//打印一下首元素
return 0;
}
运行结果(例):
000000B2C4B2F548
000000B2C4B2F548
1
1
-- -- -- -- -- -- -- -- -- --
结论
数组名是首元素地址
但是有两个例外
sizeof(数组名)——数组名代表整个数组,sizeof(数组名)计算的是整个数组的大小,单位是字节
&数组名,数组代表整个数组,&数组名,取出的是整个数组的地址
#include <stdio.h>
int main()
{
int arr[] = { 1,2,3,4,5,6,7 };
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);
return 0;
}
运行结果(例):
00000045BC4FFAD8
00000045BC4FFADC
00000045BC4FFAD8
00000045BC4FFADC
00000045BC4FFAD8
00000045BC4FFAF4
由此可见,&数组名和首元素地址的值是一样的,但是意义却完全不同,&数组名+1跳过一整个数组,数组名+1跳过一个元素
-- -- -- -- -- -- -- -- -- --
数组越界
数组的下标是有范围限制的
数组的下标规定是从0开始的,如果数组有n个元素,最后一个元素的下标就i是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 < 12; i++)
{
printf("%d\n", arr[i]);
}
return 0;
}
运行结果:
1
2
3
4
5
6
7
8
9
10
-858993460
-858993460
本长老的编译器(vs2022)是没有报错哒,但是有一个警告:
严重性代码说明项目文件行禁止显示状态
警告C6201索引“11”超出了“0”至“9”的有效范围(对于可能在堆栈中分配的缓冲区“arr”
说明确实可以做到越界访问而不报错,但是编译器也会提示你,这里可能出错了
所以我们应当自己做好越界的检查