C语言入门,深入理解指针(3)

前言

在 C 语言的学习过程中,指针和数组始终是两道绕不开的门槛。很多初学者在掌握了基本语法之后,一遇到「数组名到底是不是首元素的地址」「为什么函数内部算不出数组长度」这类问题,就会陷入困惑。究其原因,往往是对数组名的本质、指针与数组之间的等价关系,以及传参时的底层机制缺乏系统性的理解。

本文将从数组名的本质讲起,逐步深入到指针访问数组、一维数组传参、冒泡排序、二级指针、指针数组,最后用指针数组模拟二维数组,把这一系列容易混淆的知识点串成一条清晰的脉络。希望通过这篇文章,能帮你真正打通指针与数组之间的「任督二脉」。

目录

  • [1. 数组名的理解](#1. 数组名的理解)
  • [2. 使用指针访问数组](#2. 使用指针访问数组)
  • [3. 一维数组传参的本质](#3. 一维数组传参的本质)
  • [4. 冒泡排序](#4. 冒泡排序)
  • [5. 二级指针](#5. 二级指针)
  • [6. 指针数组](#6. 指针数组)
  • [7. 指针数组模拟二维数组](#7. 指针数组模拟二维数组)

1.数组名的理解

今天,我将彻底讲清楚关于数组名的全部知识,在之前我讲到过数组名是首元素的地址,但是这个是对的吗?有没有人发出过疑问呢?

我先来验证一下这个问题,我先写一个代码,要求将arr和&arr0的地址打印一下,进行比对:

得到运行结果,我发现2个地址一样,证明了数组名是首元素这个说法,但是遇到下面一段代码怎么解释呢?

运行结果:

这里若理解成首元素的地址的话,那sizeof括号里面应该是一个整型,在不同的环境下,结果输出的应该是4或8,但是为什么不是这个结果呢?

实际上,在C语言中,关于数组名不被认为是首元素的有2种特殊情况:

第1种情况:sizeof(数组名) :括号内单独放数组名,这里表示整个数组,所以计算的是这个数组的大小。

第2种情况:&数组名 :单独对数组名进行取地址,这里表示整个数组,所以取地址取出的是整个数组的地址。

但当我实际去验证时,发现首元素的地址居然和整个数组的地址的值上是一样的。

因为整个数组就是从第一个元素开始存放的,所以地址才会一样。

实际上,当我让它们在原来的基础上都加上1,这时候就可以看出它们的区别了。

arr和&arr0在加上1后,地址跳过了4个字节,+1操作跳过1个元素;&arr在加上1后,地址由末尾数字88变成了98,增加了10,10在16进制转换下,相当于16个字节,说明地址跳过了16个字节,+1操作跳过整个数组,就是因为数组的类型与首元素的类型不一样,导致跳过的字节数不一样。

2.使用指针访问数组

彻底讲清楚数组名的理解之后,是不是对数组有了更深入的理解了,下面我们就来使用指针来写一段代码来访问一下数组:

理解了这么一段代码后,我们回顾一下之前访问数组的时候,是使用下标来实现访问的,如下图:

我们试着想一下,当首元素的地址arr赋值给p时,是不是代表arr和p是等价的,那我们既然可以用arri访问数组的元素,那么我们是否可以用pi来访问数组的元素呢?

显然是可以的,那么也就说明p+i换成arr+i也是可以的,我们总结出了如下等价关系:

不管是数组形式,还是指针形式,最终都会被编译器转化为指针形式去运行。

3.一维数组传参的本质

我们学过了数组,在之前讲过数组也是可以作为参数,传递给函数的,而在今天我们要讲解一下数组传参的本质。

首先从一个问题开始,在之前我们都是在函数外部去计算数组的元素的个数,那我们今天想要将数组传参,在函数内部去完成数组的元素个数的计算可以吗?说干就干,我们写出以下代码:

但是当我一运行,显示出来的结果却是:

代码能正常运行,说明代码没有问题,是不是我们的逻辑出现了问题,当我们回看分析代码时,我发现我们在函数的调用时,函数传参传的虽然是数组名,但是这里不属于我们前面讲到的数组名看成整个数组的2种情况,既然我们传的不是整个数组,那么传递的就是首元素的地址,当我进入函数的内部进行查看时,反向推导以下,一个整型元素是4个字节,那么sizeof(arr0)=4,sz2=2,计算出sizeof(arr)等于8,为什么会是8个字节呢?

原因是我在x64的环境底下,1个指针变量的大小是8,刚好对应上sizeof(arr)求的8,这也就说明了数组传参传递的是首元素的地址,所以函数形参的部分理论上应该使用指针变量来接受首元素的地址,那也就得出在函数内部sizeof(arr)计算的是一个地址的大小,而不是求数组元素个数,所以在函数内部是没办法求数组元素个数的。

解决办法:可以在函数外部算好数组元素个数,通过传值调用进入函数内部使用

4.冒泡排序

学到这里时,我们就要来学习一种新的算法了,名字叫做冒泡排序

冒泡排序的核心思想就是:两两相邻的元素进行比较。

前2趟排序,如下图:

写出代码如下:

我们举的例子比较极端,是一组降序的数字,但是当我们遇到的不是这样的一组数字时,代码效率就降低了,那我们该怎么样去优化一下代码呢?

我们最后得到的结果满足的条件是:在一趟内,若没有任何一对数字发生交换,就说明这组数字已经是升序了。

从这个思路出发,我最终改进代码如下:

5.二级指针

我们在之前已经学过一级指针了,一级指针变量存放的是变量的地址,那指针变量也是变量,指针变量的地址也要用一个指针变量给存放起来,这时候就要用到我们的二级指针了,如下图:

下面是对二级指针类型及含义解释:

那么我们能否通过二级指针来得到变量的值呢?其实是可以的,我们写一段代码来验证一下:

解释:* ppa通过对ppa中的地址进行解引用,得到pa,其实是a的地址;**ppa再对pa进行解引用操作,得到a的值

运行结果:

结论:二级指针变量是用来存放一级指针变量的地址的

6.指针数组

接下来我们来学习指针数组,有人就会发出疑问了,指针数组是指针还是数组呀?

我们结合以前学过的知识来类比一下,整型数组:存放整型的数组;字符数组:存放字符的数组;那么,指针数组就是存放指针的数组喽,实际上就是这样的:

指针数组的类型是指针类型,如int*、char*等

那么知道了指针数组,有什么用呢?

7.指针数组模拟二维数组

运行结果:

画图演示:

上述代码模拟出二维数组的效果,实际上并非完全是二维数组,因为每一行并非是连续的。

总结

回顾整篇文章,我们围绕「指针与数组」这一主题,梳理了以下几个核心知识点:

  1. 数组名的本质 :绝大多数情况下,数组名代表首元素的地址;但存在两种特殊情况------sizeof(数组名) 和 &数组名 表示的是整个数组。通过 +1 操作观察地址跳过的字节数,可以直观地体会到数组类型与元素类型的区别。
  2. 指针访问数组 :arr[i] 与 *(arr + i) 在本质上是等价的,编译器最终都会将其转化为指针形式进行运算,这为后续灵活使用指针打下了基础。
  3. 一维数组传参的本质 :数组传参传递的是首元素的地址,因此在函数内部无法通过 sizeof(arr) 求得数组元素个数,需要在函数外部计算后传入。
  4. 冒泡排序:通过两两相邻元素比较实现排序,并利用「一趟内无交换即已有序」的思路对算法进行优化,提升效率。
  5. 二级指针:用于存放一级指针变量的地址,通过两次解引用可以逐层访问到最终变量的值。
  6. 指针数组 :本质是存放指针的数组,其元素类型为指针类型(如 int*、char*)。
  7. 指针数组模拟二维数组:利用指针数组可以模拟出二维数组的访问效果,但每一行在内存中并不连续,与真正的二维数组存在差异。

指针与数组是 C 语言中极具魅力的部分,理解它们的本质,不仅能让你写出更灵活的代码,更能为后续学习链表、动态内存管理等进阶内容奠定坚实的基础。希望这篇文章能对你有所帮助,也欢迎在评论区交流你的疑问与心得。

相关推荐
码云数智-园园3 小时前
unique_ptr 还是 shared_ptr?C++ 智能指针选型与内存泄漏实战分析
java·开发语言
禾小西4 小时前
07丨Redis 哨兵机制:主库故障后,如何恢复服务?
java·开发语言·redis
我不是阵雨4 小时前
JDK 21虚拟线程Pinning陷阱:一文拔钉解困
java·开发语言
SuperByteMaster5 小时前
wfi进入之前没有清除basepri, 导致中断优先级不够,wfi 退不出来
c语言
徐小黑ACG5 小时前
Golang 基础05 结构体struct
开发语言·算法·golang
优橙教育5 小时前
零基础学AI应用开发要多久?3个月能到什么水平
服务器·开发语言·网络·php
Navigator_Z5 小时前
LeetCode //C - 1286. Iterator for Combination
c语言·算法·leetcode
SEO_juper6 小时前
用 Python 写一个 GEO 可见性检查脚本:你的网站现在能被 AI 引用吗
开发语言·人工智能·爬虫·python·seo·外贸独立站
西柚小萌新6 小时前
【LLM&&AI应用开发 八股文】--4.3.Agent智能体(下)
java·开发语言·数据库