当前位置: 首页 > news >正文

C语言中指针的重要性及其知识梳理

一.重要性

C语言的“灵魂”
指针常被称为C语言的“灵魂”、“精华”与根本所在。这主要是因为C语言功能强大、使用灵活的特性,很大程度上体现在指针的灵活运用上。它为程序员提供了对计算机内存的直接控制能力,这是C语言区别于许多其他高级语言的关键特性。这种直接操作内存地址的能力,使得C语言既具有高级语言的特性,又能执行类似低级语言的操作,例如直接访问硬件寄存器。因此,学习指针意味着从“变量名”的抽象思维切换到“内存地址”的具体机器思维,这是理解计算机底层工作原理的重要一步。指针的重要性在于它**提供了直接访问和操作内存的底层能力**,从而衍生出高效的内存管理、灵活的数据结构实现、强大的函数交互以及底层的系统控制。尽管指针概念初学时有难度,且使用不当可能引发空指针、野指针等错误,但它是深入理解计算机系统、掌握C语言精髓的必经之路。

二、知识梳理

1.内存的理解

电脑里的中央处理器(CPU),就相当于计算机的 “运算大脑”。它处理数据时,先要从内存里读取需要用到的素材;等运算处理完成后,再把最终结果存回内存里。我们买电脑时看到的 8GB、16GB、32GB 这些数字,就是电脑内存的总容量。这么大的一块内存空间,要怎么管理才能不混乱、用得高效呢?其实核心逻辑很简单:系统会把整块内存,切分成一个个大小固定的 “小格子”,每个小格子就是一个内存单元,而每个内存单元的大小,统一规定为 1 个字节。

【计算机存储常用单位小补充】

  • 比特位(bit):计算机里最小的信息单位,1 个比特位只能存一个二进制数,要么是 0,要么是 1。
  • 字节(Byte):我们说的内存容量,核心就是以字节为基础单位计量的,1 个字节固定等于 8 个比特位。

我们可以把每个内存单元想象成一间学生宿舍——“一个字节的空间能容纳8个比特位”we'r,就像一间八人间宿舍里住着8位同学,每一位同学恰好对应一个比特位。 每个内存单元都有一个”唯一的编号“,这个编号就好比宿舍的门牌号。有了门牌号,我们能快速定位到某间宿舍;同理,有了内存单元的编号,CPU就能迅速找到对应的内存空间。 生活中我们把门牌号称为“地址”,在计算机里,内存单元的编号也被叫做“地址”。而在C语言中,给地址起了一个新的名字——“指针”。 因此我们可以这样理解:“内存单元的编号 == 地址 == 指针”。

2.地址编码理解

CPU 想要读取内存里某个字节的数据,必须先明确它在内存中的具体位置。内存中字节数量庞大,就如同快递驿站有着海量货架格子,因此需要为内存进行编址,就像给每一个货架格子编排唯一的储物编号。计算机的内存编址,并非是在每一个字节上都标注对应的地址,而是通过底层硬件设计来实现的。就像快递驿站的货架,驿站并不会在每一个储物格上都张贴编号,但是分拣员都清楚从左到右、从上到下的排序规则,能够精准找到每一个格子。这是因为驿站在搭建货架时就遵循了统一的布局规范,所有工作人员都默认遵守这个共识,内存编址也是同样的道理,依靠硬件层面的设计约定,让 CPU 可以准确找到目标字节。

3.取地址操作符(&)

⽐如,上述的代码就是创建了整型变量a,内存中 申请4个字节,⽤于存放整数10,其中每个字节都有地址,上图中4个字节的地址分别是:
1.0x000000A0F94FF994
2.0x000000A0F94FF995
3.0x000000A0F94FF996
4.0x000000A0F94FF997
如何能得到a的地址呢?
这⾥就得认识个操作符(&)-取地址操作符
(这里重新运行了程序,原有的a内存被销毁了,这里重新对变量划分了新内存)
&a取出的是a所占4个字节中地址较⼩的字节的地址,变量在内存中的存储虽然整型变量占⽤4个字节,我们只要知道了第⼀个字节地址,便可按顺序访问到4个字节的数据也是可⾏的。
4.指针变量
那我们通过取地址操作符(&)拿到的地址是⼀个数值,这个数值有时候也是需要存储起来,⽅便后期再使⽤的,那我们把这样的地址值存放在哪⾥呢?答案是:指针变量中。比如:
指针变量也是⼀种变量,就像整形变量,这种变量就是⽤来存放地址的,存放在指针变量中的值都会理解为地址。
借助上图来理解指针:
int a = 10;
int* p;
p左边写的是int**是在说明p是指针变量,⽽前⾯的int是在说明pa指向的是整型类型的对象。
5.解引⽤操作符
只要拿到了地址(指针),就可以通过地址(指针)找到地址(指针) 指向的对象,这⾥必须学习⼀个操作符叫解引⽤操作符(*)。
可以这么理解:
*就像一把钥匙,指针变量就像一把锁,用钥匙(*)来解锁锁就得到了地址。
6.指针变量大小的理解

根据前文所述,32位机器具有32根地址总线。每根地址线输出的电信号转换为数字信号后为1或0,因此32根地址线产生的二进制序列构成一个地址。这样一个32位的地址需要4字节的存储空间。同理,用于存储地址的指针变量在32位机器上也需要4字节的空间。对于64位机器,假设具有64根地址线,每个地址由64位二进制序列组成,存储这样的地址需要8字节的空间。因此,在64位机器上,指针变量的大小为8字节。指针变量的⼤⼩和类型⽆关,只要是指针变量,在同⼀个环境下,⼤⼩都是⼀样的。

7.指针的解引⽤

不难得出:指针的类型决定了,对指针解引⽤的时候有多⼤的权限(⼀次能操作⼏个字节)。

8.指针+-整数

列如:char*类型的指针变量+1跳过1个字节,int*类型的指针变量+1跳过了4个字节。 这就是指针变量的类型差异带来的变化。
指针+1,其实跳过1个指针指向的元素的类型大小。指针可以+1,那也可 以-1。
指针-指针 也同样可以这样理解。
9.void* 指针
指针类型中有⼀种特殊的类型是void *类型的,可以理解为⽆具体类型的指针(或者叫泛型指
针),这种类型的指针可以⽤来接受任意类型地址。但是也有局限性,void*类型的指针不能直接进 ⾏指针的+-整数和解引⽤的运算。
但 void*型指针也有它的重要意义,void*类型的指针是使⽤在函数参数的部分,⽤来接收不同类型数据的地址,这样的设计可以 实现泛型编程的效果。使得⼀个函数来处理多种类型的数据。很多库函数就利用了void*,比如<stdlib.h>中的qsort函数。

10.const修饰指针变量

const关键字在指针变量中有多种用法,用于限定指针本身或指针指向的数据的不可变性。根据const的位置不同,可以分为以下几种情况:

  1. 指向常量的指针(Pointer to Constant)
    格式:const T* pT const* p
    特点:指针指向的数据是常量,不可通过指针修改,但指针本身可以指向其他地址。
    示例:

    const int* p = &a; *p = 10; // 错误:不能修改指向的值 p = &b; // 正确:可以修改指针的指向
  2. 常量指针(Constant Pointer)
    格式:T* const p
    特点:指针本身是常量,不可修改指向的地址,但可以通过指针修改指向的数据。
    示例:

    int* const p = &a; *p = 10; // 正确:可以修改指向的值 p = &b; // 错误:不能修改指针的指向
  3. 指向常量的常量指针(Constant Pointer to Constant)
    格式:const T* const p
    特点:指针和指针指向的数据都不可修改。
    示例:

    const int* const p = &a; *p = 10; // 错误:不能修改指向的值 p = &b; // 错误:不能修改指针的指向

应用场景:

  • 函数参数传递时,使用const T*可以防止函数内部修改传入的数据,提高安全性。
  • 在硬件编程或嵌入式系统中,某些内存地址(如寄存器)可能不允许修改,可以使用const限定。
  • 结合泛型编程时,const可以确保模板函数或类不意外修改传入的数据,如std::vectorconst_iterator

泛型编程中的const:
在模板编程中,const可以配合类型推导(如autodecltype)使用,确保泛型代码的健壮性。例如:

template<typename T> void print(const T* arr, size_t size) { for (size_t i = 0; i < size; ++i) { std::cout << arr[i] << " "; // 安全:arr指向的数据不可修改 } }

11.野指针

野指针是指向已释放内存未分配内存超出作用域内存的指针。访问野指针会触发未定义行为(程序崩溃、数据损坏、随机输出等),是 C 语言内存错误中最常见且难排查的问题之一。

比如:(1)int *p; // 未初始化,p指向随机地址

*p = 10; // 错误:访问随机内存,可能直接崩溃

(2)int* getLocalAddr() {

int a = 10; // 局部变量,函数结束后内存被回收

return &a; // 错误:返回局部变量地址 }

int *p = getLocalAddr(); *p = 20; // 访问无效内存

(3)int arr[3] = {1, 2, 3};

int *p = arr; p += 5; // 指针越界,指向数组外的未知内存

*p = 10; // 错误:访问越界内存

11.1野指针的预防:

1. 指针初始化时置空或指向有效地址

定义指针时立即赋值,要么设为NULL(空指针),要么指向合法内存.

2. 释放内存后立即置空指针

free()后将指针设为NULL,后续可通过if (p != NULL)检查避免误访问。

3. 避免返回局部变量地址

若需返回地址,可使用以下三种安全方式:

1).用static修饰局部变量(生命周期延长至程序结束)

2).动态分配内存(malloc,需在外部手动free

3).传入外部变量地址(由调用者管理内存)

4. 严格控制指针运算边界

使用数组时,确保指针移动不超出数组范围;动态内存分配时,记录内存大小,避免越界。

5. 使用工具辅助检测

借助内存检测工具在开发阶段发现野指针问题:

  • Valgrind:运行valgrind --leak-check=full ./your_program,可检测内存泄漏、野指针等。
  • AddressSanitizer:编译时添加-fsanitize=address(如gcc -fsanitize=address test.c -o test),运行时会直接报错并定位野指针位置。

12.常见指针应用:

1.数组指针变量:

指针数组是一种特殊类型的数组,其元素存储的是内存地址(即指针)。例如:

  • 整型指针变量:int *pint,存储整型变量的地址,指向整型数据
  • 浮点型指针变量:float *pf,存储浮点型变量的地址,指向浮点型数据

同理,数组指针变量应该是指向数组的指针,存储的是数组的地址。

2.⼆维数组传参的本质:

⼆维数组的数组名表⽰的就是第⼀⾏的地址,是⼀ 维数组的地址。列如,第⼀⾏的⼀维数组的类型就是 int [5] ,所以第⼀⾏的地址的类 型就是数组指针类型 int(*)[5] 。那就意味着⼆维数组传参本质上也是传递了地址,传递的是第⼀⾏这个⼀维数组的地址。

13.函数指针变量

函数指针变量应该是⽤来存放函数地址的,未来通过地址能够调⽤函数的。
函数是有地址的,函数名就是函数的地址,当然也可以通过&函数名 的⽅ 式获得函数的地址。
如果我们要将函数的地址存放起来,就得创建函数指针变量咯,函数指针变量的写法其实和数组指针⾮常类似。
返回值类型 (*指针变量名)(参数类型列表);比如:

这里只介绍最常用的方式。
14.函数指针数组
把函数的地址存到⼀个数组中,那这个数组就叫函数指针数组.
初始化方式:
返回值类型 (*数组名[数组大小])(参数类型列表);
  • 核心:(*数组名[大小])的括号绝对不能省略
  • 优先级:[]>*>(),括号改变优先级,确保先和[]结合成数组,再和*结合成指针数组,最后和()结合成函数指针数组。

比如:

http://www.jsqmd.com/news/696726/

相关文章:

  • 告别截图!手把手教你用Mermaid.js在个人博客里画可交互流程图(附国内CDN)
  • 量子计算演进:从NISQ到FTQC的技术挑战与突破
  • flask:sqlalchemy:upgrade报错:Invalid use of NULL value
  • linux:银河麒麟服务器版安装python
  • PyQt5 QThread实战:告别界面卡顿,构建响应式GUI应用
  • LSTM在多元时间序列预测中的实战应用
  • 炉石传说终极插件指南:HsMod 完全配置手册
  • AI落地价值 = (高质量数据 × 精准问题定义) × AI能力
  • flask:用flasgger显示文档(flask+swagger)
  • [具身智能-431]:urdf-loaders ,目前 Web 端进行机械臂 URDF 3D 仿真最标准、最成熟的开源解决方案。
  • 使用CMSIS-DSP Python封装实现ECG信号滤波与嵌入式移植
  • linux: 查看银河麒麟的版本
  • [具身智能-436]:姿(Pose)、位置(Position)和姿态(Orientation)
  • 2026毕业季收藏:论文免费降AI率攻略,亲测AIGC从92%降到16%(含神级指令) - 降AI实验室
  • 端渲染?流渲染?到底怎么选!
  • 实战 | 解密CUTTag:从抗体选择到数据解读,关键环节逐一击破!
  • StructBERT-中文-large效果展示:LCQMC/STS/BQ多数据集验证的惊艳相似度匹配
  • Qwen3-4B-Instruct镜像免配置:log日志分级查看与错误码速查手册
  • Gradle、AGP、Plugin插件基本知识
  • 宏源期货白糖“保险+期货”项目助力罗城蔗农稳收增收
  • Bitwarden CLI受陷,被指与Checkmarx 供应链攻击有关
  • flask:用flasgger显示响应体文档
  • 好用的复合土工膜排名
  • 嵌入式芯片硬件缺陷的软件绕过机制与实现
  • RWKV7-1.5B-g1a镜像免配置部署:CSDN平台7860端口服务管理与健康检查全流程
  • 避坑指南:Webots仿真中激光雷达(Lidar)和距离传感器的配置、数据读取与可视化(附完整C代码)
  • AI智能体如何变革数据科学:从自动化工作流到人机协作新范式
  • 从Datawhale的Vibe镜像看数据科学协作环境的Docker化实践
  • Kubernetes和机器学习工作负载:从训练到部署的全流程管理
  • GPT-Image-2 不只是AI画图:程序员的原型流正在重写