数组存储与指针学习笔记(三)指针与数组

news2025/1/21 15:35:28

嵌入式C语言学习进阶系列文章

GUN C编译器拓展语法学习笔记(一)GNU C特殊语法部分详解
GUN C编译器拓展语法学习笔记(二)属性声明
GUN C编译器拓展语法学习笔记(三)内联函数、内建函数与可变参数宏
数组存储与指针学习笔记(一)数据类型与存储、数据对齐、数据移植、typedef
数组存储与指针学习笔记(二)枚举类型、常量与变量


文章目录

  • 嵌入式C语言学习进阶系列文章
    • 一、指针
      • 1.1 指针的本质
      • 1.2 复杂指针声明
      • 1.3 指针类型与运算
      • 1.4 指针与数组关系
    • 二、指针与结构体
    • 三、二级指针
      • 3.1 修改指针变量的值
      • 3.2 二维指针和指针数组
      • 3.3 二级指针和二维数组
    • 四、函数指针
    • 五、重新认识void


一、指针

1.1 指针的本质

  内存一般可分为静态内存和动态内存,一个程序被加载到内存运行时,代码段和数据段就属于静态内存,而堆栈则属于动态内存。

  • 静态内存的特点是内存中各个变量的地址在编译期间就确定了,在程序运行期间不再改变。
  • 动态内存中变量的地址在程序运行期间是不固定的,如函数的局部变量,如果这个函数多次被调用运行,那么每次运行都要在栈上随机分配一个栈帧空间;
      指针的原始初衷用途,其实就是访问一片匿名的动态内存。通过指针我们可以直接读写指定的内存。通变量一般采用直接寻址,既可当左值,又可当右值;而指针变量一般采用间接寻址。当指针变量通过间接寻址时,其又等价为一个普通变量(下面代码中的*p与a是等价的),既可当左值,又可当右值。

1.2 复杂指针声明

  声明一个指针,其实就是声明一个指针的类型。指针类型一般可以分为三大类。

  • 函数指针:void(*fp)(int,int)。
  • 对象指针:char*、int*、long*、struct xx*。
  • void指针:一般作为通用指针,作为函数的参数。
      函数指针,顾名思义,指针指向一个函数,指针变量存储的是函数的入口地址。当指针指向不同类型的数据时,我们称这种指针为对象指针。void
    指针既不属于对象指针,也不属于函数指针。

  和指针相关的运算符主要包括以下几种。

  • 指针声明:int*
  • 取址运算符:&
  • 间接访问运算符:*
  • 自增自减运算符:++、--
  • 成员选择运算符:.、->
  • 其他运算符:[]、()
      优先级按照从高到低的顺序依次为:[]、()、.、->、++、--、*、&
    在这里插入图片描述

  对于这种复杂的指针声明,我们可以借助“左右法则”来分析:首先从最里面的圆括号(未定义标识符)看起,先往右看,再往左看,每当遇到圆括号时,就应该掉转阅读方向。一旦解析完圆括号里所有的东西,就跳出圆括号。重复这个过程,直到整个声明解析完毕。
  我们再去分析上面声明语句中的最后一个指针声明:首先从最里面的圆括号看起,f是一个指针,整个指针表达式因此也就定了性。这条语句声明的是一个指针。然后往右看,是一个参数列表,说明该指针的类型是一个函数指针。再向左看,是一个符号,说明该指针指向的函数的返回值是一个指针。此时括号里的东西解析完毕,跳出圆括号,继续重复这个过程。往右看是一个数组,再往左看是int*,与下面类似。在这里插入图片描述
  把以上分析综合就可以得出最后的分析结果:这个复杂的指针声明相当于定义一个函数指针,该指针指向一个函数,这个函数的类型形参为(int),返回值是一个指向指针数组的指针,指针数组中的元素类型为int*。

在这里插入图片描述

1.3 指针类型与运算

  类型就是一组数值和对这些数值相关操作的集合。指针也是如此,不同的指针类型会有不同的运算操作。指针变量p通常用来指向一个不同类型的变量,每个变量类型不同,在内存中所占空间大小不同,p++也就被转换为不同的数值运算,运算结果也各不相同。但有一点是相同的,p++总是指向下一个元素的地址。为了存储变量的地址,指针变量本身也得有一个存储空间。
  加减运算:两个指针也可以直接相减,但前提是指针类型要一致,而且只能相减,不能相加,相减的结果表示两个指针在内存中的距离。两个指针相减的结果以数据类型的长度sizeof(type)为单位,而非以字节为单位。
  关系运算:两个指针可以比较大小,但比较的前提是指针类型必须相同,指针关系运算一般用在同一个数组或链表中,不同的比较结果代表不同的含义。

  • p < q:指针p所指的数在q所指数据的前面。
  • p > q:指针p所指的数在q所指数据的后面。
  • p == q:p和q指向同一个数据。
  • p != q:p和q指向不同的数据。

1.4 指针与数组关系

  从用法上看来:

  • 数组名作为函数参数时相当于一个指针地址。
  • 数组和指针一样,都可以通过间接运算符*访问。
  • 数组和指针一样,都可以使用下标运算符[]访问。

  从C语言语法上看来,数组与指针的访问方式、主要用途都不相同。
在这里插入图片描述

  1. 下标运算符[]
      下标运算符[]是数组用来访问数组元素的运算符,而间接访问运算符则是指针用来访问内存的运算符。按理而言,数组和指针是两个不同的东西,为啥运算符可以混用呢?原因如下:C语言对下标运算符的访问,是通过转化为指针来实现的。
    在这里插入图片描述
      当我们对一个数组a[n]通过下标访问时,编译器会将其转换为
    (a+n)的形式,数组名a代表的是数组首元素的地址,相当于一个指针常量。
    在这里插入图片描述

  2. 数组名本质
      当数组作为函数参数时,传递的是一个地址,此时数组名相当于一个常量指针。数组名其实也存在隐式转换,在不同的场合代表不同的意义。当我们使用数组名声明一个数组,或者使用数组名和sizeof、取址运算符&结合使用时,数组名表示的是数组类型。在其他情况下,数组名都是一个右值,表示数组首元素的地址,但是可以与间接访问运算符*构成一个左值表达式。如下面的程序所示。
    在这里插入图片描述  程序运行结果如下。
    在这里插入图片描述  了解了数组名在不同场合代表的类型及其隐式转换,也就明白了我们为什么不能直接为数组赋值,只能在初始化的时候赋值。

  3. 指针数组与数组指针
       指针数组本质上是一个数组,数组里的每一个元素存放的不是普通的数据,而是一个地址;数组指针本质上是一个指针,只不过这个指针指向的数据类型是一个数组。通过数组或指针,使用下标运算符或间接访问运算符都可以灵活实现对数组元素的访问。而指针数组的本质还是一个数组,只不过数组元素的数据类型比较特殊,是一个指针,我们按照数组的正常访问方式访问即可。

二、指针与结构体

   结构体则是由一组不同类型的数据组成的集合,我们可以通过成员访问运算符.去访问各个成员,也可以通过指针间接访问运算符-> 去访问各个成员。与指针、结构体相关的运算符如下。

  • 成员访问运算符:.
  • 成员间接访问运算符:->
  • 结构体成员取址:&stu.num
  • 结构体成员自增自减:++stu.num、stu.num++
  • 间接访问运算符:*stu.p

   举个例子。
在这里插入图片描述   程序运行结果如下。
在这里插入图片描述在这里插入图片描述
   访问结构体的成员有两种方法:直接成员访问和间接成员访问,对应的运算符分别为stu.num和p->num。构体是一个标量,当结构体作为函数的参数或者返回值时,传递的是整个结构体所有成员的值,这一点和数组是不同的,数组名作为参数时传递的仅仅是一个地址。在实际编程中,当需要结构体传参时,我们一般都使用结构体指针来实现,直接传一个地址就可以,简单高效。

三、二级指针

   指针变量主要用来存储一块内存的地址,然后通过间接访问运算符*去访问这块内存,对这块内存进行读写操作。指针变量可以保存任意类型变量的地址:数组、结构体、函数甚至另一个指针变量的地址。当一个指针变量保存的是另一个指针变量的地址时,我们称该指针是指向指针的指针,或者叫二级指针。举个例子:
在这里插入图片描述
   程序的运行结果如下。
在这里插入图片描述
   访问a变量所绑定的这块内存空间有以下三种方法。

  • 通过变量名a直接访问。
  • 通过一级指针p和间接访问运算符*间接访问。
  • 通过二级指针pp和间接访问运算符**间接访问。
       二级指针解决场景。
  • 修改指针变量的值。
  • 指针数组传参。
  • 操作二维数组。

3.1 修改指针变量的值

   函数的参数传递是值传递,传递的是变量的副本,函数形参的改变并不会改变实参的值。通过一级指针,我们可以修改一个普通变量的值。如果想修改一个指针变量的值,则可以通过二级指针来完成。
在这里插入图片描述
  程序运行结果如下。
在这里插入图片描述
  在上面的程序中,如果我们想通过change3()函数改变指针变量p的值,则只能将change3()函数的参数设计为二级指针形式,把指针变量的地址&p作为实参传递给函数,change3() 函数就可以根据p的内存地址来修改p的值了。

3.2 二维指针和指针数组

  指针数组本质上还是一个数组,只不过每个数组元素都是一个指针而已。当数组作为函数的参数时,对于一维数组来说,数组名会隐式转换为数组首元素的地址,即一级指针。当指针数组作为函数参数时,数组名也会隐式转换为首元素的地址,即指针的地址——二级指针。当数组作为函数参数时,其可以匹配的形参形式如下。
在这里插入图片描述

3.3 二级指针和二维数组

  一维数组的数组元素类型为int,我们称这个一维数组为整型数组;一维数组的数组元素类型为结构体,我们称这个数组为结构体数组。如果一维数组的数组元素还是一个数组,则我们不能称之为数组型数组,而一般称之为二维数组。C语言是把二维数组看成一个特殊的一维数组来处理的:每个元素都是一个一维数组。我们可以通过一级指针去操作一维数组,那么如何通过二级指针操作二维数组呢?
在这里插入图片描述  上面代码中有两条赋值语句,第一条p赋值语句没有问题,指针p指向一维数组首元素的地址,数组元素的类型为int,指针p的类型为int*,两者是匹配的,程序编译正常。而第二句二级指针pp的赋值,编译器在编译时会发出警告:类型不兼容。
在这里插入图片描述
  第二句的赋值语句等效为pp=&b[0],b[0]代表什么呢?C语言是把二维数组当成一维数组来处理的,二维数组b[3][5]其实就是一个一维数组b[3],该一维数组中的每一个数组元素是一个长度为5的一维数组int c[5]。如果你想把数组名b直接赋值给指针变量pp,那么指针变量的类型必须为int(*p)[5]这种类型。为了解决这个问题,有以下两种解法。

  • 指针数组方式访问

在这里插入图片描述

  • 二级指针方式访问
    在这里插入图片描述在这里插入图片描述
  • 一级指针方式访问
    在这里插入图片描述
      注意:不同的指针类型执行自增操作时,实际偏移的地址是不一样的。在使用指针操作数组时,无论操作一维数组,还是二维数组,程序员都必须时刻记住的一点就是:你定义的指针类型不同,操作数组的方式也不同。牢记这点,并熟练掌握与指针相关的声明和运算符的优先级,才能够把指针用得得心应手。

四、函数指针

  函数指针用来指向一个函数,一般我们会定义一个函数指针变量来保存函数的入口地址。
在这里插入图片描述
  调用示例如下:
在这里插入图片描述在这里插入图片描述
  我们可以通过函数名+函数调用运算符()去调用一个函数。函数名的本质其实就是指向函数的指针常量,即函数的入口地址。在fp=func;语句中,函数名会通过隐式转换,转换成fp=&func;的形式。当我们通过指针调用函数时,(*fp)()间接访问其实就等效为fp()表达式。无论是间接访问,还是多次间接访问,如下所示,它们的效果其实都是一样的,都等效为fp()。
在这里插入图片描述
  区分:指针函数指函数的类型,即函数的返回值是一个指针,除此之外和普通函数无异,就不再赘述了。指针函数的声明方式如下。
在这里插入图片描述

五、重新认识void

  void*指针,void关键字在C语言中被大量使用,大家对它既熟悉又陌生。void其实也是一种类型,只不过它比较特殊:无数值,无运算。

  • void经常用来修饰函数的返回类型,表明函数无返回值。
  • void作为函数的参数时,表明函数无参数。
  • void*指针可以指向任意数据类型,任意类型指针可以直接赋值给void*指针,不需要强制类型转换。
  • void*指针赋值给其他类型指针时,需要强制类型转换。任意类型的指针转换为void*,再转换为原来的类型时,都不会发生数据丢失,值也不会发生改变。
    在这里插入图片描述
      void*指针主要用来作为函数的参数,表示函数的参数可以是任意指针类型。当函数的返回类型为void*时,返回的指针可以指向任意数据类型。C标准库中很多函数原型中都使用了void*指针。
    在这里插入图片描述
      malloc()函数返回的指针类型为void*,因此在将malloc()函数返回的地址赋值给一个指针变量时,一般要做强制类型转换。void作为一种指针类型,除了修饰函数原型,一般不参与具体的指针运算。我们不能使用间接访问运算符访问void*,不能对void*做下标运算,但是在GNU C中可以做自增自减运算。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/511041.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

OpenCv更改颜色空间以及图像阈值

本文主要讲解以下几个方面: 如何将图片从一个颜色空间转换到另一个&#xff0c;例如 BGR 到 Gray&#xff0c;BGR 到 HSV 等。简单阈值法另外&#xff0c;我们会创建一个从图片中提取彩色对象的应用。 1.改变颜色空间 cv.cvtColor(img, flag) 参数flag表示颜色空间转换的方…

Hive语言2(大数据的核心:窗口函数)

1、Common Table Expressions&#xff08;CTE&#xff09;> 重点 公用表达式(CTE)是一个临时结果集&#xff0c;该结果集是从WITH子句中指定的简单查询派生而来的&#xff0c;该查询紧接在SELECT或INSERT关键字之前。 2.inner join&#xff08;内连接&#xff09;、left joi…

网页源码加密JavaScript程序,有效压缩和加密JS、Html、Css页面数据

我们知道&#xff0c;基于Des或Aes对称加密时&#xff0c;当明文和密码相同&#xff0c;则密文相同。而我们此次发布是WJLSymmetricEncryption4.js&#xff08;点击链接跳转到下载页面&#xff09;加密程序&#xff0c;当明文和密码相同&#xff0c;每次加密后的密文不相同&…

20230510vmlinux编译过程

1.进入linux内核源码目录下&#xff0c;打开Makefile文件&#xff0c;搜索vmlinux cmd_link-vmlinux \ $(CONFIG_SHELL) $< "$(LD)" "$(KBUILD_LDFLAGS)" "$(LDFLAGS_vmlinux)"; …

第10 CURD操作与RedisCache缓存的强制清理的实现

using System.Net; using Microsoft.Extensions.Caching.Distributed; using Core.Caching; using Core.Configuration; using StackExchange.Redis; namespace Services.Caching { /// <summary> /// 【Redis分布式缓存数据库软件管理器--类】 /// <remarks>…

索引 ---MySQL的总结(五)

索引 在mysql数据库之中&#xff0c;如果数据量过大&#xff0c;直接进行遍历会需要使用许多时间。这里使用空间换时间解决这一个问题。 目前就是从解决问题的这一个角度出发&#xff0c;需要增加搜索的速度&#xff0c;一定是要选择好用的数据结构进行搜索&#xff08;遍历的…

第十五届吉林省赛个人题解【中档题(不过可能对你来说是简单题)】(H、G、C)

文章目录 H. Visit the Park(STL)G. Matrix Repair(思维题)C.Random Number Generator(BSGS算法) H. Visit the Park(STL) 题意&#xff1a;给你一个无向图&#xff0c;每条边上都有一个数码&#xff0c;然后给你一个路径&#xff0c;每次你必须从Ai走到Ai1&#xff08;直接走…

【EHub_tx1_tx2_A200】Ubuntu18.04 + ROS_ Melodic + 锐驰LakiBeam 1L单线激光 雷达评测

大家好&#xff0c;我是虎哥&#xff0c;最近这段时间&#xff0c;又手欠入手了锐驰LakiBeam 1L激光雷达&#xff0c;实在是性价比太优秀&#xff0c;话说&#xff0c;最近激光雷达圈确实有点卷。锐驰官网的资料已经很丰富&#xff0c;我这里总结一下自己的简单测试经验&#x…

挑战14天学完Python---

抛弃了数学思维,引入了计算思维,计算思维是抽象和自动化相结合的结果 抽象:抽象问题的形式化逻辑 自动化:将抽象的结果通过程序自动实现 0.1在计算机内部转二进制 0.1转二进制 二进制的0.1与二进制0.2计算 结果再转十进制 在众多编程语言中 ,只有Python语言提供了复数类型.空间…

OpenCL编程指南-1.2OpenCL基本概念

OpenCL概念基础 面向异构平台的应用都必须完成以下步骤&#xff1a; 1&#xff09;发现构成异构系统的组件。 2&#xff09;探查这些组件的特征&#xff0c;使软件能够适应不同硬件单元的特定特性。 3&#xff09;创建将在平台上运行的指令块&#xff08;内核)。 4&#xff09…

紧跟 AI 步伐, Gitee 已支持 AI 模型托管

AI 时代已经来了&#xff01; 现在&#xff0c;越来越多的企业和个人开始使用 AI 技术来解决各种问题。想要了解 AI&#xff0c;那么就一定要了解 AI 模型&#xff0c;作为 AI 的核心技术之一&#xff0c;AI 模型为各种进阶的人工智能应用奠定了基础&#xff0c;从 ChatGPT 、…

Mysql 存储过程+触发器+存储函数+游标

视图&#xff08;view&#xff09; 虚拟存在的表&#xff0c;不保存查询结构&#xff0c;只保存查询的sql逻辑 语法 存储过程 实现定义并存储在数据库的一段sql语句的集合&#xff0c;可减少网络交互&#xff0c;提高性能&#xff0c;代码复用,内容包括&#xff1a;变量&am…

并发编程进阶

并发编程进阶 文章目录 并发编程进阶一、JMM1. JMM的定义&#xff1a;2. 内存屏障&#xff1a; 三. volatile四. as-if-serial五. happens-before六. 缓存一致性&#xff08;Cache coherence&#xff09;7. Synchronized1. synchronized 的使用2. synchronized底层原理 8. Conc…

Web3.0介绍与产业赛道(去中心化,金融与数字资产,应用与存储,区块链技术)

文章目录 1、web3.0时代——区块链技术2、产业赛道&#xff1a;去中心化金融与数字资产3、产业赛道&#xff1a;去中心化应用与存储4、区块链&#xff1a;基础设施与区块链安全和隐私 1、web3.0时代——区块链技术 Web3.0是什么 Web3.0是指下一代互联网技术&#xff0c;它将在…

最优化理论-线性规划的标准形

目录 一、引言 二、线性规划的标准形 1. 线性规划的定义 2. 线性规划的标准形 3. 线性规划的约束条件 三、线性规划的求解方法 1. 单纯形法 2. 内点法 3. 割平面法 四、线性规划的应用 1. 生产计划 2. 运输问题 3. 投资组合问题 五、总结 一、引言 最优化理论是…

数据链路层及其重要协议——以太网

文章目录 数据链路层前言1. 以太网协议2. MTU&#xff08;传输的限制&#xff09;3. ARP协议 数据链路层 前言 以太网&#xff1a; 不是一种具体的网络&#xff0c;而是一种技术标准。既包含了数据链路层的内容&#xff0c;也包含了一些物理层的内容&#xff0c;例如&#xf…

网络层——IP协议详细解读

文章目录 IP协议1. IP协议的报文格式2. IP协议的地址管理3. IP地址的组成4. IP协议的路由选择 IP协议 之前介绍了传输层的重点协议&#xff0c;TCP和UDP协议&#xff0c;以下将介绍网络层的重点协议IP协议。 1. IP协议的报文格式 IP地址 本质上是一个32位整数&#xff0c;在…

华为OD机试真题 Java 实现【不爱施肥的小布】【2023Q2】

一、题目描述 某农村主管理了一大片果园&#xff0c;fields[i]表示不同国林的面积&#xff0c;单位m2&#xff0c;现在要为所有的果林施肥且必须在n天之内完成&#xff0c;否则影响收成。小布是国林的工作人员&#xff0c;他每次选择一片果林进行施肥&#xff0c;且一片国林施…

【Linux初阶】环境变量 | 如何设置、获取环境变量?

&#x1f31f;hello&#xff0c;各位读者大大们你们好呀&#x1f31f; &#x1f36d;&#x1f36d;系列专栏&#xff1a;【Linux初阶】 ✒️✒️本篇内容&#xff1a;讨论为什么指令作为一个可执行程序不需要加 ./运行&#xff1b;环境变量为什么会自己恢复&#xff1b;环境变量…

Acwing456. 车站分级

一条单向的铁路线上&#xff0c;依次有编号为 1, 2, …, n的 n 个火车站。 每个火车站都有一个级别&#xff0c;最低为 1 级。 现有若干趟车次在这条线路上行驶&#xff0c;每一趟都满足如下要求&#xff1a;如果这趟车次停靠了火车站 xx&#xff0c;则始发站、终点站之间…