C++指针详解

news2024/10/6 10:28:44

指针

目录

  • 内存变量
  • 指针变量
    • 对指针进行赋值
    • 指针占用的内存
  • 使用指针
    • 指针用于函数参数
    • 用const修饰指针
  • void关键字
  • C++内存模型
  • 动态分配内存new 和 delete
  • 二级指针
  • 空指针
  • 野指针
  • 一位数组和指针
    • 数组的地址
    • 数组的本质
    • 数组名不一定会被解释为地址
    • 一维数组作为函数参数
    • 用new动态创建一维数组
  • 二维数组用于函数参数
    • 二维数组名是行地址
    • 把二维数组作为参数
    • 使用new动态创建二维数组
  • 多维数组
  • 函数指针和回调函数
    • 回调函数
  • 指针函数

内存变量

变量的全称是内存变量

在这里插入图片描述

以上例子中,cout会将地址转换成字符串进行输出,就有可能出现乱码的的情况

void*类型返回的是16进制的地址,longlong类型返回的是10进制的地址

指针变量

指针变量简称指针,是一种特殊的变量,专门用来存放变量在内存中的起始地址

对指针进行赋值

地址都是16进制的数,但是指针是有类型的标识符,指针的名字也是标识符,不能与其他变量重名

int *pa = &a;

对指针赋值的操作可以叫做指针指向某个变量,被指向的类型叫做基类型

指针占用的内存

在64位操作系统中,都是8字节

可以把int*当成一种数据类型

使用指针

声明指针变量后,不初始化的话,其中是乱七八糟的值,这时候不能使用指针

*运算符是解引用运算符,用于指针,可以得到该地址的内存中存储的值。

变量和指向变量的指针就像同一枚硬币的两面

在这里插入图片描述

每次运行程序的时候,变量的地址都是随机分配的,不一样

指针只是记录了变量在内存中的起始地址,类型决定了数据占用内存的大小以及如何操作这个数据

在这里插入图片描述

指针用于函数参数

指针作为参数的时候,在函数中通过解引用的方法可以修改实参的值

地址传递

  • 可以在函数中修改实参的值,作为传出参数
  • 减少内存拷贝,提升性能,每个指针都是8字节,但是要拷贝的内容可能很大,像字符串,数组什么的

const修饰指针

常量指针

const 数据类型* 变量名

不能通过解引用的方法修改内存地址中的值,用原始的变量名是可以修改的

  • 可以修改指针的指向,但是没有什么意义
  • 一般用于函数形参,表示在函数中不能用解引用的方式修改常量的值

指针常量

数据类型* const 变量名

指向的对象不能改变,所以在声明的时候必须初始化

  • 可以通过解引用的方式修改内存地址中的值
  • C++编译把指针常量做了一些特别的处理,改头换面之后,新名字叫做引用

常指针常量

指向的对象不能改变,不能通过解引用的方法修改内存地址中的值

常引用

void关键字

  • 表示无类型,用于函数返回值
  • 用于函数形参,表示无参数
  • 函数的形参使用void *,表示接受任意数据类型的指针

注意

  • 不能用void 来声明变量,不能表示一个真实的变量
  • 不能对void*指针直接解引用(需要转换成其他类型的指针)
  • 把其他类型的指针赋值给void*指针不需要转换
  • 把void*指针赋值给其他类型的指针需要转换

在c++中返回字符类型的时候会有乱码,可以先转成void*类型再输出

C++内存模型

在这里插入图片描述

程序运行后,代码段中的内容是不会改变的

  • 栈:存储局部变量、函数参数和返回值
  • 堆:存储动态开辟内存的变量
  • 数据段:存储全局变量和静态变量
  • 代码段:存储可执行程序的代码和常量

堆和栈的主要区别

在这里插入图片描述

动态分配内存new 和 delete

使用堆区内存有四个步骤

  1. 声明一个指针
  2. 使用new运算符向系统中申请一块内存,让指针指向这块内存
  3. 通过对指针解引用的方法,像使用变量一样使用这块内存
  4. 如果这块内存不存在了,使用delete运算符来释放

内存只分配不释放,后果是非常严重的,可能会用尽系统的内存

动态分配出来的内存没有变量名,只能通过指向它的指针来操作内存中的数据

动态分配的内存生命周期跟程序相同,程序退出时,如果没有释放,系统将自动回收

就算指针的作用域已经失效,所指向的内存也不会释放

用指针跟踪已分配的内存时,不能跟丢

二级指针

二级指针的变量中存放的是指针的地址

使用指针的目的

  • 传递地址
  • 存放动态分配的内存的地址

在函数中,如果传递普通变量的地址,形参使用指针,传递指针变量的地址,形参使用二级指针为了修改指针的地址

在这里插入图片描述

多级指针除了套娃没有任何意义

空指针

  • 如果对空指针解引用,程序会崩溃。
  • 如果对空指针使用delete运算符,系统将忽略该操作,不会出现异常。所以,内存被释放后,也应该把指针指向空。
  • 在函数中,应该有判断形参是否为空指针的代码,目的是保证程序的健壮性。

为什么空指针访问会出现异常

NULL指针分配的分区:其范围是从 0x000000000x0000FFFF。这段空间是空闲的,对于空闲的空间而言,没有相应的物理存储器与之相对应,所以对这段空间来说,任何读写操作都是会引起异常的。空指针是程序无论在何时都没有物理存储器与之对应的地址。为了保障“无论何时”这个条件,需要人为划分一个空指针的区域,固有上面NULL指针分区。

简而言之,系统中有专门的空指针分区,这段空间是空闲的,没有相应的物理存储对应,所以读写操作都会出错。说白了就是NULL指针没有真实的内存数据对应

nullptr等价于(void *)0

野指针

指针指向的不是一个有效或者说合法的地址

在程序中,如果访问野指针,可能会造成程序的崩溃

为什么是可能?

打人犯法吗,犯法;一定会近派出所吗,不一定;如果野指针指向的内存是内核或者其他非空闲的内存,就会导致程序的崩溃,如果是空闲的内存,就不会崩溃,但是没有人能保证那块内存是不是空闲的

出现的情况

  1. 指针在定义的时候,如果没有进行初始化,它的值是不确定的
  2. 如果用指针指向了动态分配的内存内存被释放后,指针不会置空,但是指向的地址已经失效
  3. 指针指向的变量已经超越变量的作用域,让指针指向了函数的局部变量,或者把函数的局部变量的地址作为返回值赋值给了指针

规避方法

针对上面三种情况

  1. 初始化,没有地方指的话就初始化为nullptr
  2. 动态分配的内存被释放后,指针指向nullptr
  3. 函数不要返回局部变量的地址

一位数组和指针

对地址加1操作往后移动的长度跟数据类型有关,地址往后移动的长度是 1 * sizeof(数据类型)

数组的地址

double a[5];
cout << a << &a << &a[0] << endl;

以上三个值是相同的,都是数组a的地址

C++编译器把数组名[下标]解释为*(数组首地址 + 下标)

C++编译器把**地址[下标]解释为*(地址 + 下标)**

数组的本质

数组是占用连续空间的一块内存,数据名被解释成数组第0个元素的地址,C++操作这块内存有两种方法:数组解释法和指针表示法,它们是等价的。

数组名不一定会被解释为地址

在**sizeof**运算符运用于数组名的时候,返回的是整个数组占用内存空间的字节数

可以修改指针的值,数组名是常量,不可以修改

可以p++,但是不能a++

一维数组作为函数参数

只能传数组的地址,并且把数组长度也传递进去

书写方法有以下两种

void func(int* arr, int len);
void func(int arr[], int len);

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wd3yuzEn-1683617233351)(image/image_R_EISMeJ1S.png)]

指针作为参数传入函数的时候,进行**sizeof**运算,返回就永远是8了,因为被当作是指针,而不再是数组了

用new动态创建一维数组

创建语法:数据类型* 指针 = new 数据类型[数组长度]

释放语法:delete[] 指针

注意

  • 动态创建的数组没有数组名,不能使用sizeof运算符,得到的永远是8

在这里插入图片描述

// 如果内存不够,程序不会崩溃,而是会返回空地址
int *a = new (std::nothrow) int[10000001];

二维数组用于函数参数

行指针(数组指针)

在这里插入图片描述

对一维数组取地址,得到的是行地址

在这里插入图片描述

这里有一个比较有意思的现象,a和&a的值是相同的,但是a+1与&a+1的值是不同的,原因在于&a是一个行地址。在方阵中,一个人的下一个位置是她旁边的那个,下一行的位置是下一行中正后面的那个人。

二维数组名是行地址

int bh[2][3] = {{11, 12, 13}, {21, 22, 23}};

其中,bh是二维数组名,该数组有两个元素,每个元素本身又是一个数组长度为3的整型数组。

bh被解释为数组长度为3的整形数组的行指针。

如果存放bh的值,要用数组长度为3的整型数组类型的行指针

int (*p)[3] = bh;

把二维数组作为参数

声明如下

void func(int (*p)[3], int len);
void func(int p[][3], int len);

使用new动态创建二维数组

int **mat = new int*[n];
for (int i = 0; i < n; i ++)
    mat[i] = new int[n];

多维数组

在实际开发中应用场景很少

**memset**函数

memset(数组名, 0, sizeof(数组名));

函数指针和回调函数

函数指针的主要用途是函数的回调

函数的二进制代码存放在内存四区中的代码段,函数的地址是它在内存中的起始地址,如果把函数的地址作为参数传递给函数,就可以在函数中灵活的调用其他函数

声明函数指针的时候也必须提供函数的类型,函数的类型指的是返回值和参数列表,函数名和形参名不是

// 原函数
int func1(int bh, string str);
bool func2(int id, string info);

// 函数指针
int (*pf1)(int, string);
bool (*pf2)(int, string);

// 对函数指针赋值
pf1 = func1;
// 使用函数指针调用函数
int no = 1;
string message = "测试";
pf1(no, message);

应用场景,回调函数

回调函数

需要在封装好的函数中调用一个自定义的个性化函数

如视频中的表白问题

表白公司可以提供一个封装好的表白流程,处理准备工作和收尾工作,但是中间你自己要操作的部分是你自己需要定义好的,所以表白流程的函数用一个函数指针作为参数,在其函数体内部调用自定义的表白函数。

这种情况下只能使用函数指针,没有别的方法

回调函数在多线程和网络通讯中很常用

指针函数

返回值是地址的函数是指针函数,感觉这个概念是一个伪命题。

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

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

相关文章

学习Maven Web 应用

Maven Web 应用 本章节我们将学习如何使用版本控制系统 Maven 来管理一个基于 web 的项目&#xff0c;如何创建、构建、部署已经运行一个 web 应用。 创建 Web 应用 我们可以使用 maven-archetype-webapp 插件来创建一个简单的 Java web 应用。 打开命令控制台&#xff0c;…

bootstrap-fileinput文件上传、回显使用详解

文件上传bootstrap-fileinput使用详解 一、增加页面的文件上传&#xff08;一&#xff09;bootstrap-fileinput文件上传功能1.先引入js依赖2.在html上面写入文件框 &#xff08;二&#xff09;根据若依给出的示例引用和改进&#xff08;三&#xff09;根据uploadUrl进行上传&am…

博客管理系统--显示、退出、发布功能

6&#xff1a;显示用户信息7&#xff1a;退出登录总结博客管理系统 6&#xff1a;显示用户信息 这部分&#xff1b;希望是根据用户来显示&#xff1b;如果是博客列表页则显示登录用户的信息&#xff1b;如果是博客详情页则显示作者的信息。 1&#xff1a;前后端交互接口约定 …

如何压缩pdf文件大小?四种方法随意选择

如何压缩pdf文件大小&#xff1f;PDF文件格式由于其跨平台性&#xff0c;易于浏览、打印和传输等特点&#xff0c;在现代社会中广泛应用于各个领域。然而&#xff0c;随着PDF文件越来越大&#xff0c;传输及存储所需的时间也会变得越来越长&#xff0c;从而降低了工作效率。在这…

简单记忆clarke和park坐标变换

简单记忆clarke和park坐标变换 简介 想用简单的办法把这些变换矩阵写出来&#xff0c;需要的时候可以使用&#xff0c;不用再去翻书&#xff08;当然完全记住还是更快一些&#xff09;。只是自己用来记忆这些变换的方法。 具体可以参考&#xff1a;手撕系列&#xff08;2&am…

【Redis】实现及优化分布式锁:实现、解决误删锁问题以及lua脚本确保redis操作原子性

目录 一、概念及不同分布式锁实现的对比 1、概念 2、特征 3、不同分布式锁实现的对比 二、Redis实现分布式锁的思路 1、获取锁思路 2、释放锁思路 三、代码实现分布式锁 1、准备 2、获取锁 2、释放锁 四、分布式锁的误删锁问题 1、问题 2、原因 五、误删锁的解决…

分布式配置中心

一、Config概述 Spring Cloud Config 解决了在分布式场景下多环境配置文件的管理和维护 好处&#xff1a; 集中管理配置文件 不同环境不同配置&#xff0c;动态化的配置更新 配置信息改变时&#xff0c;不需要重启即可更新配置信息到服务 二、Config 快速入门 1、使用git…

5.10-5.11总结

我教的课中 课程双击事件&#xff0c;跳转到课程界面 输入学生姓名和学号&#xff0c;添加学生 加载学生名单&#xff0c;双击学生&#xff0c;弹出学生资料&#xff0c;并且可以删除学生 但删除学生还有bug。

LeetCode - 1552 两球之间的磁力

目录 题目来源 题目描述 示例 提示 题目解析 算法源码 题目来源 1552. 两球之间的磁力 - 力扣&#xff08;LeetCode&#xff09; 题目描述 在代号为 C-137 的地球上&#xff0c;Rick 发现如果他将两个球放在他新发明的篮子里&#xff0c;它们之间会形成特殊形式的磁力。…

二维各向同性介质弹性波数值模拟(交错网格有限差分法)

一、一阶速度-应力弹性波方程 在二维各向同介质xoz平面内&#xff0c;假定体力为0。 从上面方程当中&#xff0c;我们为了得到各点的应力和速度值&#xff0c;就需要得到关于对时间t和空间x&#xff0c;z的偏导。 二、时间上的2M阶差分 由Taylor公式得 三、空间2N阶近似差分…

知识推理——CNN模型总结(持续更新)

记录一下我看过的利用CNN实现知识推理的论文。 最后修改时间&#xff1a;2023.05.10 目录 1.ConvE 1.1.解决的问题 1.2.优势 1.3.贡献与创新点 1.4.方法 1.4.1 为什么用二维卷积&#xff0c;而不是一维卷积&#xff1f; 1.4.2.ConvE具体实现 1.4.3.1-N scoring 1.5.…

RK3568平台开发系列讲解(驱动基础篇)GPIO控制方式

🚀返回专栏总目录 文章目录 一、使用GPIO sysfs接口控制IO二、使用libgpiod控制IO沉淀、分享、成长,让自己和他人都能有所收获!😄 📢GPIO是 General Purpose I/O 的缩写,即通用输入输出端口,简单来说就是MCU/CPU可控制的引脚, 这些引脚通常有多种功能,最基本的是高…

3D点云在线查看利器【LasViewer】

LasViewer是一个免费的3D点云在线查看工具&#xff0c;支持LAS/LAZ格式的3D点云文件在浏览器中的直接渲染。访问地址&#xff1a;LasViewer。 推荐&#xff1a;用 NSDT设计器 快速搭建可编程3D场景。 1、LasViewer快速上手 和BimAnt的其他在线工具一样&#xff0c;LasViewer的…

Windows11使用Cpython 编译文件 报错 error: Unable to find vcvarsall.bat 完美解决方法

开发环境说明&#xff1a; python 3.6.2Vs studio 2017 (已经安装C桌面开发&#xff09; 我的 vcvarsall.bat 路径为&#xff1a; "D:\vsstudio\VC\Auxiliary\Build\vcvarsall.bat" 一般在Vs studio 的此安装路径下 修改python源代码 修改文件为 python3.6.2\Li…

shell脚本练习题

名为lianxi.txt的文件内容如下&#xff1a; Steve Blenheim:238-923-7366:95 Latham Lane, Easton, PA 83755:11/12/56:20300 Betty Boop:245-836-8357:635 Cutesy Lane, Hollywood, CA 91464:6/23/23:14500 Igor Chevsky:385-375-8395:3567 Populus Place, Caldwell, NJ 2387…

[230516] TPO71 | 2022年托福阅读真题第4/36篇 | Electrical Energy from the Ocean | 11:50

目录 7101 Electrical Energy from the Ocean Paragraph 1 问题1 Paragraph 2 问题2 Paragraph 3 问题3 Paragraph 4 问题4 做错 Paragraph 5 问题5 做错 Paragraph 6 问题6 Paragraph 7 问题7 Paragaph 8 问题8 做错 Paragraph 2 问题9 问题10 7101…

GateWay源码解析

前言 一、GateWay的自动配置 springboot 在引入一个新的组件时&#xff0c;一般都会有对应的XxxAutoConfiguration类来对该组件进行配置&#xff0c;GateWay也不例外&#xff0c;在引入了以下配置后&#xff0c;就会生成对应的GatewayAutoConfiguration自动配置类 <!-- gat…

15.Python Package目录及打包并发布到PyPI

欢迎访问个人网络日志&#x1f339;&#x1f339;知行空间&#x1f339;&#x1f339; 文章目录 0.基本介绍1.__init__.py文件1.1 Regular Package1.2 namespace package 2.Python Package工程2.1 安装及打包并发布到pypi2.2 将Python文件编译成.so 3.包的搜索路径参考资料 0.基…

go test coverage 单测覆盖率

单元测试的最终统计标准就是单测覆盖率&#xff0c;统计单测总体覆盖了多少行代码。一般来说&#xff0c;我们只需要关注增量代码的覆盖率&#xff0c;而非全量代码。增量代码就是本次迭代改动的代码&#xff0c;比如本次迭代改动了100行代码&#xff0c;我们保证单测能覆盖到这…

【Vue工程】007-Scss

【Vue工程】007-Scss 文章目录 【Vue工程】007-Scss一、概述1、CSS 问题三大缺点CSS 预处理器 2、简介3、中文网4、Slogan 二、基本使用1、安装2、配置全局 scss 样式文件3、在 vite.config.ts 配置4、组件中使用5、访问 http://localhost:5173/home 一、概述 1、CSS 问题 参考…