指针之旅(1)—— 指针基础概念知识(详细解析)

前言:该篇我将详细讲解指针当中的一些基本概念,有内存和地址的部分硬件知识,有专门服务于指针的操作符&和*,有指针大小固定不变的原因,还有专属于指针的运算规则。

目录

1. 内存和地址

1.1 内存地址的概念(指针的概念)

1.2 理解编址【硬件知识的补充】

2. 指针相关的操作符

2.1 取地址操作符(&)

2.2 解引用操作符(*)

2.2.1 指针变量创建和拆分理解(操作符 * 在不同情况下的意义)

2.2.2 小知识:下标引用操作符[ ]的本质

3. 指针变量的大小(通用性质)

4.指针的访问范围

4.1 证明1:指针的解引用

 4.2 证明2:指针+-整数的地址跳过

5. 指针的运算(常用于数组)

5.1 指针+-整数

5.2 指针++、--

5.3 指针 - 指针

5.4 指针的关系运算(地址的高低比较)

6. 传值调用和传址调用


1. 内存和地址

1.1 内存地址的概念(指针的概念)

引入一个生活中的案例加入你所在的学生宿舍楼有100个房间,你的朋友来你宿舍玩,假如房间没有编号,就得挨个房⼦去找,这样效率很低;但是我们如果根据楼层和楼层的房间的情况,给每个房间编上号( 如102, 306, 404等),你的朋友对照房间号就可以快速找到房间。

⽣活中,每个房间有了房间号,就能提⾼效率,能快速的找到房间。如果把上⾯的例⼦对照到计算机中,⼜是怎么样呢?

内存单元:其实计算机中也是把内存划分为⼀个个的内存单元,每个内存单元的大小取1个字节

补充:8比特(bit)=1字节(byte),1024比特 = 1KB,1024KB=1MB ……电脑上的运行内存一般是8GB/16GB/32GB

在这个生活案例中,一个学生宿舍等于一个内存单元,这个宿舍可以住8个“比特人”。而门牌号对应的就是内存单元的编号,在生活中也被称为地址,C语⾔中给地址起了个新的名字叫:指针

所以我们可以理解为: 内存单元的编号 == 地址 == 指针

1.2 理解编址【硬件知识的补充】

因为内存中字节很多,所以需要给内存进⾏编址(就如同宿舍很 多,需要给宿舍编号⼀样)。计算机中的编址,并不是把每个字节的地址记录下来,⽽是通过硬件设计完成的。

但是硬件与硬件之间是互相独⽴的,那么如何通信呢?答案很简单,⽤"线"连起来。 ⽽CPU和内存之间也是有⼤量的数据交互的,所以两者必须也⽤线连起来。

其中影响最大的一组线被称作地址总线,32位机器有32根地址总线, 每根线只有两态,表⽰0,1【电脉冲有⽆】,那么⼀根线,就能表⽰2种含义,2根线就能表⽰4种含 义,依次类推。32根地址线,就能表⽰2^32种含义(组合),每⼀种含义(组合)都代表⼀个地址。

地址信息被下达给内存,在内存上,就可以找到该地址对应的数据,将数据在通过数据总线传⼊ CPU内寄存器。


2. 指针相关的操作符

2.1 取地址操作符(&)

取地址操作符(&):用于获取变量的内存地址可以应用于任何数据类型的变量,包括基本数据类型( 如:整型、浮点型、字符型 )和复合数据类型( 如:结构体、数组、联合体)。

基本语法:

1.     &变量名                //比如:int *p = &a

%p打印格式%p是专门用来打印地址的(即:打印指针变量),以16进制输出地址。

int main()
{int a = 0;printf("a的地址是:%p", &a);return 0;
}

可以看到,地址是以16进制打印的,而且数字前面不会有“0X”。

2.2 解引用操作符(*)

解引用操作符(*):指针变量是专门⽤来存放地址的变量,对指针使用解引用操作符(*),其实是对地址进行解引用操作,从而间接访问该地址存放的信息

间接访问包括可读取、可修改:

int main()
{int a = 0;int* pa = &a;//间接访问————读取printf("读取到a的值:%d\n", *pa);//间接访问————修改*pa = 10;printf("修改后a的值:%d\n", a);return 0;
}

2.2.1 指针变量创建和拆分理解(操作符 * 在不同情况下的意义

指针变量创建的基础语法

1.     数据类型  *指针变量名;                

符号* 不是解引用的意思吗,为什么这里也有符号*,要怎么理解这里的符号*?

符号*的多重作用:其实符号* 的作用除了进行解引用操作,还有声明指针变量的作用

(1)在指针变量的创建或初始化阶段,符号*的作用是声明指针变量;

(2)出去第1种情况,其他情况都是解引用操作。

假如现在有int a=0以及int *p=&a这两条语句:

int *p的拆分解读:

int* p说明变量p是个指针,相当于函数的声明。操作*p相当于函数的调用。函数调用前必须先声明,指针也一样,像这里变量a不是指针变量却使用操作*a是会报错的

 


补充:其实在变量创建时,符号* 紧贴“数据类型” 和 紧贴“指针变量名” 的效果是一样的。(如:int*  p和int  *p)

需要注意的是:1个符号* 只能声明它身后最近的1个变量名是指针,不能1个符号* 声明多个变量名

比如:

这里的变量pa、pb、pc,只有pa是被符号*声明成指针变量,而pb、pc都是普通整型变量。

如果想让pa、pb、pc都是指针,要这样写:“ int *pa,*pb,*pc; ”。


 

2.2.2 小知识:下标引用操作符[ ]的本质

其实对于数组arr来说,我们对其进行下标引用,其实在计算机中会自动转换成解引用,再对数据进行运算等操作。( arr[数字] == *(arr + 数字) )

比如:语句arr[3] = 0。计算机会先把“arr[3]”转换成“*(arr + 3)”,再对arr+3那里的地址进行间接访问。

3. 指针变量的大小(通用性质

(1)前⾯的内容我们了解到,32位机器有32根地址总线,每根地址线出来的电信号转换成数字信号后 是1或者0,那我们把32根地址线产⽣的2进制序列当做⼀个地址,那么⼀个地址就是32个bit位,需要4个字节才能存储。如果指针变量是⽤来存放地址的,那么指针变的⼤⼩就得是4个字节的空间才可以。

(2)同理在64位机器中,有64根地址线,⼀个地址就是64个⼆进制位组成的⼆进制序列,存储起来就需要 8个字节的空间,指针变量的⼤⼩就是8个字节。

我们用代码测试一下:

int main()
{printf("%zd\n", sizeof(void*));printf("%zd\n", sizeof(char*));printf("%zd\n", sizeof(short*));printf("%zd\n", sizeof(int*));printf("%zd\n", sizeof(double*));return 0;
}

结论:

• 32位平台下地址是32个bit位,指针变量⼤⼩是4个字节

• 64位平台下地址是64个bit位,指针变量⼤⼩是8个字节

• 注意指针变量的大小和类型是⽆关的,只要指针类型的变量,在相同的平台下,大小都是相同的。

4.指针的访问范围

首结论:指针的类型决定了,指针解引⽤的时候有多大的权限(⼀次能间接访问几个字节)

4.1 证明1:指针的解引用

我们用两段不同的代码比较验证:(VS的内存窗口:存放的数值以小端字节序排列,这里看不懂为什么显示“44332211”是无所谓的)

当我们再次按下F10,n地址中的数据“11223344”会被修改成什么?

按下F10后,代码1的4个字节都被修改成0,n的值从“11223344”变成了“00000000”;代码2的最低位的1个字节被修改成0,n的值从“11223344”变成了“11223300”。所以打印的最终结果也不一样:


 4.2 证明2:指针+-整数的地址跳过

以下面的代码举例:

int main()
{int n = 10;char* Pchar = (char*)&n;int* Pint = &n;printf("变量n的地址:%p\n\n", &n);//检查Pcharprintf("Pchar存的地址值:%p\n", Pchar);printf("Pchar+1后的地址值:%p\n\n", Pchar + 1);//检查Pintprintf("Pint存的地址值:%p\n", Pint);printf("Pint+1后的地址值:%p\n", Pint + 1);return 0;
}

我创建了1个int型的变量n,然后我用 字符指针Pchar 和 整型指针Pint 都指向变量n(都存入n的地址)。现在我对Pchar和Pint都分别加一,看看它们存的地址值是否都是简单地加1?

一个内存单元是一个字节,而字符指针Pchar的访问权限是1个字节,整型指针Pint的访问权限是4个字节。char* 类型的指针变量+1跳过1个字节, int* 类型的指针变量+1跳过了4个字节。 这就是指针变量的类型差异带来的变化。

次结论:指针的类型决定了指针向前或者向后⾛⼀步有多大(的距离)


5. 指针的运算(常用于数组)

5.1 指针+-整数

因为数组在内存中是连续存放的,只要知道第⼀个元素的地址,顺藤摸⽠就能找到后⾯的所有元素。比如像下面这样操作:

int main()
{int arr[10] = {1,2,3,4,5,6,7,8,9,10};int *p = &arr[0];      //获取首元素的地址int sz = sizeof(arr)/sizeof(arr[0]);for(int i=0; i<sz; i++)printf("%d ", *(p+i));  //p+i这⾥就是指针+整数return 0;
}

补充:前面说了,计算机会把arr[i]自动转换成*(arr + i),所以把这里的 *(p+i) 换成 p[i] 结果也是一样的。

5.2 指针++、--

指针的加加减减规则与普通变量是类似的。(关于普通变量的++--,详细请看数学计算类操作符 和 算术类型转换中的++--部分)

不同的是:指针++--之后的地址值变化与指针的类型有关。

取地址&、解引用* 与 指针++-- 的优先级:

  1. 取地址运算符&的优先级最高,它用于获取变量的内存地址。
  2. 解引用运算符*的优先级次之,它用于访问指针所指向的内存地址的值。
  3. 指针加加减减操作符(++--)的优先级最低,它们用于改变指针的值。

比如*p++:先对指针p进行解引用,访问到p所指向的内存的数据,再对该数据进行++操作。

5.3 指针 - 指针

 指针 - 指针的意义在于,表示两个指针之间相隔的元素数量,而不是简单的内存地址之差

 比如:

int main()
{int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };int *p1 = &arr[0];  int* p2 = &arr[4];printf("p2 - p1 = %d\n", p2 - p1);  //p2与p1隔了4个元素return 0;
}

 

p1与p2之间隔了4个元素,而p1到p2共5个元素。


指针 - 指针的真实计算方式:

        结果 == 内存地址之差 / 被减指针的类型权限

以下面的代码为例:(pi是整型指针,pc是字符型指针,psh是短整型指针)

int main()
{int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };int *pi1 = &arr[0];  int* pi2 = &arr[4];printf("pi2 - pi1 = %d\n", pi2 - pi1); //除以intprintf("pi1 - pi2 = %d\n\n", pi1 - pi2);//除以intchar *pc = (char*)&arr[7] ;printf("pi2 - pc = %d\n", pi2 - pc);//除以intprintf("pc - pi2 = %d\n\n", pc - pi2);//除以charshort* psh = (short*)&arr[0];printf("pi2 - psh = %d\n", pi2 - psh);//除以intprintf("psh - pi2 = %d\n\n", psh - pi2);//除以shortprintf("pc - psh = %d\n", pc - psh);//除以charprintf("psh - pc = %d\n", psh - pc);//除以shortreturn 0;
}

结果如下:


补充:不存在 指针+指针、指针*指针、指针/指针、指针%指针 以及 指针的连减,上述这些操作都没有意义,编译器会报错的。

5.4 指针的关系运算(地址的高低比较)

指针的关系运算符包括<、<=、>、>=,这些运算符比较两个指针的大小,但这种比较仅在它们都指向同一个数组中的元素时才有意义。

比如:

int main()
{int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };int* p = &arr[0];int i = 0;int sz = sizeof(arr)/sizeof(arr[0]);while (p < arr + sz)     //指针的⼤⼩⽐较{printf("%d ", *p);p++;}return 0;
}

这里的条件“p < arr + sz”,就是为了防止数组的越界访问。


6. 传值调用和传址调用

学习指针的⽬的是使⽤指针解决问题,那什么问题,⾮指针不可呢?

例如:写⼀个函数,交换两个整型变量的值。

⼀番思考后,我们可能写出这样的代码:

代码1:传值调用
void Swap1(int x, int y)
{int t = x;x = y;y = t;
}int main()
{int a = 0;int b = 0;scanf("%d %d", &a, &b);printf("交换前:a=%d b=%d\n", a, b);Swap1(a, b);printf("交换后:a=%d b=%d\n", a, b);return 0;
}

当我们运⾏代码,结果如下:

其实通过监视窗口(自己调试一下),我们可以发现:main函数内部创建了a和b,在Swap1函数内部创建了形参x和y接收a和b的值,x和y确实接收到了a和b的值,不过x的地址和a的地址不 ⼀样,y的地址和b的地址不⼀样,相当于x和y是独⽴的空间那么在Swap1函数内部交换x和y的值, ⾃然不会影响a和b。

Swap1函数在使⽤ 的时候,是把变量本⾝直接传递给了函数,这种调⽤函数的⽅式我们之前在函数的时候就知道了,这 种叫传值调用

传址调用:可以让函数和主调函数之间建⽴真正的联系,在函数内部可以修改主调函数中的变量;所 以未来函数中只是需要主调函数中的变量值来实现计算,就可以采⽤传值调⽤。如果函数内部要修改 主调函数中的变量的值,就需要传址调⽤。

所以我们写出新的版本:

void Swap2(int* px, int* py)
{int tmp = 0;tmp = *px;*px = *py;*py = tmp;
}int main()
{int a = 0;int b = 0;scanf("%d %d", &a, &b);printf("交换前:a=%d b=%d\n", a, b);Swap2(&a, &b);printf("交换后:a=%d b=%d\n", a, b);return 0;
}

结果如下:

我们可以看到实现成Swap2的⽅式,顺利完成了任务,这⾥调⽤Swap2函数的时候是将变量的地址传 递给了函数,这种函数调⽤⽅式叫:传址调⽤。


本期分享完毕,谢谢大家的支持Thanks♪(・ω・)ノ

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

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

相关文章

<数据集>非洲动物识别数据集<目标检测>

数据集格式&#xff1a;VOCYOLO格式 图片数量&#xff1a;1504张 标注数量(xml文件个数)&#xff1a;1504 标注数量(txt文件个数)&#xff1a;1504 标注类别数&#xff1a;4 标注类别名称&#xff1a;[buffalo, elephant, rhino, zebra] 序号类别名称图片数框数1buffalo3…

水凝胶与柔性电子啥关系?能用来干啥?

大家好&#xff0c;今天我们来聊一聊一篇关于水凝胶在柔性电子领域应用的文章——《Smart materials for flexible electronics and devices: hydrogel》发表于《RSC Advances》。随着科技的不断发展&#xff0c;柔性电子设备越来越受到关注&#xff0c;而水凝胶作为一种具有独…

Apache Flink内存模型

Flink 内存模型 大数据中所有开源的框架都会使用到JVM&#xff0c;不如&#xff0c;MapReduce&#xff0c;Storm&#xff0c;Spark等&#xff0c;这些计算框架处理数据过程中涉及到将大量数据存储到内存中&#xff0c;此时如果内存管理过渡依赖JVM&#xff0c;会出现java对象存…

docke进阶---镜像迁移、容器的ip地址、端口映射和持久化

1.镜像的迁移 1.镜像打包 #查看镜像有一个centos的镜像 [rootdocker0 ~]# docker images REPOSITORY TAG IMAGE ID CREATED SIZE centos latest 5d0da3dc9764 2 years ago 231MB 3查看帮助文件 docker --help save Save one or more…

stm32-SD卡实验

1. SD简介 SD卡&#xff0c;Secure Digital Card&#xff0c;称为安全数字卡&#xff08;安全数码卡&#xff09;。 SD卡系列主要有三种&#xff1a;SD卡(full size)、MiniSD卡和MicroSD卡&#xff08;原名 TF卡&#xff09;。 特点&#xff1a;容量大、高安全性、体积小、传…

打包资料优化目录

这篇文章主要写一下这一次更新的几个地方&#xff0c;有对原来的代码及模型进行优化的部分&#xff0c;也有新增加的代码和模型&#xff0c;我就把几个比较典型的给列了出来。但是还有好多的更新没有在下面展示出来&#xff0c;因为一个个展示出来太复杂了。如果你对更新的内容…

暑期算法训练

目录 A.糖果&#xff08;Candy) B.小红的数组重排 C.牛牛与LCM D.子串 E.勤奋的杨老师 F.清楚姐姐跳格子 G.方块 I H.PUBG A.糖果&#xff08;Candy) 思路 &#xff1a;贪心&#xff0c;为了使操作数最少&#xff0c;我们要尽可能的先吃第二个盒子里的糖果&#x…

UE5.4 - 下载和安装

一. 简介 虚幻引擎&#xff08;Unreal Engine&#xff09;是由 Epic Games 公司推出的一款功能强大的游戏开发引擎。它于 1998 年推出第一代&#xff0c;其口号是 “全球最开放、最先进的实时 3D 创作工具”。 虚幻引擎被广泛应用于游戏产业&#xff0c;创作出了众多知名的 3…

【工具类】Java优雅的将XML转为JSON格式、XML转JSON

Java优雅的将XML转为JSON格式、XML转JSON 1. 导入依赖1.1 Maven使用1.2 Gradle使用 2. 代码编写3.运行示例 1. 导入依赖 1.1 Maven使用 <dependency><groupId>org.dom4j</groupId><artifactId>dom4j</artifactId><version>2.1.3</vers…

黑神话悟空,高清壁纸、原画,游戏截图

黑神话悟空&#xff0c;高清壁纸、原画&#xff0c;游戏截图&#xff1a; 链接&#xff1a;https://pan.quark.cn/s/cd17c05c4f33

【STM32】驱动LCD

没买LCD屏&#xff0c;没有上机实践&#xff0c;只是学习了理论。 大部分图片来源&#xff1a;正点原子HAL库课程 专栏目录&#xff1a;记录自己的嵌入式学习之路-CSDN博客 目录 1 屏幕接口 2 屏幕驱动的基本步骤 3 8080时序的各信号线 4 8080的读和写 5 屏…

二分查找算法:朴素二分+左右边界二分力扣实战应用

目录&#xff1a; 1、二分查找算法简介 2、算法原理及时间复杂度分析 2.1 朴素二分算法 3.2 查找左右边界的二分算法 3.2.1 查找左边界 3.2.2 查找右边界 3.3 时间复杂度分析 3、二分查找算法模版 3.1 朴素二分模版 3.2 查找左右边界的二分模版 4、算法应用【leetco…

考研数学暑期进度大调查,你掉队了吗?

现在已经8月&#xff0c;马上快9月份了&#xff0c;你的数学进度学到哪里啦&#xff1f; 我可不是“进度哥“&#xff0c;也不会营造焦虑&#xff0c;其实对于进度这个事情&#xff0c;我一直觉得是一个伪命题&#xff0c;因为很多同学一直鼓吹进度多快&#xff0c;结果最后考…

合宙LuatOS开发板使用说明——Air700ECQ

EVB-Air700ECQ-IO 开发板是合宙通信推出的基于 Air700ECQ 模组所开发的&#xff0c;包含电 源&#xff0c; SIM 卡&#xff0c;USB &#xff0c;天 线&#xff0c; 全 IO 引 出的最 小硬 件系 统。以 方便 用户 在设 计前期 对 Air700ECQ 模块进行性能评估&#xff0c;功能调试…

Hadoop集群运维管理

Hadoop集群运维管理 一、Hadoop 集群进程管理1.1 NameNode 守护进程管理1.2 DataNode 守护进程管理1.3 ResourceManager 守护进程管理1.4 NodeManager 守护进程管理 二、Hadoop 集群运维技巧2.1 查看日志2.2 清理临时文件2.3 定期执行负载均衡2.4 文件系统检查2.5 元数据备份 三…

Maven的一些相关知识【重修】《包括私服搭建!》

mvnrepository.com Maven 下载jar包的位置&#xff01; 【该部分有教程】 这是什么nb代码投稿视频-这是什么nb代码视频分享-哔哩哔哩视频 MAVEN 的私服搭建&#xff1a; https://zhuanlan.zhihu.com/p/520107316 2、maven私服搭建及应用&#xff08;下&#xff09;_哔哩…

SQL手工注入漏洞测试(PostgreSQL数据库)

判断注入点 and 12 判断回显点 order 不用 4 页面正常 order by 5 页面异常&#xff0c;得出只存在四个字段 测试回显位置 and 12 union select null,null,null,null and 12 union select null,null,null,null and 12 union select null,null,null,null and 12 union select…

如何在不格式化的情况下解锁 Android 智能手机密码

如果您忘记密码&#xff0c;您的 Android 移动设备将锁定您。发生这种情况时&#xff0c;通常可以通过恢复出厂设置来重新获得对设备的访问权限。可悲的是&#xff0c;这将导致所有数据丢失。下面列出的是解锁锁定的Android 手机而不会丢失任何个人数据的有效方法。 Android 手…

Open3D 近似点体素滤波(36)

Open3D 近似点体素滤波(36) 一、算法介绍二、算法实现1.代码2.效果一、算法介绍 这个算法也是体素滤波, 它保留的点是近似点,也就是新的点,原始点云中对应位置是不存在这些点的。其他的看着类似,下面是代码,滤波抽稀结果 二、算法实现 1.代码 代码如下(示例): …

Long Short-Term Memory

这篇论文总结的太抽象了&#xff0c;只是翻译了一遍。 &#xff08;我太笨了&#xff0c;如果把这个当我的入门读物&#xff0c;我觉着会把我折磨坏&#xff09; 递归神经网络的一个重要优点是它们在映射输入和输出序列时使用上下文信息的能力。不幸的是&#xff0c;对于标准的…