C++中的指针
基本概念
指针本质上也是一种变量,不过它存储的是对应变量类型的地址。
为什么要有指针
之所以要有指针,是因为虽然变量本身就包含了地址的信息(不然我们也没法通过&来取址),但变量和其地址之间的关系是一一对应的且无法修改的,无法满足我们对地址本身进行处理的需求。例如如果我们要实现数组的数据结构,将数据放入连续的内存中,那么我们需要解决下面的问题
- 这块连续内存的起始地址是什么?
- 访问数组中的某个元素时,我们该如何确定它的地址?
假如这个元素的索引是i,数组中的元素大小都是T,那么我们访问它的时候就需要通过首地址+i*T来获取它的地址。
那么问题来了,地址本身是个整型数据,为什么我们不直接使用int等整型类型来存储和操作呢?
- 首先,需要维护一个额外的变量来告诉我们这个地址对应的变量大小,否则我们没法对这个地址取值,也没办法进行前面提到的数组寻址操作
- 其次,这样写的代码可读性很差
- 再者,不同平台的地址大小不同,如果我们在32位系统中使用
int来存储地址,那么这部分代码就无法在64位的系统中运行
因此,定义一种新的变量来存储和操作地址是很有必要的。
指针的基本操作
- 赋值和初始化
1
2int a = 10;
int* p = &a; - 解引用
指针通过*操作符获取指向的内存中存储的值,值的类型由指针的类型确定 - 指针运算
- 加减运算
T*类型的指针p + i相当于p向前移动了i * sizeof(T)个字节- 支持
++和--
- 比较运算
比较两个指针的值,也就是比较两个地址是否相同
- 加减运算
指针与数组
数组名本质是指向数组首地址的、不可修改的常量指针。在作为函数参数时,它会退化为指向数组首元素的指针,这时候可以对它进行自增等运算。
int (*p)[10]表示这是一个指向长度为10的int数组的指针,其类型为int (*)[]。也就是说p++会令p向前移动40个字节int *p[10]表示这是一个包含了10个int*变量的数组。作为函数参数退化为指针后,它的类型是int**
函数指针
每个函数都有一个唯一的入口地址,函数指针存储的就是这个地址。
声明
1 | |
这里的funcPtr就是一个指向函数的指针,接受两个int参数并返回int值。
赋值与调用
- 函数名在大多数情况下都会被编译器解释为函数的地址,因此可以直接用函数名给函数指针赋值。
- 函数指针的调用方式与函数名的调用方式一致
- 基于提高可读性的目的,可以使用
typedef来封装函数指针
1 | |
指针函数
指针函数是返回指针的函数。
1 | |
注意和函数指针的区别在于是否有括号将*+标识符包裹起来,关于C/C++中的声明规则,可以阅读C Right-Left Rule (Rick Ord’s CSE 131
回调函数
通过回调函数,程序在运行时可以动态地决定调用哪个函数。这常常用于事件处理和异步编程的场景中,例如GUI编程中的按钮和ROS中对topic的订阅都会绑定对应的回调函数。在C语言中,回调函数通过函数指针来实现。
回调函数的实现
- 设计包含回调函数参数的函数接口
- 调用1.中的函数并传递合适的回调函数
1 | |
void *
- 任何类型的指针都可以直接赋值给
void指针,且无需进行强制类型转换 - 而
void指针不能直接赋值给其它指针,必须进行显示类型转换 void指针可以和其他指针比较存放的地址是否相同void指针只有在进行强制类型转换后才可以对其进行正常的指针操作
最佳实践
- 避免野指针:始终确保指针在使用前已经正确初始化,并且在不再需要时将其设置为
nullptr。 - 避免内存泄漏:动态分配的内存在使用完毕后一定要使用
free释放。 - 有效性检查:在解引用前检查它是否是
nullptr。 - 使用智能指针:C++11引入的智能指针可以自动管理内存,减少内存泄露的风险。
C++中的指针
https://guts.homes/2025/06/06/cpp-pointer/