目录

1.内存和地址

2.指针变量和地址

2.1 取地址操作符(&)

2.2 指针变量

2.3 解引用操作符(*)

2.4 指针变量的大小

3.指针变量类型的意义 

3.1 指针的解引用

3.2 指针 + - 整数

3.3 void*指针 

4.指针运算

4.1 指针 + - 整数 

4.2 指针 - 指针

4.3 指针的关系运算


正文开始

1.内存和地址

        在这里,我们先提一下存储器的概念。在计算机中,存储器可以分为:

  1. 主存储器:又称主存、内存。用来存放计算机运行期间所需的程序和数据,CPU可以直接随机地对其进行访问。特点是容量小、存取速度快、造价高。
  2. 辅助存储器:又称辅存、外存。用来存放当前暂时不用的程序和数据,以及一些需要永久性保存的信息。外存的内容需要调入主存后才能被CPU访问。特点是容量大、存取速度慢、造价低。我们常见的硬盘就属于外存。
  3. 高速缓冲存储器:又称cache。位于主存和CPU之间,用来存放当前CPU经常使用的指令和数据,以便CPU能够高速地访问它们。特点是容量小、价格高。现代计算机一般将cache制作到CPU中。

        可知,内存其实是电脑上的存储设备,程序在运行的过程中,需要向内存申请空间来使用。

        我们可以举一个生活中的例子,在大学里,同一个专业的同学,一般是在同一栋宿舍楼中的。而在一栋宿舍楼中,有不同的房间号,比如:1楼有101、102、103...,2楼有201、202、203...,3楼有301、302、303...。如果你想找到你班上的某个同学,要是一层层楼,挨个房间去找,效率会非常低。但是,如果我们知道某个同学的地址,也就是所在宿舍的门牌号,就可以根据地址直接找到这个同学了。

        如果把上面的例子推广到计算机中,是怎么样的呢?

        我们知道,在计算机中,CPU处理数据的时候,所需的数据是从内存中读取的,处理后的数据也会放回到内存中。我们日常使用的电脑,有8G、16G、32G......,这些内存空间是如何高效管理的呢?

        其实就是把内存划分为一个个的内存单元,每个单元的大小为1个字节,1个字节含有8个比特位,每个比特位可以存放1个二进制的0或1。

        计算机中常见的单位如下:

  1. bit:比特位,简称位,缩写为b。是计算机中最小的存储单位,1bit表示1个二进制位(0或1)。
  2. Byte:字节,缩写为B。1Byte = 8bit。
  3. KB:千字节。1KB =  1024B,   即2的10次方B。
  4. MB:兆字节。1MB = 1024KB,即2的20次方B。
  5. GB:吉字节。1GB = 1024MB,即2的30次方B。
  6. TB:太字节。1TB  = 1024GB,即2的40次方B。
  7. PB:拍字节。1PB = 1024TB, 即2的50次方B。

        如上图,从下往上,给每一个内存单元编了号:0,1,2,3...,用16进制来表示。

        每个内存单元,都有1个字节的空间,也就是8个比特位的空间,相当于1个宿舍里,有8个床位的空间。

        每个内存单元,都有一个编号,相当于宿舍的门牌号。有了这个门牌号,就可以快速找到一个同学。有了这个内存单元的编号,CPU就可以快速找到一个内存空间。

        生活中, 我们把门牌号也叫作地址。在计算机中,我们把内存单元的编号也叫地址。在C语言中,给地址起了新的名字:指针。

        可以这样理解:在C语言中, 内存单元的编号 = 地址 = 指针,三种说法是一样的。

        那么,我们怎么理解编址呢?

        如上图,我们知道, CPU和内存之间,有大量的数据交互,为了实现这种交互,两者之间是用线连接起来的。

        CPU是完成计算工作的,首先要从内存中读数据,计算完毕后,又将结果的数据写入内存。

        这个过程是:首先,控制总线发出Read指令,然后CPU会给出一个地址,通过地址总线向内存传递一个地址信号,从内存中找到该空间,再将该空间中的数据通过数据总线传递给CPU。在CPU利用所读的数据进行计算,得出结果之后,控制总线发出Write指令,然后CPU给出一个写入地址,通过地址总线向内存传递有一个地址信号,在内存中找到该空间,把结果的数据写入这个空间中。

        我们再来讨论一下编址:

        CPU要访问内存中的某个字节空间,必须知道这个字节空间在内存中的什么位置, 而内存中有很多个字节,就需要给内存进行编址了。就好比,一栋楼中有很多个宿舍,需要给每个宿舍进行编号。

        计算机中的编址,并不是把每个字节的地址记录下来,而是通过硬件设计完成的。

        就好比,吉他上面没有写:哆、来、咪、发、梭、拉、西。但是演奏者也能准确找到每一个音所在的位置,这是为什么呢?因为制造商已经在乐器的硬件层面上设计好了,并且所有的演奏者也知道,本质上是一种约定的共识。硬件编址也是如此。

        我们可以简单理解为:32位机器有32根地址总线,每根线只有2种状态,电脉冲的有无代表0和1。那么1根线能表达2种含义,2根线能表达4种含义,......,32根线能表达2的32次方种含义。每一种含义都代表一个地址。

        地址信息被下达给内存,在内存中,通过硬件的设计,就可以直接找到该地址中对应的数据,这个数据再通过数据总线传给CPU内的寄存器。

2.指针变量和地址

2.1 取地址操作符(&)

        在了解了内存和地址之后,我们回到C语言。在C语言中,创建1个变量,其实就是在向内存申请空间。比如:对于int a = 10;,实质就是,向内存申请4个字节的空间,把10存放进去。如下图:

        如上,创建了整型变量a,向内存申请了4个字节的空间,用于存放整数10。

        我们使用取地址操作符(&),取出了4个字节中地址较小的那个地址。在printf打印时,对应的占位符是%p。

        int变量占4个字节,我们只要知道了第一个字节的地址,就可以顺藤摸瓜访问到剩下3个字节的数据,因为相邻地址之间相差1。

2.2 指针变量

        刚刚,我们使用取地址操作符(&),取出了变量a的地址,如果我们想把这个地址存到一个变量里面去,该怎么操作呢?如下:

        指针就是地址,指针变量是专门用来存放地址的变量。应该知道的是,上面的pa,在日常生活中,指针变量有时会被口头语叫做指针,而指针实际上是地址,而非变量

        对于int类型的变量a,它的地址存放在int*类型的pa中。那么举一反三,如果有一个char类型的变量c,它的地址应该存放在什么类型的指针变量中呢?如下:

2.3 解引用操作符(*)

        指针变量是用来存放地址的,存起来的地址有啥用呢?当然是用来使用的,怎么使用呢?我们可以看下面的例子:

        如上,变量pa中存放了变量a的地址。*pa的意思就是 ,通过pa中存放的地址,找到这个地址所指向的空间,也就是找到变量a。其实,*pa就是a,*pa=20这个操作就是把a改成了20。我们可以直接通过a=20来修改a的值,但是用指针来修改,就多了一种途径,写起代码来会更灵活,而且在某些情况下,指针是会起到它的作用的。

2.4 指针变量的大小

        我们知道,各种类型的变量,都是有其大小的,比如:int的大小是4个字节,char的大小是1个字节。那么,指针变量的大小是多少呢?我们来看如下代码:

        如上图,在x64环境,即64位环境下,int*和char*的长度都是8。在x86环境,即32位环境下,int*和char*的长度都是4。这是为什么呢?

        在前面,我们提到过,对于32位机器,一般有32根地址总线,每根线传递的电信号转换成数字信号,是0或1。我们把32根地址线产生的二进制序列当做一个地址,那么地址在总线上传输的时候,长度就是32个bit位,即4个字节,在内存中存储时,和它传递时的长度是一样的,也是4个字节。

        同理,对于64位机器,一般有64根地址总线,存储的地址就有64个bit位的空间, 也就是8个字节,在总线上传输和内存中存储的长度就是4个字节了。

        结论:

  1. 32位平台下,地址是32个bit位,指针变量的大小是4个字节。
  2. 64位平台下,地址是64个bit位,指针变量的大小是8个字节。
  3. 指针变量的大小,与指针变量的类型无关。

3.指针变量类型的意义 

        刚刚我们发现,无论什么类型的指针变量,在相同的平台上,它的大小都是相同的,和类型没有关系。那么,指针变量的类型有什么意义呢?

3.1 指针的解引用

        请看代码:

        如上,在创建变量a之后,我们取a的地址,此时4个字节中依次存放着:44、33、22、11。

        如上,在使用*pa修改a的值为0之后,内存中的4个字节全部被修改为0,呈现出:00、00、00、00、00。

        在上面,我们是用int* pa来接受a的地址的,如果换成char*类型会怎么样呢?

        如上,我们发现,如果指针变量pa的类型为char*,那么在执行*pa=0后,内存中只有第1个字节被修改为00,呈现出:00、33、22、11。

        如上,运行结果显示,变量a的值确实没有修改为0,而是为第1个字节修改为0之后的结果。

        其实,指针的类型决定了,在指针解引用的时候,一次能操作几个字节。

        比如:char*的指针解引用就只能访问1个字节,而int*的指针解引用能访问4个字节

        我们还可以用下面的方式验证一下,pa由于是char*的类型,所以在打印的时候,也只能打印出第1个字节的内容,只有使用int*才能完整地打印出4个字节所表示的值。如下:

3.2 指针 + - 整数

        请看代码:

        如上,a、pa、pc的地址都是一样的,这当然应该一样。

        但是,pa+1后, 地址增加了4,而pc+1后,地址只增加了1。

        我们来看看图示:

        结论:

  1. 指针的类型决定了指针向前或向后走一步,有多大(距离)。
  2. 指针+n,其实就是跳过n个指针所指向元素的长度。
  3. 指针可以+n,也可以-n。

3.3 void*指针 

        在前面我们已经了解到, 指针变量是有很多种类型的,比如:int*表示指针指向的是int类型的变量、char*表示指向char类型的变量、short*表示指向short类型的变量、double*表示指向double类型的变量......

        其实,在指针类型中,有一种特殊的类型叫void*类型,可以理解为,无具体类型的指针(或泛型指针)。

        void*指针可以接收任意类型的地址

        void*指针的局限性在于,不能直接进行指针的解引用操作和+-整数操作

        我们可以看到,void*指针可以接收不同类型的地址,但是无法直接进行指针运算。那么,void*指针到底有什么用呢?

        一般,void*指针是在函数的参数部分使用的,用来接收不同类型的数据,这样的设计可以实现泛型编程的效果,使得一个函数可以处理多种类型的数据

4.指针运算

        指针的基本运算有3种,如下:

  1. 指针+-整数
  2. 指针-指针
  3. 指针的关系运算

4.1 指针 + - 整数 

        假如要遍历一个数组,打印出每一个元素,我们首先想到的可能是如下方法:

        其实,我们还可以使用指针的方式来遍历数组。

        我们知道,数组在内存中是连续存放的,只要知道了第一个元素的地址,就能顺藤摸瓜找到后面的所有元素。我们可以将首元素的地址存放在变量p中,*p访问的是第1个元素,*(p+1)访问的是第2个元素,*(p+2)访问的是第3个元素......循环下去,我们就能遍历数组了,请看下图:

        当然了,我们也可以用指针实现倒序打印数组,把数组的尾元素地址交给指针变量p,*p访问的是倒数第一个元素,*(p-1)访问的是倒数第二个元素......循环下去即可,请看代码:

4.2 指针 - 指针

        关于指针减去指针,我们要注意的是:

  1. 指针 - 指针的前提是:2个指针指向同一块空间。
  2. 指针 - 指针得到的是:2个指针之间的元素个数。 

        请看代码:

        我们再来看看图示:

        我们可以联想到前面所提到的指针 + - 整数:

        那么,指针 - 指针有什么用呢?我们来看看它的应用:

        如上,为了统计字符串中,\0之前的字符个数,我们使用了strlen这个库函数。那么,我们能不能不使用库函数,而是自己写一个函数,来统计字符个数呢?

        我们先来看看指针+1的方式:

        我们再来思考一下,用指针 - 指针的方式来解决这个问题。如果我们能够找到'\0'的指针,用'\0'的指针减去首元素的指针,就可以得到元素个数了。请看代码:

4.3 指针的关系运算

        指针的关系运算,其实就是地址比较大小。我们知道,地址其实就是一种编号,是数值,当然可以比较大小了。

        我们还是以遍历数组为例子:


完结

 

        

        

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

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

013 HTTP篇

3.1 HTTP常见面试题 1、HTTP基本概念: 超文本传输协议:在计算机世界里专门在「两点」之间「传输」文字、图片、音频、视频等「超文本」数据的「约定和规范」HTTP常见的状态码 [[Pasted image 20250705140705.png]]HTTP常见字段 Host 字段:客户…

每日面试题20:spring和spring boot的区别

我曾经写过一道面试题,题目是为什么springboot项目可以直接打包给别人运行?其实这涉及到的就是springboot的特点。今天来简单了解一下springboot和spring的区别, Spring 与 Spring Boot:从“全能框架”到“开箱即用”的进化之路 …

ClickHouse数据迁移

ClickHouse实例是阿里云上的云实例,想同步数据到本地,本地部署有ClickHouse实例,下面为单库单表 源实例:阿里云cc-gs5xxxxxxx.public.clickhouse.ads.aliyuncs.com:8123 目标实例:本地172.16.22.10:8123 1、目标实例建…

sqli-labs-master/Less-41~Less-50

Less-41这一关还是用堆叠注入,这关数字型不需要闭合了。用堆叠的话,我们就不爆信息了。我们直接用堆叠,往进去写一条数据?id-1 union select 1,2,3;insert into users (id,username,password) values(666,zk,180)--看一下插进去了没?id-1 u…

Tiger任务管理系统-10

十是个很好美好的数字,十全十美,确实没让人失望,收获还是很大的。 温习了前端知识,巩固了jQuery,thymeleaf等被忽视的框架,意外将之前的所学所用的知识都连起来了,感觉有点像打通了任督二脉一样…

ora-01658 无法为表空间 users中的段创建initial区

ora-01658 无法为表空间 users中的段创建initial区 参考1 参考2 参考3 参考4 给用户新增表空间 alter tablespace system add datafile D:\APP\ADMINISTRATOR\ORADATA\ORCL\SYSTEM03.DBF size 5G autoextend on next 10M;设置表空间文件自动扩展 ALTER DATABASE DATAFILE /…

lodash的替代品es-toolkit详解

一、es-toolkit简介 es-toolkit 是一款先进的高性能 JavaScript 实用程序库,体积小巧,并支持强类型注释,典型特征包括: 提供各种日常实用函数并采用现代实现,例如: debounce、delay、chunk、sum 和 pick 等 设计充分考虑了性能,在现代 JavaScript 环境中实现了 2-3 倍…

【原创】基于gemini-2.5-flash-preview-05-20多模态模型实现短视频的自动化二创

画面和解说保持一致,这个模型就是NB[16:57:37] [*] 正在从视频中提取帧和时长 (频率: 1.0 帧/秒)... [16:57:55] [] 提取完成。视频时长: 83.40秒, 提取了 84 帧。 [16:57:55] [*] 使用AI供应商: gemini [16:57:55] [*] 正在进行视觉分析... [16:57:55] L-> 正…

数仓架构 数据表建模

数仓架构 主要用来描述 数据加工的实时链路 和 离线链路之间的关系,即 流批 关系; lamda 架构, 是两条路, 实时计算式的, 维护数据的实时性。然后每天经过批计算后, 覆盖实时的计算结果。 保证数据准确性。 kappa架构, 即流批一体了 数据建模 星型模型是数据仓库中最…

vscode调试python脚本时无法进入函数内部的解决方法

只需在launch.json配置文件中添加“justMyCode”:false.

Python day37

浙大疏锦行 python day37. 内容: 保存模型只需要保存模型的参数即可,使用的时候直接构建模型再导入参数即可 # 保存模型参数 torch.save(model.state_dict(), "model_weights.pth")# 加载参数(需先定义模型结构) mod…

ORACLE进阶操作

1 事务 事务的任务便是使数据库从一种状态变换成为另一种状态,这不同于文件系统,它是数据库所特用的。 所有的数据库中,事务只针对DML(增删改),不针对select select只能查看其他事务提交或回滚的数据,不能查…

Modbus 的一些理解

疑问:(使用的是Modbustcp)我在 Modbus slave 上面设置了slave地址为1,位置为40001的位置的值为1,40001这个位置上面的值是怎么存储的,存储在哪里的?他们是怎么进行交互的?在Modbus协…

【运动控制框架】WPF运动控制框架源码,可用于激光切割机,雕刻机,分板机,点胶机,插件机等设备,开箱即用

WPF运动控制框架源码,可用于激光切割机,雕刻机,分板机,点胶机,插件机等设备,考虑到各运动控制硬件不同,视觉应用功能(应用视觉软件)也不同,所以只开发各路径编…

RabbitMQ-日常运维命令

作者介绍:简历上没有一个精通的运维工程师。请点击上方的蓝色《运维小路》关注我,下面的思维导图也是预计更新的内容和当前进度(不定时更新)。中间件,我给它的定义就是为了实现某系业务功能依赖的软件,包括如下部分:Web服务器代理…

【Linux基础知识系列】第九十篇 - 使用awk进行文本处理

在Linux系统中,文本处理是一个常见的任务,尤其是在处理日志文件、配置文件和数据文件时。awk是一个功能强大的文本处理工具,广泛用于数据提取、分析和格式化。它不仅可以处理简单的文本文件,还可以处理复杂的结构化数据&#xff0…

第二十七天(数据结构:图)

图:是一种非线性结构形式化的描述: G{V,R}V:图中各个顶点元素(如果这个图代表的是地图,这个顶点就是各个点的地址)R:关系集合,图中顶点与顶点之间的关系(如果是地图,这个关系集合可能就代表的是各个地点之间的距离)在顶点与顶点…

数据赋能(386)——数据挖掘——迭代过程

概述重要性如下:提升挖掘效果:迭代过程能不断优化数据挖掘模型,提高挖掘结果的准确性和有效性,从而更好地满足业务需求。适应复杂数据:数据往往具有复杂性和多样性,通过迭代可以逐步探索和适应数据的特点&a…

什么是键值缓存?让 LLM 闪电般快速

一、为什么 LLMs 需要 KV 缓存?大语言模型(LLMs)的文本生成遵循 “自回归” 模式 —— 每次仅输出一个 token(如词语、字符或子词),再将该 token 与历史序列拼接,作为下一轮输入,直到…

16.Home-懒加载指令优化

问题1:逻辑书写位置不合理问题2:重复监听问题已经加载完毕但是还在监听