那些年与指针的爱恨情仇(一)---- 指针本质及其相关性质用法


关注小庄 顿顿解馋 (≧∇≦)

引言: 小伙伴们在学习c语言过程中是否因为指针而困扰,指针简直就像是小说女主,它逃咱追,我们插翅难飞…本篇文章让博主为你打理打理指针这个傲娇鬼吧~
本节我们将认识到指针本质,何为指针和她的性质用法,请放心食用!

指针本质及其性质用法

  • 一.指针的本质
    • 1.🏠 内存与地址
    • 2.🏠 指针变量与地址
      • 2.1 取址符(&)
      • 2.2指针变量
      • 2.3解引用操作符(*)
      • 2.4指针变量的大小
  • 二.指针的性质
    • 1.🏠指针类型的意义
      • 1.1 不同指针类型的解引用
      • 1.2指针+-整数
    • 2.🏠 const修饰指针
      • 2.1 const修饰变量
      • 2.2 const修饰指针变量
    • 3.🏠 指针运算
      • 3.1 指针+-整数
      • 3.2 指针-指针
      • 3.3指针的关系运算
  • 三.指针的使用
    • 1.🏠 野指针
    • 3.🏠assert断言
    • 4.🏠传值调用和传址调用
    • 在这里插入图片描述

一.指针的本质

1.🏠 内存与地址

内存单元:我们在学校寄宿时是几个人为一个集体为宿舍的,为了方便入住以及内务检查学校给每个宿舍设置了门牌号方便辨认。类似地,存储数据的内存划分一个一个内存单元同时给它们编上“号”方便取出数据。

内存单元:内存最小单元,单位是字节也就是8个bit。

地址:正如上言所说,我们给内存单元的编号就是地址,在c语言中我们叫它指针

总结: 指针 == 地址 == 内存单元编号 他是我们用来取数据的东西

2.🏠 指针变量与地址

数据在内存中的地址就是指针,类比之前我们学习过的数据类型,整形变量是存储整形的变量,那么指针变量就是存指针的变量

我们知道指针变量了先不着急,我们知道指针变量存的是地址,赋值两边要相同类型你知道怎么获得地址吗?

2.1 取址符(&)

取址符顾名思义就是取出地址,打印地址时我们用的是%p,在我们编译器如何查看地址呢?接下来上图

在这里插入图片描述
在vs上进入调试模式->调试->窗口->内存/监视窗口都可查看内存,别忘记加上我们的取址符哦

2.2指针变量

指针变量也是⼀种变量,这种变量就是⽤来存放地址的,存放在指针变量中的值都会理解为地址,他的定义如下:

Datatype * name;
int a = 10;
int * pf = &a;
char c = 'c';
char * pc = &c;

理解:这里的*用来表示这是一个指针变量,前面的Datatype表示的是指针指向的数据类型,而去掉变量名就是这个指针变量的类型,比如int* pf表示pf是个指针变量它指向的数据类型是int,存储的是int类型数据的地址,指针类型是int *

类比地,聪明的你应该也会类比其他数据类型了吧~

void*类型
在指针类型中有这么一个存在,它可以可以理解为⽆具体类型的指针(或者叫泛型指针),这种类型的指针可以⽤来接受任意类型地址。就像一个公共场所。

2.3解引用操作符(*)

有的小伙伴到这里可能要问了既然知道什么是指针变量,那他怎么用啊?快端上来罢!这里我们就要了解我们的解引用操作符了

客人房间(变量)房间编号(地址)
变量a0x11223344
变量b0x12332166
变量c0x12ffaa11

如上表所示,每个变量(房间)都对应一个编号(地址),而要进入房间使用它就得用解引用操作符

int a = 1;
printf("%d ",a);
int *p = &a;
*p = 10;
printf("%d",a);

输出结果是1 10

总结: 解引用操作符(*)作用是通过地址,以地址为媒介,找到地址指向的空间对该空间进行操作。

2.4指针变量的大小

我们知道不同的数据类型所占字节大小不同,那不同的指针类型是不是也是大小不同呢?接下来我们先普及一下编址的相关知识

在这里插入图片描述
CPU与内存之间是有大量数据交互的。大致过程如下:CPU先通过控制总线下命令->地址总线收到命令找对应地址->通过数据总线传输数据。在这个过程中地址总线不只图中一条哦,作者只是大概画出来。不同机器地址总线总数不同,比如32位机器有32条,64位有64条,每根线只有两态,表⽰0,1【电脉冲有⽆】,那么⼀根线,就能表⽰2种含义,2根线就能表⽰4种含义,依次类推。32根地址线,就能表⽰2^32种含义,每⼀种含义都代表⼀个地址

通过上面的知识普及我们了解到地址总线发出的电信号转换成二进制序列就是我们的地址,所以不同的机器就会产生不同位的二进制序列当做地址,32位地址->32个bit->4个字节,64位地址->64个bit->8个字节。

printf("%zd\n", sizeof(char *));
printf("%zd\n", sizeof(short *));
printf("%zd\n", sizeof(int *));
printf("%zd\n", sizeof(double *));

x86(32位环境) 输出结果为4 4 4 4
x64(64位环境) 输出结果为8 8 8 8

总结: 指针变量大小与它指向的数据类型无关,而与使用的机器有关

二.指针的性质

1.🏠指针类型的意义

既然指针变量大小与指针类型无关,那为什么还要设置这么多类型的指针变量?有句话叫做存在即合理,设置了它肯定是有它的意义的。

1.1 不同指针类型的解引用

//代码1
int n = 0x11223344;
int *pi = &n;
*pi = 0;
//代码2
int n = 0x11223344;
char* pc = &n;
*pc = 0;

两段代码都是存了n的地址,只不过是不同指针类型,那是否值都变为0了呢?我们用之前的查看内存知识观察下。
在这里插入图片描述
通过上面观察我们发现不同指针类型得出的结果也不同,由此我们可以得出结论:不同指针类型解引用权限不同,也就是能操作字节个数不同,正如上面的例子int解引用能将4个字节都置为0,而char解引用只能将1个字节置为0.

1.2指针±整数

int n = 10;
char *pc = (char*)&n;
int *pi = &n;
printf("%p\n", &n);
printf("%p\n", pc);
printf("%p\n", pc+1);
printf("%p\n", pi);
printf("%p\n", pi+1)

输出结果:
004FF700
004FF700
004FF701
004FF700
004FF704

我们可以看出, char* 类型的指针变量+1跳过1个字节, int* 类型的指针变量+1跳过了4个字节。
这就是指针变量的类型差异带来的变化。

结论:指针类型还决定了指针向前或向后走一步有多大。
注:之前提到的void*虽然能接收各种类型指针,但却不能使用指针加减也不能解引用,可谓是上天为你关了一扇门也为你打开一扇窗啊。

2.🏠 const修饰指针

2.1 const修饰变量

变量是可以修改的,如果把变量的地址交给⼀个指针变量,通过指针变量的也可以修改这个变量。
但是如果我们希望⼀个变量加上⼀些限制,不能被修改,怎么做呢?这就是const的作⽤。

int m = 0;
m = 20;//m是可以修改的
const int n = 0;
n = 20;//n是不能被修改的

加上const后n不能动了,那我们运用之前的解引用呢?

const int n = 0;
printf("n = %d\n", n);
int*p = &n;
*p = 20;
printf("n = %d\n", n);

我们可以看到这⾥⼀个确实修改了,但是我们还是要思考⼀下,为什么n要被const修饰呢?就是为了
不能被修改,如果p拿到n的地址就能修改n,这样就打破了const的限制,这是不合理的,所以应该让
p拿到n的地址也不能修改n,那接下来怎么做呢?

2.2 const修饰指针变量

我们给上几段代码来测试下

//代码1 无const
int n = 10;
int m = 20;
int *p = &n;
*p = 20;//能否赋值
p = &m; //能否赋值
//代码2  const在*左边
int n = 10;
int m = 20;
const int *p = &n;
*p = 20;//能否赋值
p = &m; //能否赋值
//代码3 const在*右边
int n = 10;
int m = 20;
int *const p = &n;
*p = 20;//能否赋值
p = &m; //能否赋值
//代码4 const在*左边和右边
int n = 10;
int m = 20;
int const *const p = &n;
*p = 20;//能否赋值
p = &m; //能否赋值

代码1 :都能赋值
代码2 : 语句1不能赋值
代码3 :语句2不能赋值
代码4: 两语句都不能赋值

总结:1.const如果放在*的左边,修饰的是指针指向的内容,保证指针指向的内容不能通过指针来改变,但指针变量本身的内容可变。2.const如果放在*的右边,修饰的是指针变量本身的内容,保证存储的内容不能修改,但指针指向内容可通过指针修改3.const在*的左边和右边则指针变量本身内容和指针指向内容都不能被修改。

总的来说,const修饰变量具有常量属性,但解引用相当于绕道找后门进入这个变量修改变量的值。此时用const修饰指针相当于把这个后门也关了

3.🏠 指针运算

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

3.1 指针±整数

根据我们前面分析,指针±整数表示不同指针向前一步或向后一步的大小。这在数组可以很好运用,我们知道数组在内存中是连续存放的,这样知道其中一个元素地址再运用指针±整数就能得到其他元素地址,我们可以用它来打印数组

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]);
for(i=0; i<sz; i++)
{
printf("%d ", *(p+i));//p+i 这⾥就是指针+整数
}

3.2 指针-指针

int arr[10] = {1,2,3,4,5,6,7,8,9,10};
printf("%d",&arr[2]-&arr[0]);
printf("%d",&arr[0]-&arr[2]);

输出结果为2 -2
我们可以得出指针-指针的绝对值得到的是指针与指针之间的元素个数,可以类比日期-日期是天数,而指针-指针、就是元素个数。前面的指针±也可以这样类比,日期加天数还是日期

注意:该运算的前提条件是相同类型的指针变量

3.3指针的关系运算

指针也是可以比较大小的,比较的是在内存位置的高低。

三.指针的使用

  接下来我们来了解一些关于指针的使用和注意事项吧

1.🏠 野指针

野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的),通常以下三种情况会造成野指针。

  1. 指针未初始化
  2. 指针越界访问
  3. 指针指向空间释放(一般指针调用函数后内存空间被回收)
//原因3
int* test()
{
int n = 100;
return &n;
}
int main()
{
int*p = test();
printf("%d\n", *p);
return 0;
}

这个地方*p接收了返回的&n地址,但是调用函数后n就被释放了,也就是我们常说的空有其表

规避方法:

  1. 指针初始化为NULL
  2. 小心越界访问
  3. 不要返回局部变量地址
  4. 指针不用了及时置为NULL

3.🏠assert断言

assert.h 头⽂件定义了宏 assert() ,用于在运⾏时确保程序符合指定条件,如果不符合,就报错终⽌运行这个宏常常被称为“断⾔”。assert() 宏接受⼀个表达式作为参数。如果该表达式为真(返回值⾮零), assert() 不会产⽣任何作⽤,程序继续运⾏。如果该表达式为假(返回值为零), assert() 就会报错,在标准错误stderr 中写⼊⼀条错误信息,显⽰没有通过的表达式,以及包含这个表达式的⽂件名和⾏号。

好处:
1.能⾃动标识⽂件和出问题的⾏号
2.有⽆需更改代码就能开启或关闭 assert() 的机制。(如果已经确认程序没有问题,不需要再做断⾔,就在 #include <assert.h> 语句的前⾯,定义⼀个宏NDEBUG)

4.🏠传值调用和传址调用

我们知道函数传值调用时形参是实参的一份临时拷贝,改变形参不影响实参,那有什么方法能改变我们的实参呢?答案是我们的传址调用。

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

输出结果
交换前 a=10 b=20
交换后 a=20 b=10

由此可以得出,传址调用本质上也是传值,也是拷贝地址的值传到函数内部,只不过地址指向的空间是唯一的,我们便能通过地址来改变它的空间。值得注意的是,在函数内部创建的px,py跟a,b不是同个变量只不过他们存储的值(地址)一样,存储的值能干的事一样!

在这里插入图片描述

本次分享到这里就结束了,希望小伙伴能够学到知识,喜欢的话给小庄来个三连吧!

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

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

相关文章

RHCE项目:使用LNMP搭建私有云存储

目录 一、准备工作 1、关闭防火墙、安全软件 2、搭建LNMP环境 3、上传软件 4、设置nextcloud安装命令权限 二、数据库 1、设置数据库 2、重启数据库 三、配置nginx 四、安装nextcloud 五、内网穿透 1、创建内网映射 2、linux系统安装花生壳客户端 3、重新打开浏览…

林浩然与极限的“无穷”约会

林浩然与极限的“无穷”约会 Lin Haoran’s Encounter with the Mathematical “Infinity” 在数学王国里&#xff0c;有一位名叫林浩然的大侠&#xff0c;他的江湖就是高等数学的殿堂。而他要挑战的终极Boss&#xff0c;便是那个既神秘又顽皮的“极限”。 In the kingdom of …

C# .Net6搭建灵活的RestApi服务器

1、准备 C# .Net6后支持顶级语句&#xff0c;更简单的RestApi服务支持&#xff0c;可以快速搭建一个极为简洁的Web系统。推荐使用Visual Studio 2022&#xff0c;安装"ASP.NET 和Web开发"组件。 2、创建工程 关键步骤如下&#xff1a; 包添加了“Newtonsoft.Json”&…

【Git】项目管理笔记

文章目录 本地电脑初始化docker报错.gitignoregit loggit resetgit statusgit ls-filesgit rm -r -f --cached拉取仓库文件更新本地的项目报错处理! [rejected] master -> master (fetch first)gitgitee.com: Permission denied (publickey).error: remote origin already e…

BACnet转IEC104网关BE104

随着电力系统信息化建设和数字化转型的进程不断加速&#xff0c;对电力能源的智能化需求也日趋增强。健全稳定的智慧电力系统能够为工业生产、基础设施建设以及国防建设提供稳定的能源支持。在此背景下&#xff0c;高性能的工业电力数据传输解决方案——协议转换网关应运而生&a…

研发日记,Matlab/Simulink避坑指南(六)——字节分割Bug

文章目录 前言 背景介绍 问题描述 分析排查 解决方案 总结归纳 前言 见《研发日记&#xff0c;Matlab/Simulink避坑指南&#xff08;一&#xff09;——Data Store Memory模块执行时序Bug》 见《研发日记&#xff0c;Matlab/Simulink避坑指南(二)——非对称数据溢出Bug》…

Arduino开发实例-DRV8833电机驱动器控制直流电机

DRV8833电机驱动器控制直流电机 文章目录 DRV8833电机驱动器控制直流电机1、DRV8833电机驱动器介绍2、硬件接线图3、代码实现DRV8833 使用 MOSFET,而不是 BJT。 MOSFET 的压降几乎可以忽略不计,这意味着几乎所有来自电源的电压都会传递到电机。 这就是为什么 DRV8833 不仅比基…

寒假思维训练day15 牛客练习赛121

牛客练习赛ABCD题解&#xff0c;更新一个题解作为今天的任务收尾。 寒假思维训练day15 摘要&#xff1a; Part1&#xff1a;B题&#xff0c;B-You Brought Me A Gentle Breeze on the Field_牛客练习赛121 (nowcoder.com) Part2: C题&#xff0c;C-氧气少年的水滴 2_牛客练…

职言圈 | 小伙年薪95w,女朋友父母却爱搭不理,如今上岸国家电网,人不到,叔叔阿姨吃饭都不动筷子...

在职场中&#xff0c;每个人都经历着不同的起伏和挑战。有时候&#xff0c;我们会面临着职业生涯上的起伏&#xff0c;但正是这些经历让我们成长&#xff0c;让我们更加坚韧。 就像这位网友一样&#xff0c;他在过去的一年里经历了美团L8的职位和年薪95W&#xff0c;却面临着女…

Sentinel解密:SlotChain中的SLot大揭秘

欢迎来到我的博客&#xff0c;代码的世界里&#xff0c;每一行都是一个故事 Sentinel解密&#xff1a;SlotChain中的SLot大揭秘 前言SlotChain简介&#xff1a;Sentinel的第一道防线入场仪式&#xff1a;SlotChain中的初始化SlotSlotChain的执行流程&#xff1a;从规则解析到流…

数据可视化练习

文章目录 试题示例 试题示例 绘制下图所示的表格 根据下表的数据&#xff0c;将班级名称一列作为x轴的刻度标签&#xff0c;将男生和女生两列的数据作为刻度标签对应的数值&#xff0c;使用bar()函数绘制下图所示的柱形图。 方式一 import numpy as np import matplotlib.p…

Kotlin快速入门系列3

Kotlin条件、循环控制 IF条件控制 与Java类似&#xff0c;一个if语句可包含布尔表达式和一条或多条语句。 fun compare(a:Int,b:Int) : Int{//常规传统用法var max aif (b>a) max breturn max//使用elsevar mMax : Intif(a>b){mMax a}else{mMax b}return mMax//使…

Unity中URP下额外灯的距离衰减

文章目录 前言一、额外灯的距离衰减二、DistanceAttenuation函数的传入参数1、distanceSqr2、distanceAndSpotAttenuation3、_AdditionalLightsAttenuation4、GetPunctualLightDistanceAttenuation函数三、DistanceAttenuation函数的程序体 前言 在上一篇文章中&#xff0c;我…

一个新的springboot-vue项目如何启动起来

一个新的springboot-vue项目如何启动起来 1.导入mysql 打开yml文件修改数据库密码 名称 用户名 2.打开pom.xml配置maven依赖 尽量换成自己使用过的版本号&#xff0c;或者打开中央仓库搜索相关内容版本号&#xff1a;https://central.sonatype.com/ 注解为黄色 说明工程…

《合成孔径雷达成像算法与实现》Figure5.17

clc clear close all距离向参数 R_eta_c 20e3; % 景中心斜距 Tr 25e-6; % 发射脉冲时宽 Kr 0.25e12; % 距离向调频率 Fr 7.5e6; % 距离向采样率 Nrg 256; % 距离线采样点数 Bw abs(Kr*Tr); …

@JsonIgnore的使用及相关问题的解决

目录 1 前言 2 对比及其使用方法 3 遇到的相关问题及解决方法 1 前言 在我们编写的后端项目中&#xff0c;有时候可能需要将某个实体类以JSON格式传送给前端&#xff0c;但是其中可能有部分内容我们并不想传送&#xff0c;这时候我们选择将这部分内容变成Null&#xff0c;这…

Flutter 点击空白处关闭软键盘,点击非TextField 关闭软键盘的方法

1&#xff1a;点击空白处(非控件上)关闭软键盘。 此方法有个问题&#xff0c;就是点击非空白区域&#xff0c;不会关闭软键盘&#xff0c;比如点击旁边的其他按钮&#xff0c;则软键盘还在。只适合点击空白处关闭软键盘 在 main.dart 入口 build 中增加 builder: (context, ch…

一文速通Python添加、修改和删除字典元素

添加、修改和删除字典元素是 Python 中使用字典时常见的操作。字典是一种无序、可变的数据结构&#xff0c;用于存储键值对。在 Python 中&#xff0c;对字典元素进行添加、修改和删除操作可以帮助我们动态地管理数据&#xff0c;方便地根据需求对字典进行更新和维护。 一、添…

图文解析交流慢充原理和握手协议以及OBC工作原理

1.接口定义 2.硬件连接原理 2.obc工作原理 OBC里面包括单片机1和单片机2&#xff0c;DSP。 有的厂家方案只有一个单片机&#xff0c;CC/CP部分直接用DSP实现。交流桩的ARM控制K1、K2&#xff0c;S1。单片机1控制K3。单片机2控制S2。DSP控制K4。BMS控制PDU里面的K5&#x…

使用Opencv-python库读取图像、本地视频和摄像头实时数据

使用Opencv-python库读取图像、本地视频和摄像头实时数据 Python中使用OpenCV读取图像、本地视频和摄像头数据很简单&#xff0c; 首先需要安装Python&#xff0c;然后安装Opencv-python库 pip install opencv-python然后在PyCharm或者VScode等IDE中输入对应的Python代码 一…