012 - C++指针

news2024/11/15 5:39:23

本期我们将学习 C++ 中的指针。

指针是一个令很多人都很痛苦的内容,然而指针其实没有大家想象中的那么复杂。另外我先要说明本期我们要讨论的是原始的指针,还有一种常用的指针叫智能指针,这个我们在之后的内容中会接触学习。

计算机处理内存,对计算机来说内存就是一切,如果非要我说出编程中最重要的一件事,我可能会说是内存

当你编写了一段程序并启动它时,所有的程序都被载入到内存中,指令告诉计算机在你写的代码中要做什么。所有这些都被加载到内存中,CPU 就是这样访问你的程序并执行它的指令的。

当你创建一个变量,并从磁盘中读取数据时,所有的这些都存储在内存中,如果没有内存就什么也做不了。而指针对于 管理操纵内存 非常重要。

在接下来的内容中,可能会多次提到一句话,指针是一个整数,一种存储内存地址的数字,希望大家能牢记并彻底理解这句话。

本期我们不会深入讨论内存在计算机中是如何工作的,但是我们还是要有个大概的印象,就是只要把你的内存放在电脑里,它就像一条很长的直线而不是一大块。内存就像我们现实世界中的一条街,这一条街有开始也有结束,就像一根线一样,线上就是一堆房子, 没有房子是横穿街道的,假设只有这一条街道,一排的房子,我们现在把这个比喻用在电脑上,它只是一条线性的线,在这条直线上的每一所房子都有一个号码和一个空间,空间是一个字节,我们显然需要一种方法来寻址所有的 byte 来定位我们这条街上所有的房子。

例如,假设某人在网上订了东西想要送货上门,他需要被送到正确的房子里,或者可能有人把东西从他们的房子里取出去,无论哪种操作,你需要能够从这些房子的内存字节中读写,指针就是这些地址,这些地址告诉我们房子在哪里,这是非常重要的,因为我们在代码中所做的几乎所有的事情都是在从内存中读写,当然你完全有可能写一个不使用指针的 C++ 程序,你完全可以这样做。然而指针是非常有用的工具,正如我刚才提到的,内存可能是你拥有的最重要的东西,是计算机可以提供的很重要的资源,它可以被用于做几乎所有的事情,能够对内存有更多的控制至关重要。

再次重申,一个指针只是一个地址,它是一个保存内存地址得到整数,这就是所有内容了,忘掉所谓的类型,类型与这些无关,类型只是我们为了让生活更容易而创造的的某种虚构,这都不重要,让我们来看些例子。

第一个指针

我们来创建一个空指针,void 的意思是无类型。

imY0gP.png

千万记住,一个指针只是一个地址,它只是一个在内存中保存地址的整数,它不需要类型,如果我们给指针一个类型,我们只是说,这个地址的数据,被假设为我们给的类型,除此之外它没有任何意义,它只是一些我们在实际的源代码可以编写的东西,使我们的生活在语法层面上更容易,为了让我们的生活更轻松。

我们当然可以使用指针类型,不过类型不会改变一个指针的实质,——指针只是一个内存地址,它是一个整数。

所以 void 指针 意味看我们现在不关心我们的代码中这个类型是什么类型的,因为我们只想保存一个地址。

我把它称为 ptr( pointer 的简写),其值设置为0。0 是什么意思?我们给这个指针的内存地址是 0,这是什么意思?

0 实际上不是一个有效的内存地址,内存地址不会一直到 0,这是无效的,这意味着这个指针是无效的,无效指针是完全可以接受的状态,但我要说的是 0 不是一个有效的内存地址,我们不能从内存地址 0 中读取或写入数据,如果我们尝试这样做的话,程序会崩溃,所以 0 意味着没有。

我们也可以这样写。

imYCLX.png

这种写法实际上是一个 #define,你把鼠标悬浮在上面可以看到,是 #define NULL 0,和我们用 0 是一样的。或者我们也可以用 C++ 关键字 nullptr,这个会在 C++11 里面介绍。

好的,我们设置了我们的第一个指针,它是无类型的,它的内存地址是 0,一点用处没有,但它可能是你能写的最简单一个指针,这可以让我们做一些更有用的事情。

imYwnt.png

对上面的代码我们做一些解释。

我们创造一个整数变量,当然我们创建的每个变量都有一个内存地址,因为我们需要一个地方来存储这个变量。

如果我想知道这个变量的内存地址,我可以通过使用 & 运算符来做到这一点,如果我在一个已经存在的变量前面加上一个 & 符号,我们实际上是在问这个变量,嘿,你的内存地址是什么?

我们取这个变量的内存地址,把它赋值给新的变量 ptr。

我们现在有了变量 var 的内存地址,我们把它存储在另一个变量中。

我们设置一个断点调试一下。

imY2RJ.png

我们可以看变量的值,如果把鼠标放在 var 上面,可以看到它的值是 8,如果我把鼠标悬停在 ptr 上面,我们可以看到一个十六进制的数字,如你所见,仍然只是一个数字,它是一个整数,这个指针变量现在保存的是 var 变量的内存地址。我可以复制它,打开内存视窗。

这个视图现在显示程序中的所有内存,粘贴地址到输入栏,然后去掉开始的那些信息,按下 enter 我们被带到这 个内存地址,我们可以看到在这个内存地址有 8 这个值。

[imYHoc.png

在基本层面上,这就是它的全部,其他的一切都建立在此之上,这里没有什么魔法,这就是指针的工作方式,指针是一个保存地址的变量,就像其他变量一样,它不是保存变量 a 这样的值本身的值,它保存着一个内存地址,它的内存地址也是值,也是一个整数,这个整数有多大,以及这个指针有多大,取决于很多东西,可能是32 位整数,也可能是64 位,这并不重要,关键是,它是个整数。

回到我们的代码,将 void 修改为 int 并运行,你会发现实际上没有改变任何变化。

我们再做一下修改。

imYbfA.png

调试程序,你可以在内存视窗看到它仍然被设为 8,这里不同类型地址的赋值需要做一下转换。

虽然在以上例子可以看出类型无关紧要,但类型对该内存的操作很有用,如果我想对它进行读写,类型可以帮助我,因为编译器会知道。例如,一个整数应该是四个字节,所以我在那儿设置一个值,它会设置四个字节的内存。

让我们回到我们的空指针,所以空指针这么好用,为什么不用空指针呢?假设我想使用我的数据,我有一个指针指向那个数据,现在我想要写入或读取数据,我该如何操作呢?换句话说,我们知道数据在哪里,但是我怎么能访问它呢,这就要靠逆向引用了(指针的 * 运算符通常被称为 dereference 运算符)。

我们有变量 var,指针 ptr 指向 var,但是我怎么才能回到这个 var 呢? 你可以通过在指针前面插入一个星号来实现这一点,换句话说,我实际上是在逆向引用那个指针,这意味着我现在可以访问我可以读取或写入数据的数据。我们试着这样做一下。

imYjjo.png

运行后我们发现程序报错了,因为我们说过这个指针是一个空指针,也就是说,计算机怎么可能将这个值写入到一个 void 指针,它不知道那是什么,这个 10 是 short 类型吗?——两个字节的整数,是 int 类型吗?——四个字节的整数,是 long long 类型吗?—— 8 个字节的整数,它不知道这需要多少字节的数据,我们刚刚说它是 10,但是 10 可以代表任何东西,这个时候就需要类型了,我们需要告诉编译器,这是一个整数,所以是 4 个字节,我们修改一下。

imY8FN.png

当然,是我们告诉编译器,这是一个整数,编译器自己并不知道这是不是正确的,如果我们犯了错,比如我们说这实际是一个 double,那程序的运行可能就有点麻烦了。

好吧,通过写代码的时候,逆向引用 *指针,我可以访问这个数据,这个例子中,我写入这个数据。

你现在应该知道指针是如何工作的了,这就是它的全部,指针只是指向内存中的一个位置。有些人说它指向一个内存块,这样说不是很准确,因为我们不知道这块内存是多大,在这个例子中,它是 4 个字节,因为我们创建了一个整数,一个整数是 4 字节的内存,所以我们确实知道这个指针指向的内存是 4 个字节。然而在实际的指针中,并不知道内存多大,当我们创建数组时,它会跟踪内存大小,这个之后再讲。

简单地说,我们不知道指针有多大,我们不知道指针指向的数据多大,因为指针并不包含数据,一个指针就是一个整数,它是一个内存地址,就是这样。

到目前为止,我们一直在栈上直接创建数据,如果我们像上面的例子一样操作,那就是在栈中创建变量(之后我们会讲到栈和堆的内容)。

new & delete

如果我想在堆上创建一个变量,或许我可以问我们的电脑,嘿,我想让你给我分配一些内存,我想有一定的尺寸(比如 8 个字节),我会这样做。

imYIYV.png

上面的代码给我们分配了 8 个字节的内存,并返回一个指向那块内存开始位置的指针,然后我可以使用 memset 的函数,它可以用我们指定的数据填充一段内存块。

memset 接收一个指针,这个指针将会是内存块的开始的指针,然后是将要填入的值,比如 0,最后是应该填入多少字节,我们要 8 个字节。

运行这个程序。在内存视窗可以看到 buffer 位置的连续 8 个字节都为 0。

imYrld.png

这个例子中,我们使用了新的关键字 new 申请了堆内存,当我们完成它后,我们也应该删除数据。

我们可以通过键入 delete 完成删除,我们知道它是一个数组,我们使用数组来分配堆内存,所以我们应该使用 delete[ ] 来删除 buffer。

imY3xb.png

这个例子再次强调了,这个指针,我们分配 8 个 char,1 个 char 是一个字节,这样我们就分配了 8 个字节, 我们用来存储数据的指针指向了数据的开头。

指针的指针

还有一点我想说的是指针本身是变量,这些变量也存储在内存中,这意味着我们可以得到双指针或三指针,意思可以有指向指针的的指针。这一切可以如何运作呢,好吧,你只要往下一层想,我现在有一个指针指向我的指针,于是我有了一个变量 a 来存储内存地址,它指向另一个变量 b,变量存储变量 c 的内存地址。就这么简单。

在 buffer 的例子中,我们可以创建一个双指针试一下。

imYRLq.png

设置一个断点进行调试。

imYmnz.png

就是这样 ,指针的指针,不是很难理解吧。

后话

回到指针上来,再次强调,它只是存储内存地址的整数,这就是他们应该在你脑子里的全部,后面我们我会讲更多关于指针处理的话题,处理算术和更高级的指针操作。

好了,本期内容就到这里,下期再见。

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

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

相关文章

LeetCode_二叉搜索树_中等_236.二叉搜索树的最近公共祖先

目录1.题目2.思路3.代码实现(Java)1.题目 给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。 百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 …

jQuery讲解|这一章就够了|(超详细|保姆级)

🙈作者简介:练习时长两年半的Java up主 🙉个人主页:老茶icon 🙊 ps:点赞👍是免费的,却可以让写博客的作者开兴好久好久😎 📚系列专栏:Java全栈,计…

【设计模式】生产者消费者模型

带你轻松理解生产者消费者模型!生产者消费者模型可以说是同步与互斥最典型的应用场景了!文末附有模型简单实现的代码,若有疑问可私信一起讨论。 文章目录一:为什么要使用生产者消费者模型?二:生产者消费者模…

JDK 17:Java 17 中的新特性简介

Java 开发工具包 (JDK) 17 将是一个长期支持 (LTS) 版本,预计来自 Oracle 的扩展支持将持续数年。该功能集定于 6 月 10 日冻结,届时 JDK 17 将进入初始阶段。作为 OpenJDK JDK 17 的一部分提交的功能包括: 特定于上下文的反序列化过滤器允许…

计算机网络 实验一

⭐计网实验专栏,欢迎订阅与关注! ★观前提示:本篇内容为计算机网络实验。内容可能会不符合每个人实验的要求,因此以下内容建议仅做思路参考。 一、实验目的 掌握在Packet Tracer软件中搭建实验平台,配置基本的网络参数…

8D和A3报告

8D和3A报告,他们都不仅仅是记录问题的一种文书,而是解决问题的工具。 A3发展于TPS (Toyota Production system),可以用来解决问题,沟通,记录,是一种流程,当人们在使用A3…

MySQL中添加新字段

© Ptw-cwl 要在MySQL中添加新字段,您可以使用ALTER TABLE语句。 以下是添加新字段的基本语法: ALTER TABLE table_name ADD column_name datatype;其中: table_name 是您要在其中添加新字段的表的名称。column_name 是新字段的名称。…

Linux安装Anaconda

目录1.下载Anaconda的安装包2.安装Anaconda3.用conda创建虚拟环境4.安装项目依赖包1.下载Anaconda的安装包 首先需要在官网上选择需要安装的版本。 官网地址:https://repo.anaconda.com/archive/,如选择当前最新版本进行安装: https://repo.…

TWIST阅读笔记

目录TWIST: Two-Way Inter-label Self-Training for Semi-supervised 3D Instance Segmentation摘要本文方法语义引导的实例提议生成提议纠正基于提议的伪标签更新TWIST: Two-Way Inter-label Self-Training for Semi-supervised 3D Instance Segmentation 摘要 利用无标签数…

浙大版《C语言程序设计实验与习题指导(第3版)》题目集实验2合集

实验2-1-1 计算摄氏温度 本题要求编写程序,计算华氏温度100F对应的摄氏温度。计算公式:C5(F−32)/9,式中:C表示摄氏温度,F表示华氏温度,输出数据要求为整型。 输入格式:本题目没有输入。 输出格式:按照下…

Java每日一练(20230413)

目录 1. 子集 II 🌟🌟 2. 快乐数 ※ 3. 整数反转 ※ 🌟 每日一练刷题专栏 🌟 Golang每日一练 专栏 Python每日一练 专栏 C/C每日一练 专栏 Java每日一练 专栏 1. 子集 II 给你一个整数数组 nums ,其中可能…

【pip换源操作】解决用pip下载Python第三方库慢问题

python自带的第三方库使用pip安装速度会很慢,还有可能会报错。 常见的报错信息有: check_hostname requires server_hostname raise ValueError(“check_hostname requires server_hostname”) ValueError: check_hostname requires server_hostname EO…

波尔模型的实验验证之类氢粒子光谱类实验

光谱产生的原因:原子中电子在轨道上跃迁产生,如莱曼系为电子从n2,3,4等轨道跃迁到n1的基态轨道产生。 中心的原点为原子核,中心最接接近原子核的圆为n1的电子轨道。 r_na_0n^2,轨道大小正比于n的平方 根据电子轨道图即可以获得…

AE开发之图层渲染20210603

AE开发之图层渲染比例符号化地图的整饰唯一值符号的符号化过程点符号设置,线符号设置标注图层,(写得不好,不推荐看) 唯一值符号化,字段进行设置,这里用到了UniqueValueRenderer接口,这里面有一…

用pyocd读写gd32f4系列mcu的otp区

如前一篇文章所述,pyocd是一个调试、编程cortex-m单片机的简单、强大的工具,本文就结合实例讲解pyocd的一些用法。 使用j-link、dap-link等工具在keil或其它ide中调试单片机程序的场景比较常见,而使用这些工具对单片机片内和片外flash存储区…

[ 应急响应基础篇 ] evtx提取安全日志 事件查看器提取安全日志

🍬 博主介绍 👨‍🎓 博主介绍:大家好,我是 _PowerShell ,很高兴认识大家~ ✨主攻领域:【渗透领域】【数据通信】 【通讯安全】 【web安全】【面试分析】 🎉点赞➕评论➕收藏 养成习…

【C++中关于abs()函数的告警问题】“abs“ is ambiguousC/C++(266)

C中关于abs()函数的告警问题 “abs” is ambiguousC/C(266) 在调试异常打卡记录这道华为OD机考题的时候,完成了C版本之后,在vscode进行调试,出现了如下的告警:abs有歧义,所以就开始查找到底是设什么原因: …

MAC-安装Java环境、JDK配置、IDEA插件推荐

背景:发现经常换电脑装环境等比较麻烦,主要还是想记录一下,不要每次安装都到处翻。。 1、下载并安装JDK 到官网下载所需的JDK:https://www.oracle.com/technetwork/java/javase/downloads/jdk11-downloads-5066655.html 这儿下…

Wine运行器帮助——使用Qemu User运行Wine(理论支持全架构)

提醒 Qemu 的转换效率较低,如果可以的话建议使用其它方案以提升运行效率 此方案理论上支持全架构(amd64、arm64、mips64、loongarch64、riscv64、ppc64、s390x……),只要能跑 Qemu User 即可,在 x86、arm64 真机测试通…

[建议收藏]45 个 Git 经典操作场景,专治各种不会合并代码的童鞋~~

有人说:一个人从1岁活到80岁很平凡,但如果从80岁倒着活,那么一半以上的人都可能不凡。 生活没有捷径,我们踩过的坑都成为了生活的经验,这些经验越早知道,你要走的弯路就会越少。