指针之旅(2)——const修饰词 野指针、空指针与泛型指针

目录

Part one(一)

1. const关键字

1.1 const修饰普通变量

1.1.1 const的作用

1.1.2 指针绕过const

1.2 const修饰指针变量

1.2.1 const在 * 前

1.2.2 const在 * 后

1.2.3 双重const修饰

Part two(二)

1. 野指针

* 野指针的成因

2. 泛型指针 void*

特性1

特性2

特性3

3. 空指针 NULL

4. assert 断言 

5. 如何规避野指针


Part one(一)

1. const关键字

const可以修饰普通变量,也可以修饰指针变量,两种场景会有所差异。

1.1 const修饰普通变量

1.1.1 const的作用

const用来声明一个普通变量后,一旦该变量被初始化,它的值将不能再被直接改变。(不能再通过赋值改变)

创建格式:

(1) const 数据类型 变量名 = 初始值;        //如:const int a = 10;

(2) 数据类型 const 变量名 = 初始值;        //如:int const b = 34;

(至于为什么会有两种格式,你可以在 知识点1.2 找到答案)

注意:const修饰的变量只能被初始化,不能被赋值,否则会报错。

(补充:在C语言中,被const修饰的变量并不是常量,而是常变量;但在C++中,被const修饰的变量是常量。详细请看《数组 基础知识 和 冷知识(超详细总结)》中的常变量冷知识)

1.1.2 指针绕过const

以一个例子引入:

int main()
{int m = 0;m = 20;//m是可以修改的const int n = 0;n = 20;//n是不能被修改的return 0;
}

n被const修饰后,在语法上加了限制。只要我们在代码中对n就⾏修改,就不符合语法规则并报错,致使没法直接修改n。

但是如果我们绕过n,使⽤n的地址,去修改n就能做到了。(虽然这样做违反了语法规则

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

运行结果:

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

1.2 const修饰指针变量

首先我们要明确一点:const 修饰的对象是变量,而不会是数据类型。(在判断const对什么起作用时,可以去掉数据类型再判断。)

1.2.1 const在 * 前

作用:const如果放在*的左边,修饰的是指针指向的内容,保证指针指向的内容不能通过指针来改变。 但是指针变量的值可变,也就是指针存入的地址可改变(可改变地址指向)。

创建格式:(由于const对数据类型没有影响,所以有2种写法)

(1) const 数据类型 *变量名 = 初始值;        //如:const int *p1 = &a;

(2) 数据类型 const *变量名 = 初始值;        //如:int const *p2 = &b;

例如:

const int a = 10;

const int *p = &a;

去掉数据类型再判断:

const *p = &a;

可以发现const此时修饰的是*p,所以*p的值不能改变

而*p的值就是a的值,所以此时不能再通过*p间接改变a的值,这下const a成为了真正意义上的不可改变的变量。(没错,它在c语言中还属于变量)

代码演示:

1.2.2 const在 * 后

作用:const如果放在*的右边,修饰的是指针变量本⾝,保证了指针变量的地址指向不能修改。但是指针指向的内容,可以通过指针改变。

创建格式:

数据类型* const p = 初始值;        //如:int* const p = &c; 

例如:

int a = 10;

int *const p = &a;

去掉数据类型再判断:

*const p = &a;

可以发现const此时修饰的是p,所以p的值不能改变

而p装的是地址,所以指针p的地址指向不能变

代码演示:

1.2.3 双重const修饰

那我们能不能既限定指针变量不能改变地址指向,也要求它不能改变所指向的内容?

答案是肯定的,我们可以用2个const来修饰指针变量。

创建格式:

const 数据类型* const 变量名 = 初始值;        //如:const int* const p = &d;

Part two(二)

1. 野指针

概念:野指针是一种非法的指针,它所储存的地址具有(1)随机性、不可知性(2)越界访问性和危险性。[这两种性质一般不会同时存在]

* 野指针的成因

成因1:指针未初始化

int main()
{int* p;    局部变量指针未初始化,默认为随机值*p = 20;return 0;
}

这也是性质(1)的由来。 

成因2:指针越界访问

int main()
{int arr[10] = { 0 };int* p = &arr[0];int i = 0;for (i = 0; i <= 11; i++){当指针指向的范围超出数组arr的范围时,p就是野指针*(p++) = i;}return 0;
}

这也是性质(2)的由来。  

成因3:指针指向的空间被释放

int* test()
{int n = 100;return &n;
}
int main()
{int* p = test();printf("%d\n", *p);return 0;
}

这也是性质(1)的由来 。

函数被调用时,变量n才会被分配内存空间,函数调用结束会收回n的空间。此时返回的地址值是一个随机值。补充一下,成因3所说的指针指向的空间被释放,还包括malloc来的空间被free掉的情况。

野指针的产生我们是有办法规避的,但在讲规避方法之前,我想再补充几个必要的知识点并加以区分。

2. 泛型指针 void*

误区:有些同学会说,void类型被称为空类型,那void*一定就是空指针了。

这样理解是错误的。void*其实被叫作泛型指针,可以理解为⽆具体类型的指针;而空指针NULL其实是泛型指针中的一个特殊值(具体内容后面会说)。

相比于其他类型的指针,void*有这几种特性:

特性1

void*指针与其他指针一样,大小都是4个字节(32位环境)或8个字节(64位环境)。

int main()
{printf("int型的大小是:%zd\n", sizeof(int*));printf("char型的大小是:%zd\n", sizeof(char*));printf("void型的大小是:%zd\n", sizeof(void*));return 0;
}

以X64环境为例:

(至于大小为什么固定在4和8,详细请看《指针之旅(1)—— 指针基础概念知识》)

特性2

泛型指针可以⽤来接收任意类型地址。(所以有时候void*被称为万能指针)

如果将⼀个int类型的变量的地址,赋值给⼀个char*类型的指针变量,编译器给出一个提醒:

虽然这样写能成功通过编译,但这并不是正确的使用方法。因为指针变量不仅仅是用来存储地址这么简单,指针变量的数据类型还决定着指针访问内存的权限大小。(具体请看《指针之旅(1)—— 指针基础概念知识》中的“指针的访问范围”这一部分知识点)

但如果用void*的指针来存储这个int型的指针就完全没有问题

特性3

void* 类型的指针不能直接进行(1)指针的+-整数(2)解引⽤的运算。 

我们知道,void类型本身没有长度。所以void*指针只能用来接收,它本身不具有访问地址的权限

不知道每次访问的内存字节有多少,就无法进行指针的+-整数和解引⽤的运算。

【但有一种方法可以使得void*类型的指针也能进行指针的+-整数和解引⽤的运算,那就是强制类型转换。等到时候《指针之旅(5)》写出来的时候会讲,本篇重点在于区分野指针、空指针和void*指针】

3. 空指针 NULL

概念:NULL是C语⾔中定义的⼀个标识符常量(这一种宏定义),被称作空指针,它的值是0

注意:0也是地址,这个地址是⽆法使⽤的,访问该地址会报错。(不止是0,访问其他比较低的地址也会报错,这是系统底层的地址,不允许随意更改的)

我们来看一下关于NULL的宏定义:

#ifdef __cplusplus#define NULL 0
#else#define NULL ((void *)0)
#endif

从中我们可以看到,NULL的值是0,而且NULL算是一个void*类型的指针。

4. assert 断言 

assert是一个宏定义,用于在程序中插入断言语句。使用前要包含头文件<assert.h>

assert的用法格式:

1.        assert( exp );

//exp是表达式

assert的运行逻辑:

(1)当exp为真时,程序会继续运行下去。

(2)当exp为假时,会提前终止程序,并提示你在第XX行触发了断言

比如:

运行结果:

我们看到,assert触发后并没有执行“printf("a是%d", a);”,而是提前终止了程序,并打印出错误信息。(这个作用在代码量非常多的情况很有用,可以快速知道程序错误的位置并加以修改)

补充:assert() 的缺点是,因为引⼊了额外的检查,增加了程序的运⾏时间。 ⼀般我们可以在 Debug 中使⽤,在 Release 版本中选择禁⽤ assert 就⾏,在 VS 这样的集成开 发环境中,在 Release 版本中,直接就是优化掉了。这样在debug版本写有利于程序员排查问题, 在 Release 版本不影响用户使⽤时程序的效率。

5. 如何规避野指针

❶.指针初始化阶段:

如果明确知道指针指向哪⾥就直接赋值地址

如果不知道指针应该指向哪⾥,可以给指针赋值NULL,读写该地址会报错。(这样可以防止对随机值地址的非法访问,因为只有访问NULL时会报错)

❷.⼩⼼指针越界

⼀个程序向内存申请了哪些空间,通过指针也就只能访问哪些空间,不能超出范围访问,超出了就是 越界访问。

❸.指针变量不再使⽤时,及时置NULL; 指针使⽤之前用assert检查有效性

当指针变量指向⼀块区域的时候,我们可以通过指针访问该区域,后期不再使⽤这个指针访问空间的 时候,我们可以把该指针置为NULL。

因为⼀个约定俗成的规则就是:只要是NULL指针就不去访问, 同时使⽤指针之前可以判断指针是否为NULL。

    假设有一个指针int *p,判断p是不是野指针可以用aseert(p) 或者 assert(p != NULL)。

我们可以把野指针想象成野狗,野狗放任不管是⾮常危险的,所以我们可以找⼀棵树把野狗拴起来, 就相对安全了,给指针变量及时赋值为NULL,其实就类似把野狗栓前来,就是把野指针暂时管理起来。

不过野狗即使拴起来我们也要绕着⾛,不能去挑逗野狗,有点危险;对于指针也是,在使⽤之前,我 们也要判断是否为NULL,看看是不是被拴起来起来的野狗,如果是不能直接使⽤,如果不是我们再去使⽤。


本期关于指针的知识分享完毕,感谢大家的支持Thanks♪(・ω・)ノ

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

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

相关文章

Sentence-BERT实现文本匹配【回归目标函数】

引言 上篇文章我们通过Sentence-Bert提出的分类目标函数来训练句子嵌入模型&#xff0c;本文同样基于Sentence-Bert的架构&#xff0c;但改用回归目标函数。 架构 如上图&#xff0c;计算两个句嵌入 u \pmb u u和 v \pmb v v​之间的余弦相似度&#xff0c;然后可以使用均方误…

Webpack详解与配置环境

webpack&#xff1a;webpack网址 1、工作原理&#xff1a; Webpack是一个非常强大的静态模块的打包工具。从文件入口开始&#xff0c;递归解析以来关系&#xff0c;然后将所有模块打包成一个或多个budle文件。 2、webpack核心概念&#xff1a; Entry&#xff1a;入口起点(en…

【STM32+HAL库】---- 通用定时器实现外部脉冲计数

硬件开发板&#xff1a;STM32G0B1RET6 软件平台&#xff1a;cubemaxkeilVScode1 新建cubemax工程 1.1 配置系统时钟RCC 1.2 配置定时器 选择通用定时器TIM2&#xff0c;时钟源选择ETR2&#xff0c;对应的输入端口为PA0引脚&#xff0c;预分频系数为0&#xff0c;重装载值选择…

Python简易IDE工作界面制作

、 休闲一下&#xff0c;学习编程还是要学习一些界面编程&#xff0c;能够根据需要制作图形操作界面&#xff0c;这样我们开发的程序才能方便操作和使用&#xff0c;同时获得更友好的人机交互体验。下面是一个用PyQt5制作的简易界面&#xff0c;供大学参考。如下图所示&a…

在SpringMVC中用fmt标签实现国际化/多语言

SpringMVC中用fmt标签实现国际化主要解决界面的多语言化&#xff0c;ftm标签会根据浏览器的语言值来先择对应的文件配置&#xff0c;如中文简体的浏览器值是zh_CN,那么ftm标签就会用以zh_CN.properties结尾的配置文件中的key来取值&#xff0c;从而实现自多语言的自动切换&…

自动化运维之SaltStack 部署应用

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:Linux运维老纪的首页…

【Steam游戏星露谷物语添加Mod步骤】

Steam游戏星露谷物语添加Mod步骤 星露谷物语添加拖拉机模组一、安装SMAPI二、正式开始添加MOD 星露谷物语添加拖拉机模组 一、安装SMAPI 星露谷物语添加拖拉机mod为例&#xff0c;添加其它mod一样的步骤。 首先&#xff0c;打开Steam&#xff0c;打开一次星露谷物语这款游戏&…

论文速读纪录 - 202408

特别鸣谢kimi&#xff0c;以下论文均在kimi辅助下阅读。 目录 RMIB: Representation Matching Information Bottleneck for Matching Text RepresentationsAttentionRank: Unsupervised keyphrase Extraction using Self and Cross AttentionsANSWERING COMPLEX OPEN-DOMAIN …

标准库标头 <optional> (C++17)学习之optional

类模板 std::optional 管理一个可选 &#xfeff;的所含值&#xff0c;即既可以存在也可以不存在的值。 一种常见的 optional 使用情况是作为可能失败的函数的返回值。与如 std::pair<T, bool> 等其他手段相比&#xff0c;optional 可以很好地处理构造开销高昂的对象&a…

DataGridView用法合集【精品】

1.当前的单元格属性取得、变更 [VB.NET] Console.WriteLine(DataGridView1.CurrentCell.Value) Console.WriteLine(DataGridView1.CurrentCell.ColumnIndex) Console.WriteLine(DataGridView1.CurrentCell.RowIndex) DataGridView1.CurrentCell DataGridView1(0, 0) [C#] Con…

虚幻引擎VR游戏开发02 | 性能优化设置

常识&#xff1a;VR需要保持至少90 FPS的刷新率&#xff0c;以避免用户体验到延迟或晕眩感。以下是优化性能的一系列设置&#xff08;make sure the frame rate does not drop below a certain threshold&#xff09; In project setting-> &#xff08;以下十个设置都在pr…

基于php+vue+uniapp的医院预约挂号系统小程序

开发语言&#xff1a;PHP框架&#xff1a;phpuniapp数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;PhpStorm 系统展示 后台登录界面 管理员功能界面 用户管理 医生管理 科室分类管理 医生信息管理 预…

机器人外呼有哪些优势?

机器人外呼&#xff0c;作为一种结合了计算机技术和人工智能技术的自动化工具&#xff0c;具有多重显著优势。以下是其主要优势的详细阐述&#xff1a; ### 1. 高效性 * **大幅提升工作效率**&#xff1a;机器人外呼可以全天候、不间断地进行工作&#xff0c;不受时间、地点和…

【优质源码】3D多人在线游戏,前端ThreeJS,后端NodeJS

3D多人在线游戏 【源码】3D多人在线游戏源码&#xff0c;前端ThreeJS&#xff0c;后端NodeJS&#xff0c;完整源码。 游戏画面 启动方法 先启动服务器端。 在目录&#xff0c;3D-multi-player-main\3D-multi-player-main\nodeapps\blockland 中&#xff0c;运行&#xff1a…

Elasticsearch之原理详解

简介 ES是使用 Java 编写的一种开源搜索引擎&#xff0c;它在内部使用 Lucene 做索引与搜索&#xff0c;通过对 Lucene 的封装&#xff0c;隐藏了 Lucene 的复杂性&#xff0c;取而代之的提供一套简单一致的 RESTful API 然而&#xff0c;Elasticsearch 不仅仅是 Lucene&#…

设计模式及创建型模式-python版

1 架构模式与设计模式 架构模式搞层次的设计模式&#xff0c; 描述系统整体结构和组织方式&#xff0c;设计模式是针对某个问题的解决方案&#xff0c;是一种解决问题的思路。 2 设计模式的分类 2.1 创建型模式 单例模式&#xff0c;工厂方法模式&#xff0c;抽象工厂模式&…

JVM2-JVM组成、字节码文件、类的生命周期、类加载器

目录 Java虚拟机的组成 字节码文件 字节码文件打开方式 字节码文件的组成 基本信息 Magic魔数 主副版本号 常量池 字段 方法 属性 字节码常用工具 javap jclasslib插件 Arthas 类的生命周期 概述 加载阶段 连接阶段 验证 准备 解析 初始化阶段 类加载器…

linux 下一跳缓存,early demux(‌早期解复用)‌介绍

3.6版本以后的下一跳缓存 3.6版本移除了FIB查找前的路由缓存。这意味着每一个接收发送的skb现在都必须要进行FIB查找了。这样的好处是现在查找路由的代价变得稳定(consistent)了。3.6版本实际上是将FIB查找缓存到了下一跳(fib_nh)结构上&#xff0c;也就是下一跳缓存下一跳缓存…

ESP32无线WiFi芯片模组,设备物联网连接通信,产品智能化交互升级

在数字化浪潮的推动下&#xff0c;我们正步入一个万物互联的新时代。物联网&#xff08;IoT&#xff09;技术&#xff0c;作为连接物理世界与数字世界的桥梁&#xff0c;正逐渐渗透到我们生活的每一个角落。 乐鑫正通过其创新的无线WiFi芯片模组&#xff0c;为这些领域的发展提…

界面控件DevExpress中文教程:如何使用AI扩展Excel计算?

DevExpress WinForms拥有180组件和UI库&#xff0c;能为Windows Forms平台创建具有影响力的业务解决方案。DevExpress WinForms能完美构建流畅、美观且易于使用的应用程序&#xff0c;无论是Office风格的界面&#xff0c;还是分析处理大批量的业务数据&#xff0c;它都能轻松胜…