本章重点在于学习一维数组以及二维数组的使用
- 一维数组的创建和初始化
- 一维数组的使用
- 一维数组在内存中的存储
- 二维数组的创建和初始化
- 二维数组的使用
- 二维数组在内存中的存储
- 数组越界
- 数组作为函数参数
1. 一维数组的创建和初始化
1.1 数组的创建
数组是一组相同类型元素的集合。 数组的创建方式:
type_t arr_name [const_n];
//type_t 是指数组的元素类型
//const_n 是一个常量表达式,用来指定数组的大小
数组创建的实例:
//代码1
int arr1[10];
//代码2
int count = 10;
int arr2[count];//数组时候可以正常创建?
//代码3
char arr3[10];
float arr4[1];
double arr5[20];
注:数组创建,[ ]中要给一个常量才可以,不能使用变量。
1.2 数组的初始化
数组的初始化是指,在创建数组的同时给数组的内容一些合理初始值(初始化)。
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'};
char arr5[] = {'a','b','c'};
char arr6[] = "abcdef";
我们可以发现在数组创建并初始化中,将数组内所有的元素都做了初始化的操作,这种我们一般叫做完全初始化,相反只初始化了一些元素,我们则叫他为不完全初始化。
详细举例:
完全初始化:
不完全初始化:
没有做初始化的操作:
我们可以看到即便是没有做到完全初始化,只要初始了数组的一个元素,编译器就会自动的帮我们把剩余的元素都初始化成0。当然,如果一个元素都没有做初始化的话,那么数组中所有元素的值都将是随机值。
数组在创建的时候如果想不指定数组的确定的大小就得初始化。数组的元素个数根据初始化的内容来确定。 但是对于下面的代码要区分,内存中如何分配。
可以看到跟我们所分析的情况基本一致。
1.3 一维数组的使用
对于数组的使用我们之前介绍了一个操作符:[],下标引用操作符。它其实就数组访问的操作符。 我们来看代码:
总结:
- 数组是使用下标来访问的,下标是从0开始。
- 数组的大小可以通过计算得到。
//确定数组内的元素个数
int arr[10] = {0};
int sz = sizeof(arr)/sizeof(arr[0]);
1.4 一维数组在内存中的存储
接下来我们探讨数组在内存中的存储。 看代码:
仔细观察输出的结果,我们知道,随着数组下标的增长,元素的地址,也在有规律的递增。 由此可以得出结论:
数组在内存中是连续存放的,并且地址是从低地址往高地址存储。
2. 二维数组的创建和初始化
通过前面我们了解了
一维数组
就是由多个相同类型元素组成的集合
,当你理解一维数组以后,那么二维数组
就很好理解了,就是由多个相同类型的数组并且数组大小一致组成的。
2.1 二维数组的创建
//数组创建
int arr[3][4];
char arr[3][5];
double arr[2][4];
上面二维数组的创建我们可以总结出来,我们可以省略由多少个一维数组组成二维数组,但是我们不能省略单个一维数组的大小。
2.2 二维数组的初始化
我们先来看看完全初始化的二维数组
看到这个一维数组也用花括号括起来,我也在思考这个括号能不能取消掉,跟一维数组一样的创建形式,接下来我们看看
看起来好像没什么问题,但是真的是这样嘛,我们再看看。
我们从这张图是不是能总结出什么,当我们一维数组不需要括号的时候,二维数组内的元素将有序的依次的从第一个一维数组填满以后再填后面的一维数组,直到元素全部填完。如果一维数组有括号的话,我们可以针对性的某些一维数组存储一些值,但不一定需要存储完全。
其实我们还需要注意一个细节接下来我们继续看一下。
我们可以看到,即便我们对二维数组进行了一个类似完全初始化,但是依旧出错了,从这里我们基本可以确认,二维数组并不能像
一维数组一样初始化可以省略数组大小而根据数组内容决定
,二维数组也有自己的规则,严格执行必须要确定一维数组的大小
。
2.3 二维数组的使用
遍历二维数组
2.4 二维数组在内存中的存储
在前面,我们观察过了一维数组在内存中的存储情况,现在我们可以尝试用同样的方法去观察二维数组在内存中的存储情况。
从以上图中我们可以观察到,二维数组在内存中的存储跟一维数组在内存中的
存储方式基本一致
,都是连续存储的,并且地址也是由低地址到高地址。
也就是说只要我们知道了二维数组中第一个一维数组的第一个元素,我们就能跟前面的一维数组一样,顺藤摸瓜找到二维数组中所有的元素,都可以将它遍历出来。
到此为止,我们可以在简单的总结一下,一维数组其实就是多个相同类型元素的数组,那么二维数组我们是不是可以理解,在确定数组之间元素个数相同以及类型相同的条件下,
二维数组就是【一维数组】的数组
。
3. 数组越界
数组的下标是由范围限制的。
数组下标规定是从0开始的,如果数组有n个元素,那么最后一个元素的下标就是n-1.
所以数组的下标如果小于0,或者大于n-1,就是数组越界访问了,超出了数组合法空间的访问。
C语言本身是不做对数组下标的越界检查,编译器也不一定报错,但是编译器不报错,并不意味着程序就是正确的。
所以程序员写代码时,最好自己做越界的检查。
输出结果:
当然二维数组也的存在数组越界的情况。
4. 数组作为函数参数
4.1 冒泡排序函数的错误设计
#include <stdio.h>
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 - 1 - i; j++)
{
if (arr[j] < arr[j + 1])
{
//三杯水交换
int tmp = 0;
tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
}
}
}
}
int main()
{
int arr[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int sz = sizeof(arr) / sizeof(arr[0]);
int i = 0;
bubble_sort(arr);
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
输出结果:
可以看出来并没有什么变化,我们需要进行分析找出错误的地方。下面进行调试
从这里我们基本确定了在sz上面出现了问题,那我们不禁思考为什么会出现这样的问题呢?
为什么说形参接受的arr是一个指针变量了呢?
4.2 数组名是什么?
先给出结论,数组名通常情况下都是首元素的地址。抱着这个问题,下面我们可以验证一下。
从这里我们能看出arr数组名确实是首元素的地址。
但是有两个例外
。
- sizeof(数组名),数组名单独放在sizeof()内部,这里的数组名表示整个数组,计算的是整个数组的大小。
- &数组名,这里的数组名也表示整个数组,这里取出来的是整个数组的地址。
除此之外遇到的数组名都表示数组首元素的地址。
情况一:
在sizeof里面的arr代表的是整个数组,那么得到的也就是整个数组的字节。
情况二:
这里的arr加上&并不是代表的是首元素的地址,而是整个数组的地址,当&arr + 1的时候,相当于跨越了一整个数组大小的字节。
4.3 冒泡排序函数的正确设计
难道我们每次需要冒泡排序的时候,都要手搓一遍嘛?既然我们学习了函数,那能不能给它封装起来呢。
#include <stdio.h>
//冒泡排序函数封装
void bubble_sort(int i, int sz, int arr[])
{
//总共需要比较的趟数
for (i = 0; i < sz - 1; i++)
{
int j = 0;
//一趟需要比较的次数
for (j = 0; j < sz - 1 - i; j++)
{
if (arr[j] < arr[j + 1])
{
//三杯水交换
int tmp = 0;
tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
}
}
}
}
int main()
{
int arr[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int sz = sizeof(arr) / sizeof(arr[0]);
int i = 0;
bubble_sort(i, sz, arr);
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
输出结果:
到此本章的内容就全部结束了,如果你认为对你有帮助的话,不妨留下一个赞吧~