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

news2024/11/14 3:10:57

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

目录

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);
	//检查Pchar
	printf("Pchar存的地址值:%p\n", Pchar);
	printf("Pchar+1后的地址值:%p\n\n", Pchar + 1);
	//检查Pint
	printf("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); //除以int
	printf("pi1 - pi2 = %d\n\n", pi1 - pi2);//除以int

	char *pc = (char*)&arr[7] ;
	printf("pi2 - pc = %d\n", pi2 - pc);//除以int
	printf("pc - pi2 = %d\n\n", pc - pi2);//除以char

	short* psh = (short*)&arr[0];
	printf("pi2 - psh = %d\n", pi2 - psh);//除以int
	printf("psh - pi2 = %d\n\n", psh - pi2);//除以short

	printf("pc - psh = %d\n", pc - psh);//除以char
	printf("psh - pc = %d\n", psh - pc);//除以short
	return 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.coloradmin.cn/o/2074435.html

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

相关文章

力扣网页端无法进入(问题已解决)

力扣网页端无法进入&#xff08;问题已解决&#xff09; 这两天在刷leetcode的时候突然发现无法进入力扣主页&#xff0c;换了浏览器也不行&#xff0c;但其他网站都能正常进去&#xff0c;其它主机也可以。 可能是DNS解析错误 在实际应用过程中可能会遇到DNS解析错误的问题&am…

OpenCV Rect_< _Tp > 模版类详解及其成员函数用法示例

OpenCV Rect_< _Tp > 模版类是一个2维矩形模板类&#xff0c;其英文全称为Rect_< _Tp > Class Template Reference&#xff0c;其公有成员函数有以下几个&#xff1a; 其公有属性有&#xff1a; Rect_< _Tp > 模版类以左上角点tl&#xff0c;坐标_Tp x,Tp y及…

2023年高教社杯国赛b题详细代码 文章 教学 2024数模国赛教学: 多波束测深技术问题分析与建模

本系列专栏将包括两大块内容 第一块赛前真题和模型教学,包括至少8次真题实战教学,每期教学专栏的最底部会提供完整的资料百度网盘包括:真题、数据、可复现代码以及文章. 第二块包括赛中思路、代码、文章的参考助攻, 会提供2024年高教社国赛各个赛题的全套参考内容(一般36h内更新…

matlab与VS混合编程以及错误解决

目录 前言&#xff1a; 1. matlab打包生成dll文件 打包方法一&#xff1a; 打包方法二&#xff1a; 2. VS端配置 3. 代码测试 4. 错误解决 a. 1.0x0000000000000000 处有未经处理的异常(在 Project1.exe 中): 0xC0000005: 执行位置 0x0000000000000000 时发生访问冲突。…

Unity游戏开发——Unity脚本组件:游戏开发的灵魂

Unity游戏开发 “好读书&#xff0c;不求甚解&#xff1b;每有会意&#xff0c;便欣然忘食。” 本文目录&#xff1a; Unity游戏开发 Unity游戏开发Unity脚本组件&#xff1a;游戏开发的灵魂前言1.Standard Assets导入报错解决办法2. 什么是Unity脚本组件&#xff1f;3. 创建和…

vue 精选评论词云 集成echarts-wordcloud TF-IDF算法

这一期在我们的系统里集成词云组件&#xff0c;开发的功能是景区精选评论的词云展示功能。 这个界面的逻辑是这样的&#xff1a; 在数据框里输入城市&#xff0c;可以是模糊搜索的&#xff0c;选择城市&#xff1b; 选择城市后&#xff0c;发往后台去查询该城市的精选评论&a…

python,json数据格式,pyecharts模块,pycharm中安装pyecharts

json数据格式 JSON是一种轻量级的数据交互格式 可以按照JSON指定的格式去组织和封装数据 JSON本质上是一个带有特定格式的字符串 主要功能&#xff1a; json就是一种在各个编程语言中流通的数据格式&#xff0c;负责不同编程语言中的数据传递和交互. 类似于&#xff1a; 国…

P39-数据存储2

编程题 编程题 编程题

2024 年顶级 Flutter UI 框架和库

根据 2022 年 StackOverflow 调查显示&#xff0c;Flutter 是最受欢迎的跨平台工具之一。自发布以来的 16 个月内&#xff0c;已有超过 200 万开发者采用了 Flutter。在本博客中&#xff0c;我们将浏览 GitHub 上可用的顶级 Flutter 存储库。除了每个存储库之外&#xff0c;还提…

MySQL 系统学习系列 - 事务、视图与存储过程的使用《MySQL系列篇-06》

数据库事务、视图、存储过程 事务 1. 事务简介 事务&#xff08;transaction&#xff09;是指访问并更新数据库中各种数据的一个程序执行单元&#xff08;unit&#xff09; [最小执行单元] MySQL事务主要用于处理操作量大。复杂度高的数据 1.MySQL数据库只有InnoDB引擎支持事…

App应用冷启动耗时排查

1 查看冷启动耗时 adb shell am start -S -W com.gerry.lifecycle/com.gerry.lifecycle.MainActivity发现冷启动耗时居然要6s多&#xff0c;下面开始排查 2 生成trace文件 // Application中开始trace记录 override fun attachBaseContext(base: Context?) {super.attachBas…

虚幻5|简单的设置角色受到伤害,远程攻击机关设置,制作UI,低血量UI

虚幻5|制作玩家血量&#xff0c;体力&#xff08;还未编辑&#xff0c;只用于引用&#xff09;-CSDN博客 需完成制作玩家血量及体力部分 一.给角色添加死亡动画 1.为了保证角色在播放死亡蒙太奇的时候&#xff0c;不会重新播放&#xff0c;而是保持原来倒地的姿势&#xff0…

Renesa Version Board开发RT-Thread 之WIFI创建Client

概述 本文主要介绍使用Renesa Version Board中WIFI功能&#xff0c;该模块基于RW007模块设计&#xff0c;RT-Thread软件架构已经实现该硬件相关的驱动接口。笔者基于该模块的相关接口在LWIP软件框架的基础上实现Client功能。实现数据的发送和接收。 1 WLAN 框架简介 参考文档…

【Java】—— Java面向对象基础:使用Java创建和打印员工对象信息

在Java中&#xff0c;类的定义和使用是面向对象编程的核心。本文将通过一个简单的例子来展示如何定义一个员工类&#xff08;Employee&#xff09;&#xff0c;并在测试类中创建员工对象&#xff0c;为这些对象的属性赋值&#xff0c;并打印出它们的信息。 定义员工类&#xff…

大模型微调

文章目录 前言一、使用的库二、数据预处理1.引入库2.读入数据3.对数据进行预处理4.转换为json格式文件 三&#xff0c;使用算子分析数据并进行数据处理四&#xff0c;划分训练集和测试集五&#xff0c;编写训练脚本开始训练六&#xff0c;进行模型推理人工评估总结 前言 这是使…

网络优化|单源最短路|Dijkstra|Floyd|Matlab

图和网络可以用来描述集合元素和元素之间关系。大量的最优化问题都可以抽象为网络模型加以解释&#xff0c;描述和求解。 图与网络模型在建模时具有直观、易理解、适应性强等&#xff0c;广泛应用在管理科学、物理学、化学、计算机科学、信息论、控制论、社会科学以及军事科学等…

C# 循环访问目录树详解与示例

文章目录 一、目录树遍历的概念二、使用System.IO命名空间三、DirectoryInfo和FileInfo类四、递归遍历目录树五、示例&#xff1a;列出目录树中的所有文件和文件夹六、异常处理七、迭代方法八、总结 在C#中&#xff0c;访问文件系统是常见的需求之一。有时我们需要遍历目录树以…

嵌入式开发技术进步带来新机遇

嵌入式开发作为信息技术领域的重要分支&#xff0c;随着科技的不断进步&#xff0c;正迎来新的机遇。本文将从人工智能、物联网、边缘计算等方面探讨嵌入式开发技术的进步如何带来新的发展机遇&#xff0c;并展望未来的发展趋势。 一、引言 嵌入式系统是一种特殊的计算机系统&…

unreal engine5中character角色和怪物交互时发生穿模

UE5系列文章目录 文章目录 UE5系列文章目录前言一、原因定位二、解决方法 前言 在 Unreal Engine 5 中&#xff0c;角色“穿模”通常指的是角色模型与其他物体&#xff08;如墙壁、地面或其他对象&#xff09;发生不正确的穿透或重叠现象。这可能是由多种原因造成的&#xff0…

C++ | Leetcode C++题解之第372题超级次方

题目&#xff1a; 题解&#xff1a; class Solution {const int MOD 1337;int pow(int x, int n) {int res 1;while (n) {if (n % 2) {res (long) res * x % MOD;}x (long) x * x % MOD;n / 2;}return res;}public:int superPow(int a, vector<int> &b) {int an…