1、数组名的理解
在上一章节我们在使用指针访问数组的内容的时候,我们有下面这样的代码
在上述的代码中我们使用&arr[0]来取得数组首元素的地址,但是我们前面也说过,数组名字 其实也就是数组的地址,而且还是首元素的地址。那么是不是说&arr[ 0 ]=arr;呢?下面我们 通过测试看看。
代码如下:
输出结果:
可以看到数组名和数组首元素地址打印出的结果是一模一样的,数组名就是首元素的地址, 那么如果数组名就是首元素地址那么有下面的代码,又该如何解释呢?
可以发现其输出的结果是40,但是如果arr就是地址的话其输出的结果应该为:4或者8才对 呀。这要如何理解呢?
其实数组名就是数组首元素(第一个元素)的地址是对的,但是也有两个例外:
- sizeof(数组名),sizeof中单独存放数组名,这里的数组名表示整个数组,计算的是整个数 组的大小,单位是字节
- &数组名,这里的数组名表示整个数组,取出的是整个数组的地址(这里和首元素的地址是 有点不一样的)。
除此之外其他情况数组名就是首元素的地址。
此时我们再看看下面的代码:
运行结果:
这时可以发现三个打印出来的结果是一样的,那arr和&arr到底有什么区别呢?
我们又看下面的代码:
输出结果:
这里我们发现&arr[0]和&arr[0]+1相差4个字节,而arr和arr+1相差4个字节,这是因为&arr[0] 和arr都是首元素的地址,所以是4个字节的空间,+1就是跳过一个元素的空间,然而&arr和 &arr+1则是跳过了40个字节的空间,这是因为&arr是整个数组的地址,+1操作就是跳过整个 数组。
总结:
数组名是首元素的地址,除了两个之外:一个是sizeof(数组名),还有一个就是&arr。
2、使用指针访问数组
有了前面的知识的基础,我们对于使用指针访问数组就会显得很简单了,之前我们对于数组 的输入和输出都是使用的循环,然后是使用数组的下标对每个元素进行输入和输出的,现在 我们有了指针,可以使用arr+i,因为arr是首元素的地址,那么+i就是第i个的元素。
如下:
这个代码搞明⽩后,我们再试⼀下,如果我们再分析⼀下,数组名arr是数组⾸元素的地址, 可以赋值 给p,其实数组名arr和p在这⾥是等价的。那我们可以使⽤arr[i]可以访问数组的元 素,那p[i]是否也可 以访问数组呢?
如下所示:
代码的运行结果如下:
可以发现将*(p+i)换成p[i]也是可以正常打印的,这是因为p[i]本质上也是等于*(p+1)。
那么arr[i]是不是就等价于*(arr+i)。这时候我们奇思妙想一下arr[i]会不会也等价为*(i+arr).
那么*(i+arr)是不是也等价为i[arr],也就是arr[i]也可以写成[i]arr,那么p[i]也就可以写成i[p]呢?
下面我们通过代码来检验:
运行结果:
可以看到这几种表达方式是等价的,这是不是打开了新世界的大门。
3、一维数组传参本质
前面我们也学过了,数组是可以作为参数传给函数的,我们现在讨论一下数组传参的本质。
我们从一个问题开始,我们之前都是再函数外部求数组的元素个数的,那么我们可以把数组 传给一个函数后,在函数内部求整个数组的元素个数吗?
我们实践一下看看。
运行结果:
我们发现函数内部计算数组的元素个数是没有正确获得数组元素的个数的。
这里我们就要了解一下数组传参的本质了,前面我们有说过,数组名是数组的首元素地址; 那么在数组传参的时候,给函数传的是数组名,那么也就是说本质上数组传递的是数组首元 素的地址。
所以函数形参的部分理论上应该使用指针变量来接收首元素的地址。那么在函数内部我们写 sizeof(arr)计算的应该是一个地址的大小,而不是数组的大小。正是因为函数的参数部分 本质上是指针,所以在函数内部是没办法求数组的元素个数的。
那么我们是否也就可以使用一个指针变量来结合搜数组的传参呢?
代码展示:
运行结果:
通过上面的对比可以发现一维数组传参,形参的部分可以写成数组的方式,也可以通过指 针变量来接收,其传参的本质也是传的首元素的地址。
4、冒泡排序
例如我现在有一排打乱的数字,我现在需要其变成从小到大的顺序。冒泡排序就是模仿泡泡 的样子,大的泡泡就会升起来,在普通的冒泡排序中其默认是升序的。我们今天实现一个即 可以升序,还可以降序的。
冒泡排序的逻辑就是相邻的两个数,然后如果我们要升序的就将大的那个数放在后面,小的 数放在前面,降序就是反过来这样操作
下面我们来具体实现一下冒泡排序:
首先我们是对一组数进行排序,所以我们需要一个数组来存放这堆数字,然后我们是要对这 个数组的元素进行操作的,所以我们的函数参数需要一个数组,然后我们还需要这个数组的 长度,然后我们这个函数由于是要实现两个功能,所以我们要有一个选择来让其选择升序还 是降序,这里我们和前面写扫雷和猜数字游戏一样:0表示降序,1表示升序,所以我们的函 数还需要一个参数来接收用户的选择。
其流程大致如下:
开始:
|
↓
输入待排序数组
|
↓
n = 数组长度
|
↓
重复执行以下步骤 n-1 次
|
↓
i = 0
|
↓
重复执行以下步骤 n-i-1 次
|
↓
如果数组[i] > 数组[i+1]
|
↓
交换数组[i] 和 数组[i+1]
|
↓
i = i + 1
|
↓
i = i + 1
|
↓
排序完成,输出数组
|
↓
结束
代码如下:
运行结果:
当然上面的代码还有很多的优化空间,下来我们自己发挥主观能动性来优化把。
5、二级指针
指针变量也是一种变量,是变量就有地址,那么指针变量的地址存放在哪里呢?
这就是二级指针
其逻辑大致如下:
对于二级指针的运算有如下:
1、*ppa通过对ppa中的地址解引用,这样找到的是pa,*ppa其实访问的就是pa,*ppa其实 访问的就是pa。
2、*ppa先通过*ppa找到pa,然后对pa进行解引用,然后*pa找到的就是a。
二级指针现在用得比较少了,后续我们也会深入去学习的。
6、指针数组
指针数组是指针还是数组?
我们类⽐⼀下,整型数组,是存放整型的数组,字符数组是存放字符的数组。 那指针数组 呢?是存放指针的数组。
指针数组的每个元素都是用来存放地址(指针)的,如下图所示:
指针数组的每个元素都是地址,也可以指向一个区域
7、指针数组实现二位数组
我们先创建几个一维数组,然后将这个数组的地址存入一个指针数组中:
我们打印一下这个指针数组:
可以看到确实通过指针数组,模拟实现了二位数组。