一维数组⭐️
一维数组的创建🍁
说到数组啊,我们首先得来先理解数组是什么意思:
数组是一组相同类型元素的集合。
为什么会有数组的出现?我们已经知道,变量都是单个的,个体的,但是如果当我们需要使用到同
一类型的多个变量时,这时候,我们就可以用数组来存放,而不是一个个去创建。这就是使用数组
的好处,关键是在于相同类型的元素。🔔
数组创建的基本方式:
type arr_name [const]; //type 是指数组的元素类型 //const 是一个常量表达式,用来指定数组的大小
我们可以来举个例子看一看:
我们可以根据类型创建出不同类型的数组,同时也可以常量来指定数组的大小。
变长数组🍁
变长数组(variable-length array),C语言术语,也简称VLA。是指用整型变量或表达式声明或定义的数组,而不是说数组的长度会随时变化,变长数组在其生存期内的长度同样是固定的
我们来看看这一小段代码:
为什么编译不通过?原因在于:数组创建,在C99标准之前, [] 中要给一个常量才可以,不能使用变量。在C99标准支持了变长数 组的概念。这也说明了,当前编译器VS2019不属于C99标准。但是在其他一些编译器上是支持的!
初始化🍁
数组的初始化:数组的初始化是指,在创建数组的同时给数组的内容一些合理初始值(初始化)
数组在创建的时候如果想不指定数组的确定的大小就得初始化。数组的元素个数根据初始化的内容来确定。看下面的代码:
这里我们来看看一个经典的问题:下面的代码区分,内存中如何分配
我们不妨打开调试,打开监视,看看此时arr1和arr2里面的内容是什么:
在这里我们先来看看值,arr1中其实除了abc之外,还有\0,那这个\0在用sizeof计算大小的时候有没有算入呢?简单测试一下:
答案是有的!
使用🍁
对于数组的使用我们引入了一个操作符: [] ,下标引用操作符。它其实就数组访问的操作符。 我们来看代码:
我们要知道:数组是使用下标来访问的,下标是从0开始。数组的大小可以通过计算得到。可以通过sizeof来计算👇
越界问题🍁
数组的下标是有范围限制的。 数组的下规定是从0开始的,如果数组有n个元素,最后一个元素的下标就是n-1。 所以数组的下标如果小于0,或者大于n-1,就是数组越界访问了,超出了数组合法空间的访问。 C语言本身是不做数组下标的越界检查,编译器也不一定报错,但是编译器不报错,并不意味着程序就 是正确的, 所以程序员写代码时,最好自己做越界的检查。
我们稍微改变一下上面的代码,这也是许多初学者容易犯错误的地方,下面我们来看看这张图,里面标注了我想说的东西
与上面代码不同的地方在于i<=10!!!!大家留意一下🍭
我要提醒大家的是:在有些情况下,发生数组越界并不会报错,恰恰相反,它还会给你打印出结果来,到底是怎么一回事呢?我们通过一道题看一下究竟是怎么回事:
在这里,可怕的是编译器并没有报错,它还随机打印出一个值出来,对于数组越界的问题我们一定要多加注意啊!
同时,对于二维数组来说,二维数组的行和列也可能存在越界。
在内存中的存储🍁
不知道你有没有想过数组在内存中是怎么存储的?数组其实是顺序表,它其实在物理上和逻辑上是连续的,怎么去理解呢⁉️
我们可以通过创建一个数组,然后打印出地址,看看代码及其运行图:
我们知道int是4个字节,也就是相邻的元素之间差距4个字节4️⃣
同时,我们仔细观看结果,我们不难发现:随着数组下标的增长,元素的地址,也在有规律的递增。 由此可以得出结论:数组在内存中是连续存放的。这样就是我一开始所说的问题。大家好好想想。💡
好啦,到达这里,我们初步建立起了对一维数组的认识,现在基于一维数组的知识上,我们来看看二维数组⏬
拓展:sizeof和strlen()的区别🍁
关于这两个,我发现好多人老是弄混,所以在这里,给大家补充补充把:
sizeof
sizeof是一个操作符,是用来计算变量(类型)所占内存空间的大小,不关注内存中存放的具体内容,单位是字节
strlen
strlen是一个库函数,是专门求字符串长度的,只能针对字符串从参数给定的地址向后一直找\0,统计\0之前出现的字符的个数,strlen是计算的空间中字符的个数(不包括‘\0’)。
怎么理解sizeof是一个操作符?✅
很简单,我们可以看到。sizeof就算是不使用()也可以使用,这不难理解为什么说sizeof是一个操作符了!🆗
二维数组⭐️
二维数组本质上是以数组作为数组元素的数组,即“数组的数组”,类型说明符 数组名 [常量表达式] [常量表达式]。 二维数组又称为矩阵,行列数相等的矩阵称为方阵。
可千万不要以为二维数组是什么高大上的东西,本质上也只是数组而已。可别到后面看到矩阵就晕了,不知道矩阵是什么,其实用二维数组就是矩阵。
二维数组的创建🍁
我们同样可以根据不同类型创建不同的二维数组出来。第一个[]为行,第二个[]为列,比如第一个arr就是3行4列
初始化🍁
我们可以在创建的过程中就对其赋值,完成初始化,通过调试监视窗口,让我们来看看此时数组内容是什么:
我们可以知道:1.在初始化的过程中,里面的元素如果没有{}的话,就会直接默认放在第一行,第一行不够放的时候,会进入第二行,以此类推。
2.元素不够的时候,会自动初始化为0!
下面我们来看看二维数组是怎么使用的⏬
使用🍁
我们需要明确一点的就是:二维数组的使用也是通过下标的方式。
好啦,接下来基于一维数组的理解上,二维数组的使用对于我们并不是什么难事了,直接来看看我们的代码练习一下:
我们可以看到打印出来的结果只有一行,这时候可能有人会问了,怎么打印出类似矩阵的效果呢?好的,下面为你解答
我们只要每行(外层循环i)打印结束后加个换行就行了,这时候,你可能又会问:怎么让打印结果好看一些呢?作为一个有些强迫症的我继续为你解答!!!
看,这样打印出来的效果是不是好多了,区别在于%-2d,向左边对齐,如果是%2d就是向右边对齐,这里我就不演示了,可以自己去试一试。
下面,我们来看看二维数组是怎么存储的
在内存中的存储🍁
像一维数组一样,这里我们尝试打印二维数组的每个元素的地址
从运行结果来看,通过结果我们可以分析到,其实二维数组在内存中也是连续存储的。根据类型的不同会决定差距多少
好,抛开数组的基本知识意外,我们还会说到数组作为参数是怎么使用的
数组作为函数参数⭐️
错误的冒泡排序🍁
往往我们在写代码的时候,会将数组作为参数传个函数,这里以实现一个冒泡排序函数为例子引入数组作为函数参数是怎么一回事:
冒泡排序:
比较相邻的元素。如果第一个比第二个大,就交换他们两个。
对每一对相邻元素做同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。
针对所有的元素重复以上的步骤,除了最后一个。
持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较
其实冒泡排序就两个核心:1.两层for循环2.交换
下面我们来模拟实现冒泡排序
是不是觉得代码没毛病?❌
我可以负责任很明确的告诉你,上面代码出错了,为什么?
没有达到我们预期的效果。这时候怎么办,调试
请注意,这时候的sz并不是我们想要的结果,怎么变成1了呢?难道数组作为函数参数的时候,不是把整个数组的传递过去?
好啦,到这里,我们先搁置一下这个问题,给大家先结束数组名是什么,通过介绍完数组名之后,你就会知道为什么会出现这种情况了
数组名🍁
我们来看看数组名的地址和第一个元素的地址有什么联系🔭
我们可以得出一个结论:
数组名是数组首元素的地址。如果数组名是首元素地址,那怎么去理解这个呢?
结果是40,为什么呢?别急,凡事都有例外:数组名是数组首元素的地址。(有两个例外)📝
1.sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小,单位是字节.通过它我们可以计算数组的元素个数
2.&数组名,这里的数组名表示整个数组,取出的是整个数组的地址
怎么去理解第二点呢?给你一个代码你就能理解搞定了:
可以看到&arr[0]+1走了4个字节,而&arr+1走了整个数组12个字节。所以说&arr取的是整个数组的地址!!💡
好啦,基于以上的知识,我们回头看看我们刚开始的代码,通过一张图,给你指出为什么sz是1:
好啦,基于此,那我们要怎么去改进冒泡排序呢?🔈
正确的冒泡排序🍁
传参数的时候直接把大小一起传过去就避免了错误的情况,OK,到这里,我们的冒泡排序总算是大功告成啦!🎉
结束语😄
好啦,通过上面的介绍,相信你对数组有了更加深刻的理解。到了这里,也是我们该说再见的时候了,本篇博客的内容也到了这里结束,如果觉得写得不错的话,记得给个赞呗,你们的支持是我创作的最大动力🌹