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


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

引言: 小伙伴们在学习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.mfbz.cn/a/351882.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

将Html页面转换为Wordpress页面

问题&#xff1a;我们经常会从html源码下载网站上获得我们想要的网站内容框架&#xff0c;以及部分诸如联系我们&#xff0c;About 等内页&#xff0c;但是在文章的发布上&#xff0c;则远不如Wordpress简便。而Wordpress尽管有各种模板&#xff0c;但修改又比较麻烦。解决方法…

day34WEB 攻防-通用漏洞文件上传黑白盒审计逻辑中间件外部引用

目录 一&#xff0c;白盒审计-Finecms-代码常规-处理逻辑 黑盒思路&#xff1a;寻找上传点抓包修改突破获取状态码及地址 审计流程&#xff1a;功能点-代码文件-代码块-抓包调试-验证测试 二&#xff0c;白盒审计-CuppaCms-中间件-.htaccess 三&#xff0c;白盒审计-Metin…

CVE-2024-23897 Jenkins 任意文件读取漏洞

项目介绍 Jenkins是一个开源软件项目&#xff0c;是基于Java开发的一种持续集成工具&#xff0c;用于监控持续重复的工作&#xff0c;旨在提供一个开放易用的软件平台&#xff0c;使软件项目可以进行持续集成。Jenkins是开源CI&CD软件领导者&#xff0c; 提供超过1000个插…

guhub访问

问题 访问不了guhub 解决过程 可以正常访问了 总结 此方法参考微软官方的方法&#xff0c;修改dns服务器网址&#xff0c;由原本的自动分配改为指定微软dns服务器网址

深度强化学习(王树森)笔记02

深度强化学习&#xff08;DRL&#xff09; 本文是学习笔记&#xff0c;如有侵权&#xff0c;请联系删除。本文在ChatGPT辅助下完成。 参考链接 Deep Reinforcement Learning官方链接&#xff1a;https://github.com/wangshusen/DRL 源代码链接&#xff1a;https://github.c…

苹果手机突然无服务了,这是怎么回事?

苹果手机无疑是一款备受青睐的智能设备&#xff0c;但有时候我们可能会面临一个令人困扰的情况——苹果手机突然无服务。这种情况可能会在任何时候发生&#xff0c;无论是在使用手机时&#xff0c;刚刚升级系统&#xff0c;或者是突然发现在本应有信号的区域却无法连接到运营商…

Linux——常用命令

1、命令的基本格式 对服务器来讲&#xff0c;图形界面会占用更多的系统资源&#xff0c;而且会安装更多的服务、开放更多的端口&#xff0c;这对服务器的稳定性和安全性都有负面影响。其实&#xff0c;服务器是一个连显示器都没有的家伙&#xff0c;要图形界面干什么&#xff…

Nodejs基础4之fs模块的批量重命名练习、path模块、HTTP协议

Nodejs基础 fs模块fs练习-批量重命名fs练习-批量排序重命名 path模块resolve 解决sep 分隔符parse 方法其他的几个方法 HTTP协议http报文安装fiddler软件配置fiddler使用fiddler 请求报文结构整体请求行请求方法URLHTTP版本号 请求头请求体 响应报文结构整体响应行响应状态码响…

用甘特图有效管理多个项目进度

当公司或组织同时承担多个项目时,合理规划各项目的时间节点与资源分配对确保高效完成至关重要。采用甘特图可以直观地展示多个项目的时间进程、关键里程碑以及资源分配情况,便于从宏观层面全面把控各项目的动态。 在线甘特图软件 zz-plan.com 提供了非常强大的时间轴规划功能,支…

14、Kafka ------ kafka 核心API 之 流API(就是把一个主题的消息 导流 到另一个主题里面去)

目录 kafka 核心API 之 流APIKafka流API的作用&#xff1a;流API的核心API&#xff1a;使用流API编程的大致步骤如下&#xff1a;代码演示 流API 用法MessageStream 流API 代码演示消息从 test1主题 导流到 test2主题演示使用匿名内部类对消息进行处理Topology 拓扑结构 讲解 代…

程序员该懂的一些测试(二)单元测试与集成测试

集成测试才是目的 单元测试只是对每个小模块进行的测试&#xff0c;可以理解成每次提交的一个小功能。 最终系统的良好运行依赖于这些小模块之间良好协作。所以需要有集成测试 集成测试回滚数据&#xff0c;保证测试的可重复性 我们可以把 ApplicationContext 理解成 DI 容器…

Transformer and Pretrain Language Models3-6

Pretrain Language Models预训练语言模型 content&#xff1a; language modeling&#xff08;语言模型知识&#xff09; pre-trained langue models(PLMs&#xff09;&#xff08;预训练的模型整体的一个分类&#xff09; fine-tuning approaches GPT and BERT&#xff08;…

【探索科技 感知未来】文心一言大模型

【探索科技 感知未来】文心大模型 &#x1f6a9;本文介绍 文心一言大模型是由中国科技巨头百度公司研发的一款大规模语言模型&#xff0c;其基于先进的深度学习技术和海量数据训练而成。这款大模型具备强大的自然语言处理能力&#xff0c;可以理解并生成自然语言&#xff0c;为…

JVM-字节码应用

一、字节码的应用远超你的想象 二、ASM介绍与读取字节码实战 用CoreAPI解析和TreeAPI都能做字节码解析&#xff0c;区别&#xff0c;TreeAPI必须读取完整字节码信息&#xff0c;才能做解析。 下面代码&#xff0c;使用CoreAPI做解析&#xff1a; package asm;public class MyM…

leetcode.2846. 边权重均等查询【lca+树形dp】

原题链接&#xff1a;2846. 边权重均等查询 题目描述&#xff1a; 现有一棵由 n 个节点组成的无向树&#xff0c;节点按从 0 到 n - 1 编号。给你一个整数 n 和一个长度为 n - 1 的二维整数数组 edges &#xff0c;其中 edges[i] [ui, vi, wi] 表示树中存在一条位于节点 ui …

微服务-微服务Alibaba-Nacos注册中心实现

1. 系统架构的演变 俗话说&#xff0c; 没有最好的架构&#xff0c;只有最合适的架构。 微服务架构也是随着信息产业的发展而出现的最有普 遍适用性的一套架构模式。通常来说&#xff0c;我们认为架构发展历史经历了这样一个过程&#xff1a;单体架构——> 垂直架构 ——&g…

leetcode 42.接雨水

问题1&#xff1a;怎么算接水量 总的接水量第一列接水量第二列接水量第三列接水量…最后一列接水量 问题2&#xff1a;当前列的接水量怎么计算 当前的接水量min(当前列左边最高的墙x1&#xff0c;当前列右边最高的墙x3&#xff09;- 当前列x2的高度 问题2图解&#xff1a; …

蓝桥杯-sort排序(上)

sort排序 &#x1f388;1.算法说明&#x1f388;2.例题&#x1f52d;2.1例题一&#x1f52d;2.2例题二&#x1f52d;2.3例题三&#x1f52d;2.4例题四&#x1f52d;2.5例题五&#x1f52d;2.6例题六 &#x1f388;1.算法说明 &#x1f50e;对于一个数组&#xff0c;通过对数组中…

新版UI界面影视小程序亲测无问题带详细搭建教程

新版UI界面影视小程序亲测无问题带详细搭建教程 环境php7.0 — fileinfo–redis–sg11 mysql5.5 apache2.4 添加站点php7.0—-创建ftp—-上传后端文件《后端文件修改&#xff0c;/maccms/wxapi/config/dbs.php–修改当前数据库》—-设置ssl—-打开数据库安装cms 安装好后管…

如何写出有效的单元测试?

什么是单元测试 《单元测试的艺术》中对单元测试的定义&#xff1a; 一个单元测试是一段自动化的代码&#xff0c;这段代码调用被测试的工作单元&#xff0c;之后对这个单元的单个最终结果的某些假设进行校验。 单元测试几乎都是用单元测试框架编写的&#xff1b;只要产品代码…