深入理解指针初阶:从概念到实践

一、引言

    在 C 语言的学习旅程中,指针无疑是一座必须翻越的高峰。它强大而灵活,掌握指针,能让我们更高效地操作内存,编写出更优化的代码。但指针也常常让初学者望而生畏,觉得它复杂难懂。别担心,本文将用通俗易懂的语言,结合丰富的代码示例,带你逐步揭开指针初阶的神秘面纱,让你从入门到熟练掌握指针的基础概念与应用

二、指针是什么

2.1 概念剖析

    指针是编程语言中的一个特殊对象,它的值是内存中另一个数据的地址。在计算机的内存世界里,每一个存储单元都有一个唯一的编号,就像我们住的房子都有门牌号一样,这个编号就是地址。而指针变量,就是专门用来存放这些地址的变量。

    想象一下,内存是一个巨大的仓库,里面有无数个小格子(内存单元),每个小格子都存放着不同的数据。指针就像是一把带有格子编号(地址)的钥匙,通过这把钥匙,我们就能快速找到并访问对应的格子里的数据   

2.2 代码示例

    这段代码中,int *p声明了一个指针变量p,它的类型是int *,表示它可以存放int类型变量的地址。&a获取了变量a的地址,然后将这个地址赋值给p。此时,p就指向了变量a所在的内存单元

2.3 内存编址与指针大小

    计算机的内存编址方式与机器的位数密切相关。对于 32 位机器,假设有 32 根地址线,每根地址线在寻址时能产生一个电信号(正电或负电,对应 1 或 0),那么 32 根地址线产生的地址数量就是2^32个。由于每个地址标识一个字节(1Byte),所以 32 位机器可以编址的内存空间大小2^32,换算后就是 4GB(2^32/1024/1024/1024 GB)。

    在 32 位机器上,地址是由 32 个 0 或 1 组成的二进制序列,这样的地址需要用 4 个字节的空间来存储,因此一个指针变量的大小就是 4 个字节。同理,64 位机器有 64 根地址线,能产生2^64个地址,可以编址的内存空间更大,而一个指针变量的大小为 8 个字节,才能存放一个地址。

    总结来说,指针是用来存放地址的变量,地址唯一标识一块内存空间,指针的大小在 32 位平台是 4 个字节,在 64 位平台是 8个字节

三、指针和指针类型

3.1 指针类型的定义

    变量有不同的类型,如整形int、浮点型float等,指针也有类型。指针的定义方式是type + *,例如:

     这里,char*类型的指针用于存放char类型变量的地址,short*类型的指针用于存放short类型变量的地址,以此类推。NULL是一个特殊的指针常量,表示空指针,即不指向任何有效内存地址

3.2 指针类型的意义

指针类型在指针运算中起着关键作用,主要体现在两个方面:指针加减整数和指针解引用

3.2.1 指针加减整数

    在这段代码中,pcchar*类型的指针,piint*类型的指针,它们都指向变量n。当pc + 1时,指针向前移动 1 个字节;而pi + 1时,指针向前移动 4 个字节(假设在 32 位机器上,int类型占 4 个字节)。这表明指针的类型决定了指针向前或向后移动一步的距离 

3.2.2 指针解引用

   在调试这段代码时可以发现,*pc = 0只修改了n所在内存空间的 1 个字节,而*pi = 0则修改了n所在内存空间的 4 个字节(假设int类型占 4 个字节)。这说明指针的类型决定了对指针解引用时的权限,即能操作几个字节。char*的指针解引用只能访问 1 个字节,而int*的指针解引用能访问 4 个字节 

四、野指针

4.1 野指针的概念

野指针是指指针指向的位置不可知(随机、不正确、没有明确限制)的指针。当指针变量在定义时未初始化,其值是随机的,此时去解引用这个指针,就相当于访问了一个不确定的地址,结果是不可预测的,可能导致程序崩溃或产生其他未定义行为。

4.2 野指针的成因

4.2.1 指针未初始化

    在这段代码中,p是一个未初始化的指针,它的值是随机的,对其进行解引用操作*p = 20,会访问一个不确定的内存地址,这是非常危险的

4.2.2 指针越界访问

    这里,数组arr有 10 个元素,合法的下标范围是 0 到 9。但在for循环中,i的值可以达到 11,当i为 10 和 11 时,指针p超出了数组arr的范围,成为野指针,此时对p进行解引用操作会访问到不属于数组的内存区域,可能导致程序出错

4.2.3 指针指向的空间释放(动态内存开辟时讲解,此处简单提示)

    在使用动态内存分配函数(如malloc)时,如果释放了指针指向的内存空间,但没有及时将指针置为NULL,那么该指针就会变成野指针。例如: 

4.2.4返回局部变量的指针

局部变量在函数内部定义,其作用域仅限于函数内部。当函数执行结束,局部变量所占用的内存空间会被系统自动释放。若在函数中返回指向局部变量的指针,函数结束后,该指针指向的内存空间已无效,但指针本身依然存在,进而成为野指针

    在上述代码中,test函数返回了指向局部变量num的指针。当test函数执行完毕,num所在内存空间被释放,p就变成了野指针。此时对p进行解引用操作,程序行为未定义,可能输出看似正确的值(若释放的内存未被覆盖),也可能导致程序崩溃 

4.3 规避野指针的方法

4.3.1 指针初始化

在定义指针变量时,尽量给它一个初始值,可以是NULL,也可以是指向合法内存地址的值。例如:

4.3.2 小心指针越界

    在使用指针访问数组或其他内存区域时,要确保指针不会超出其合法范围。在访问数组元素时,要注意下标的边界条件。

4.3.3 指针指向空间释放及时置NULL

    当释放了指针指向的内存空间后,立即将指针置为NULL,这样可以避免误操作。例如:

    当指针变量指向⼀块区域的时候,我们可以通过指针访问该区域,后期不再使⽤这个指针访问空间的时候,我们可以把该指针置为NULL。因为约定俗成的⼀个规则就是:只要是NULL指针就不去访问,同时使⽤指针之前可以判断指针是否为NULL。
    我们可以把野指针想象成野狗,野狗放任不管是⾮常危险的,所以我们可以找⼀棵树把野狗拴起来,就相对安全了,给指针变量及时赋值为NULL,其实就类似把野狗栓起来,就是把野指针暂时管理起来。
    不过野狗即使拴起来我们也要绕着⾛,不能去挑逗野狗,有点危险;对于指针也是,在使⽤之前,我 们也要判断是否为NULL,看看是不是被拴起来起来的野狗,如果是不能直接使⽤,如果不是我们再去使⽤
4.3.4 指针使用之前检查有效性

    在使用指针之前,检查指针是否为NULL,以确保指针指向的是有效内存地址。例如:

4.3.5 避免返回局部变量的地址

五、指针运算

指针运算主要包括指针加减整数、指针减指针和指针的关系运算

5.1 指针加减整数

    指针加减整数的运算规则与指针类型密切相关。前面已经介绍过,指针的类型决定了指针移动一步的距离。下面通过一个示例来进一步理解:

     在这段代码中,vpfloat*类型的指针,for循环中vp++每次使vp向后移动 4 个字节(假设float类型占 4 个字节),从而遍历整个values数组,并将数组元素初始化为 0

5.2 指针减指针

    指针减指针的结果是两个指针之间元素的个数(前提是两个指针指向同一块连续内存区域)。下面是一个计算字符串长度的函数示例:

    在这个函数中,ps都是char*类型的指针,p从字符串的起始位置开始,逐个字符向后移动,直到遇到字符串结束标志'\0'。最后返回p - s,即字符串的长度(不包括'\0'

5.3 指针的关系运算

    指针的关系运算允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但不允许与指向第一个元素之前的那个内存位置的指针进行比较。例如:

     在第一个for循环中,vp从数组最后一个元素后面的位置开始,向前移动并初始化数组元素。虽然在大部分编译器上第二个简化的for循环也能正常工作,但从标准角度来看,不建议这样写,因为标准并不保证其可行性

六、指针和数组

6.1 数组名与指针的关系

    数组名在很多情况下表示的是数组首元素的地址。通过下面的代码可以验证:

6.2 用指针访问数组元素

    由于数组名可以当成地址存放到一个指针中,因此我们可以使用指针来访问数组元素。例如:

    在这段代码中,p指向数组arr的首元素,p + i计算的是数组arr下标为i的元素的地址。通过这种方式,我们可以直接用指针遍历数组并访问元素:

     这里,*(p + i)就相当于arr[i],通过指针间接访问数组元素并输出其值 

七、二级指针

7.1 二级指针的概念

    指针变量也是变量,既然是变量就有地址。二级指针就是用来存放一级指针变量地址的指针。例如:

    在这个例子中,a是一个普通的int类型变量,pa是指向a的一级指针,ppa是指向pa的二级指针 

7.2 二级指针的运算

    二级指针的运算主要涉及解引用操作

     在这段代码中,*ppa通过对ppa中的地址进行解引用,找到的是pa,因此*ppa = &b就相当于pa = &b,使pa指向了变量b。而**ppa先通过*ppa找到pa,然后对pa进行解引用操作*pa,找到的是a,所以**ppa = 30就相当于*pa = 30,最终相当于a = 30 

八、指针数组

8.1 指针数组的定义

     指针数组是一个数组,数组中的每个元素都是一个指针。例如:

     这里,arr3是一个指针数组,它有 5 个元素,每个元素都是一个int*类型的指针。

8.2 指针数组的应用场景

    指针数组在处理多个相同类型的指针时非常方便。在处理多个字符串时,可以使用指针数组来存储每个字符串的首地址:

 

    在这个例子中,strs是一个指针数组,每个元素都是一个指向char类型的指针,分别指向不同的字符串。通过遍历指针数组,可以方便地访问每个字符串。

九、总结

    本文详细介绍了指针初阶的各个重要知识点,包括指针的基本概念、指针类型、野指针、指针运算、指针与数组的关系、二级指针以及指针数组。指针作为 C 语言的核心特性之一,虽然具有一定的复杂性,但通过深入理解其原理,并结合大量的代码实践,我们能够逐步掌握它,并在编程中充分发挥其强大的功能。希望读者在学习指针的过程中,多思考、多实践,不断积累经验,为后续更深入的 C 语言学习和编程开发打下坚实的基础

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

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

相关文章

八、OSG学习笔记-

前一章节: 七、OSG学习笔记-碰撞检测-CSDN博客https://blog.csdn.net/weixin_36323170/article/details/145558132?spm1001.2014.3001.5501 一、了解OSG图元加载显示流程 本章节代码: OsgStudy/wids CuiQingCheng/OsgStudy - 码云 - 开源中国https:…

在 ARM64 架构系统离线安装 Oracle Java 8 全流程指南

在 ARM64 架构系统离线安装 Oracle Java 8 全流程指南 文章目录 在 ARM64 架构系统离线安装 Oracle Java 8 全流程指南一、引言二、下载前的准备2.1 确认系统架构2.2 注册 Oracle 账号 三、从 Oracle 官方下载 Java 8 for ARM643.1 访问 Oracle Java 存档页面3.2 选择合适的版本…

栈的简单介绍

一.栈 栈是一种先进后出的结构:(先出来的是45,要出12就必须先把前面的数据全部出完。) 2.实例化一个栈对象: 3.入栈: 4.出栈:(当走完pop就直接弹出45了。) 5.出栈的…

java韩顺平最新教程,Java工程师进阶

简介 HikariCP 是用于创建和管理连接,利用“池”的方式复用连接减少资源开销,和其他数据源一样,也具有连接数控制、连接可靠性测试、连接泄露控制、缓存语句等功能,另外,和 druid 一样,HikariCP 也支持监控…

HCIA项目实践--RIP相关原理知识面试问题总结回答

9.4 RIP 9.4.1 补充概念 什么是邻居? 邻居指的是在网络拓扑结构中与某一节点(如路由器)直接相连的其他节点。它们之间可以直接进行通信和数据交互,能互相交换路由信息等,以实现网络中的数据转发和路径选择等功能。&am…

【ThreeJS Basics 1-3】Hello ThreeJS,实现第一个场景

文章目录 环境创建一个项目安装依赖基础 Web 页面概念解释编写代码运行项目 环境 我的环境是 node version 22 创建一个项目 首先,新建一个空的文件夹,然后 npm init -y , 此时会快速生成好默认的 package.json 安装依赖 在新建的项目下用 npm 安装依…

【JavaEE进阶】依赖注入 DI详解

目录 🌴什么是依赖注入 🎄依赖注入的三种方法 🚩属性注⼊(Field Injection) 🚩Setter注入 🚩构造方法注入 🚩三种注⼊的优缺点 🌳Autowired存在的问题 🌲解决Autowired存在的…

在Mac arm架构终端中运行 corepack enable yarn 命令,安装yarn

文章目录 1. 什么是 Corepack?2. 运行 corepack enable yarn 的作用3. 如何运行 corepack enable yarn4. 可能遇到的问题及解决方法问题 1:corepack 命令未找到问题 2:Yarn 未正确安装问题 3:权限问题 5. 验证 Yarn 是否启用成功6…

16.React学习笔记.React更新机制

一. 发生更新的时机以及顺序## image.png props/state改变render函数重新执行产生新的VDOM树新旧DOM树进行diff计算出差异进行更新更新到真实的DOM 二. React更新流程## React将最好的O(n^3)的tree比较算法优化为O(n)。 同层节点之间相互比较,不跨节点。不同类型的节…

SQL数据清理:去除字段值中的多余符号(Demo例子)

目录 前言1. 基础2. 进阶 前言 Excel中有大量不合法的符号,导入到系统之后,数据库有很多脏数据,对此下述展开sql的清洗教程 在数据库的文本字段中,可能会存在多余的逗号或符号,如,销售,, 或 二手车,销售,,这种情况 希…

计算机组成原理

观看地址如下【2019版】1.3.2 性能指标2——速度_哔哩哔哩_bilibili 第一章 计算机系统概述 了解 #低电平高电平 #计算机的发展 主要是因为逻辑元件的限制 选择题 微处理器的发展 这里的机器字长为 软硬件的发展 几种指令和数据流 计算机的系统结构 需求产生变化 电信号…

基于MATLAB的沥青试样孔隙率自动分析——原理详解与代码实现

摘要 在材料科学与土木工程领域,沥青孔隙率是评价其耐久性和稳定性的重要指标。本文提出一种基于图像处理的孔隙率自动计算方法,通过MATLAB实现灰度化、对比度增强、形态学处理等关键步骤,最终输出试样孔隙率。代码注释清晰,可直…

【嵌入式Linux应用开发基础】open函数与close函数

目录 一、open函数 1.1. 函数原型 1.2 参数说明 1.3 返回值 1.4. 示例代码 二、close函数 2.1. 函数原型 2.2. 示例代码 三、关键注意事项 3.1. 资源管理与泄漏防范 3.2. 错误处理的严谨性 3.3. 标志(flags)与权限(mode&#xff…

【通俗易懂说模型】一篇弄懂几个经典CNN图像模型(AlexNet、VGGNet、ResNet)

🌈 个人主页:十二月的猫-CSDN博客 🔥 系列专栏: 🏀深度学习_十二月的猫的博客-CSDN博客 💪🏻 十二月的寒冬阻挡不了春天的脚步,十二点的黑夜遮蔽不住黎明的曙光 目录 1. 前言 2. …

Android 14.0 Launcher3单层模式workspace中app列表页排序功能实现

1.概述 在14.0的定制化开发中,对于Launcher3的功能定制也是好多的,而对于单层app列表页来说排序功能的开发,也是常有的功能这就需要了解加载app数据的流程,然后根据需要进行排序就可以了,接下来就来实现这个功能 如图: 2. Launcher3单层模式workspace中app列表页排序功能…

8K样本在DeepSeek-R1-7B模型上的复现效果

7B Model and 8K Examples: Emerging Reasoning with Reinforcement Learning is Both Effective and Effic (notion.site) 港科大助理教授何俊贤的团队以Qwen2.5-Math-7B(基础模型)为起点,直接对其进行强化学习。整个过程中,没有…

四、自然语言处理_08Transformer翻译任务案例

0、前言 在Seq2Seq模型的学习过程中,做过一个文本翻译任务案例,多轮训练后,效果还算能看 Transformer作为NLP领域的扛把子,对于此类任务的处理会更为强大,下面将以基于Transformer模型来重新处理此任务,看…

MATLAB 生成脉冲序列 pulstran函数使用详解

MATLAB 生成脉冲序列 pulstran函数使用详解 目录 前言 一、参数说明 二、示例一 三、示例二 总结 前言 MATLAB中的pulstran函数用于生成脉冲序列,支持连续或离散脉冲。该函数通过将原型脉冲延迟并相加,生成脉冲序列,适用于信号处理和系统…

算法练习——滑动窗口

前言:滑动窗口的难点不在于怎么编写代码,而在于如何想到这题是用滑动窗口的算法去解决。其次滑动窗口的左端和右端在滑动时窗口内数据存在单调性。 一:长度最小的子数组 题目要求: 解题思路: 对于第一道滑动窗口算法…

Zabbix-监控SSL证书有效期

背景 项目需要,需要监控所有的SSL证书的有效期,因此需要自定义一个监控项 实现 创建自定义脚本 在Zabbix的scripts目录(/etc/zabbix/scripts/)下创建一个新的shell脚本check_ssl.sh,内容如下 #!/bin/bash time$(echo | openssl s_client…