抽丝剥茧C语言(中阶)数组
数组
- 导语
- 1. 一维数组。
-
- 1.1 数组的创建
- 1.2 数组的初始化
- 1.3 一维数组的使用
- 1.4 一维数组在内存中的存储
- 2. 二维数组
-
- 2.1 二维数组的创建
- 2.2 二维数组的初始化
- 2.3 二维数组的使用
- 2.4 二维数组在内存中的存储
- 3. 数组越界
- 4. 数组作为函数参数
-
- 4.1 冒泡排序函数的错误设计
- 4.2 数组名是什么?
- 4.3 冒泡排序函数的正确设计
- 本篇完
导语
本章会详细的讲解数组,以前因为数组的困惑会迎刃而解。
数组的作用是储存大量元素,不用不停的创建变量。
1. 一维数组。
1.1 数组的创建
数组是一组相同类型元素的集合。
数组的创建方式:
type_t arr_name [const_n];//type_t 是指数组的元素类型//const_n 是一个常量表达式,用来指定数组的大小
数组创建的实例:
//代码1int arr1[10];//代码2int count = 10;int arr2[count];//数组时候可以正常创建?//代码3char arr3[10];float arr4[1];double arr5[20];
你需要在你的数组里面放什么数据,你就创建什么类型的数组。
注:数组创建,在C99标准之前, [] 中要给一个常量才可以,不能使用变量。在C99标准支持了变长数组的概念。
我们知道,[ ]里面是常量才行,变量是不可以的,不过在C99便准中是允许像代码2那个样子的,如果配合scanf这个函数就可以自己确定数组的大小了,但是在代码运行的过程中,只能让这个变长数组确定一次大小。
这里我们就不做演示了,VS2022这个编译器不支持完整版C99的便准,大家有兴趣可以去GCC编译器试一下。
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";
在这里我们可以看到arr1里最初只初始化了三个值,1,2,3这三个数,可是我们定义他里面可以储存10个整形的元素,现在只初始化了三个元素元素,后面的7个元素呢?第二个数组没有定义里面有多少元素,这和个数据的具体大小又应该是多少?第三个设定是存入5个整形元素。第四个知识区别于第三个类型而已,但是98代表什么?第五个数组里面和第二个有些类似,里面是三个字符,那么第六个数组呢,是储存进了一个字符串,这又什么不同呢?
不要急,我们可以通过编译器来进行调试,通过调试能让抽象的代码在你的眼里变得逐渐容易理解起来。
我们VS2022编译器是有调试的,先按一下F10,然后点击上面的调试,鼠标移动到窗口,然后有一个叫做监视的地方,移动到监视然后有监视窗口那个地方,点击它。
然后我们在添加监视的地方输入你要监视的数组名就可以了,我们监视这里所有的数组,看看它们内部储存了什么:
通过调试我们看到,arr1当中储存的数据除了1,2,3后面都是0,也就是说我们未完全初始换的数组没被初始化的部分默认就是0。
arr2它初始化了4个整形元素,这里显示它里面也只储存了4个元素,这说明如果没有定义数组里面有几个元素那么就按照它初始化的元素个数来计算,如果是既没有定义数组里面有多少元素,也没给初始化元素,这样肯定是行不通的。
arr3看起来还是很正常的对吧,事实也确实如此。
arr4我么你明明初始化中有一个是98,为什么变成了b呢?这是因为ASCII码值的互相转换,字符的本质其实就是ASCII码值,98对应的就是小写字母b。
arr5这里我们和arr2类似,注意它们是单个字符,不是字符串,所以占用内存是3个字符空间大小。
arr6这里显示了里面有7个元素,因为我们之前了解过,字符串的末尾都会有隐藏的\0这个字符,它也会占一个空间,所以在定义数组里面有多少元素的时候不要把隐藏的\0给忘记了!
1.3 一维数组的使用
对于数组的使用我们之前介绍了一个操作符: [] ,下标引用操作符。它其实就数组访问的操作符。
我们来看代码:
#include int main(){ int arr[10] = {0};//数组的不完全初始化 int sz = sizeof(arr)/sizeof(arr[0]);//计算数组的元素个数 //对数组内容赋值,数组是使用下标来访问的,下标从0开始。所以: int i = 0;//做下标 for(i=0; i<10; i++)//给arr数组赋值1到10 { arr[i] = i; } //输出数组的内容 for(i=0; i<10; ++i){ printf("%d ", arr[i]);} return 0; }
输出结果是:
1 2 3 4 5 6 7 8 9 10
黑色的是我们刚才存入的元素,下面紫色的是我们数组的下标。
数组的下标永远都是从0开始,也就是说实际上下边的位置与储存元素的位置是相差1的。
我们之前还有过一个操作,就是计算数组的大小。
int arr[10];int sz = sizeof(arr)/sizeof(arr[0]);
sizeof是个操作符,它计算的是我们数据类型的长度,单位为字节。
我们把数组名放进去就等于计算了整个数组的长度,数组里面有10个元素,我们不用知道数组里面的元素是啥,只需要知道是10个整形即可,一个整形是4个字节,十个整形40个字节;那么,把arr[0]放进去等于计算什么呢,等于计算的是数组第一个元素的长度,第一个元素是个整形等于4个字节,也就是说等于总元素长度除以单个元素长度,等于40/4也就等于10,也就是十个元素。
1.4 一维数组在内存中的存储
接下来我们探讨数组在内存中的存储。
看代码:
#include 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;}
我们来看一下输出结果是什么(32位平台):
仔细观察输出的结果,我们知道,随着数组下标的增长,元素的地址,也在有规律的递增。
由此可以得出结论:数组在内存中是连续存放的。
2. 二维数组
2.1 二维数组的创建
//数组创建int arr[3][4];char arr[3][5];double arr[2][4];
这个和一维数组的格式一样,但是多了一个[]。
2.2 二维数组的初始化
//数组初始化int arr[3][4] = {1,2,3,4,5,6,7,8,9,10,11,12};int arr1[3][4] = {1,2,3,4};int arr2[3][4] = {{1,2},{4,5}};int arr3[][4] = {{2,3},{4,5}};//二维数组如果有初始化,行可以省略,列不能省略int arr4[][4] = {1,2,3,4,5,6};
我们先来看一下二维数组元素分布(arr举例)。
红色的数数组下标,蓝色的是数组的元素。
大家可以这么理解,二维数组的第一个[下标]里面是访问行,第二个[下标]访问的是列。
如你所见,想访问6这个元素就要arr[1][1]。
至于arr1,它一共有3行4列,但是只初始化了4个元素,那么它的分布如何呢?
让我们再次打开调试看一看:
我们可以发现,arr1第一个[]中,下标为0的那一行,初始化了1,2,3,4,
而其他行只初始化了一堆0,和一维数组一样也是未完全初始化的地方默认为0。
至于arr2我们发现,第一行初始化了1和2,后面的是默认值为0,0因为这一行需要4个元素,一共有4列,第二行初始化了4和5,然后0,0,最后一行全是0,这是因为那个花括号,花括号代表一行,就相当于集合一样。
arr3我们发现,它没有写列,这不要紧,我们有花括号,也就等于知道了有几行,所以是两行,四列,编译器会自己识别。
arr4发现花括号也没有了,不要紧,有列,你知道了一行有4个元素,这里有6个元素,那么可以凑够两行,第二行元素不够不要紧,0来凑数。
2.3 二维数组的使用
二维数组的使用也是通过下标的方式。
看代码:
#include int main(){int arr[3][4] = { 0 };int i = 0;for (i = 0; i < 3; i++){int j = 0;for (j = 0; j < 4; j++){arr[i][j] = i * 4 + j;//给二维数组赋值}}for (i = 0; i < 3; i++){int j = 0;for (j = 0; j < 4; j++){printf("%d ", arr[i][j]);//打印二维数组}}return 0;}
我们的输出是:
0 1 2 3 4 5 6 7 8 9 10 11
2.4 二维数组在内存中的存储
像一维数组一样,这里我们尝试打印二维数组的每个元素。
#include int main(){int arr[3][4];int i = 0;for (i = 0; i < 3; i++){int j = 0;for (j = 0; j < 4; j++){printf("&arr[%d][%d] = %p\n", i, j, &arr[i][j]);}}return 0;}
这是输出结果(32位平台):
还是每个地址相差4,我们这里也说明,其实它们的地址是连续存放的,即便是二维数组。
3. 数组越界
数组的下标是有范围限制的。
数组的下规定是从0开始的,如果数组有n个元素,最后一个元素的下标就是n-1。
所以数组的下标如果小于0,或者大于n-1,就是数组越界访问了,超出了数组合法空间的访问。
C语言本身是不做数组下标的越界检查,编译器也不一定报错,但是编译器不报错,并不意味着程序就是正确的,
所以程序员写代码时,最好自己做越界的检查。
我们用一维数组的代码举例:
#include int main(){ int arr[5] = {1,2,3,4,5}; int i = 0; for(i=0; i<=5; i++) { printf("%d\n", arr[i]);//当i等于5的时候,越界访问了 } return 0; }
代码运行结果:
最后我们打印了一串非常奇怪的数字,这就是数组越界了。
你看,你数组前后的位置,外面有啥我们根本不知道。
不仅仅是一维数组,二位数组也会越界的,比如说:
#include int main(){int arr[3][4] = { 1,2,3,4,5,6,7,8,9,10,11,12 };printf("%d\n", arr[0][5]);printf("%d\n", arr[2][5]);return 0;}
我们的输出结果是:
第一个我们明明想打印的是第一行的元素,结果却打印了第二行的第一个元素,正常来说我们是访问了第一行的第五个元素,打印了6也代表着它们的排序向上面说的一样,是像三个一维数组排在了一起一样。
而第二个输出结果就真的越界了,是随机值。
4. 数组作为函数参数
往往我们在写代码的时候,会将数组作为参数传个函数,比如:我要实现一个冒泡排序函数将一个整形数组排序。
冒泡排序简单的说就是把一个无序列的排序成有序列的数组,通过一个最左边或者是最右边开始(这里举例子用最左边)第一个元素和第二个元素对比谁大,大的移到第二个元素的位置,然后第二个元素和第三个元素比较,谁大谁到第三个元素那里,以此类推,直到尽头,这是第一次排序,最大的数在数组的最左边:
3 1 7 5 8 9 0 2 4 6
1 3 7 5 8 9 0 2 4 6
1 3 7 5 8 9 0 2 4 6
1 3 5 7 8 9 0 2 4 6
1 3 5 7 8 9 0 2 4 6
1 3 5 7 8 0 9 2 4 6
1 3 5 7 8 0 2 9 4 6
1 3 5 7 8 0 2 4 9 6
1 3 5 7 8 0 2 4 6 9
看到了吗,9到了最后一位。这样循环,第二次也是从头开始,只不过到达最左边前面的元素停下,变成这样:
1 3 5 7 0 2 4 6 8 9
以此循环,最终是这样,0 1 2 3 4 5 6 7 8 9
每次就像一个气泡一样往后面排序,这就是冒泡排序的逻辑。
4.1 冒泡排序函数的错误设计
那我们将会这样使用该函数:
//方法1:#include 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; }
我们这里运行代码之后发现,arr数组里面的数字纹丝未动,这是为什么呢?
我们再次打开调试看看。
我们发现,计算数组长度的sz竟然是1?这是为什么呢。
难道数组作为函数参数的时候,不是把整个数组的传递过去?
4.2 数组名是什么?
我们来看这段代码:
#include int main(){ int arr[10] = {1,2,3,4,5}; printf("%p\n", arr); printf("%p\n", &arr[0]); printf("%d\n", *arr); return 0; }
输出结果是:
我们这里发现,通过数组名打印的地址和数组首元素的地址相同,解引用之后都是1,也就是说,数组名就等于首元素地址(无论一维数组还是二维数组)
也就是说我们上面传参等于传进去了首元素的地址而已,这就是为何sz等于1。
但是有两种例外的情况,数组名不是首元素地址。
- sizeof(数组名),计算整个数组的大小,sizeof内部单独放一个数组名,数组名表示整个数
组。 - &数组名,取出的是数组的地址。&数组名,数组名表示整个数组。
#include int main(){int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };printf("%d\n", sizeof(arr));printf("%p\n", &arr);printf("%p\n", &arr + 1);return 0;}
这是输出代码:
我们发现确实如此,第一段代码是10个整形元素的数组的长度,第二段代码相差的是整个数组的地址,就像这样:
除此1,2两种情况之外,所有的数组名都表示数组首元素的地址。
4.3 冒泡排序函数的正确设计
当数组传参的时候,实际上只是把数组的首元素的地址传递过去了。
所以即使在函数参数部分写成数组的形式: int arr[] 表示的依然是一个指针: int *arr 。
那么,函数内部的 sizeof(arr) 结果是4。
如果 方法1 错了,该怎么设计?
#include 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 arr[] = { 3,1,7,5,8,9,0,2,4,6 }; int sz = sizeof(arr) / sizeof(arr[0]); int i = 0; bubble_sort(arr, sz);//是否可以正常排序? for (i = 0; i < sz; i++) { printf("%d ", arr[i]); } return 0;}
我们直接在main函数内部计算即可,这样我们的sz就是10了。
输出结果是:
0 1 2 3 4 5 6 7 8 9
本篇完
我们数组的这一片完结, 内容不是很多但很重要,但是很重要。
请路过的家人们点个赞!!!大佬们纠正一下错误和指点不足!!!