C++中的指针

基本概念

指针本质上也是一种变量,不过它存储的是对应变量类型的地址。

为什么要有指针

之所以要有指针,是因为虽然变量本身就包含了地址的信息(不然我们也没法通过&来取址),但变量和其地址之间的关系是一一对应的且无法修改的,无法满足我们对地址本身进行处理的需求。例如如果我们要实现数组的数据结构,将数据放入连续的内存中,那么我们需要解决下面的问题

  1. 这块连续内存的起始地址是什么?
  2. 访问数组中的某个元素时,我们该如何确定它的地址?
    假如这个元素的索引是i,数组中的元素大小都是T,那么我们访问它的时候就需要通过首地址+i*T来获取它的地址。

那么问题来了,地址本身是个整型数据,为什么我们不直接使用int等整型类型来存储和操作呢?

  • 首先,需要维护一个额外的变量来告诉我们这个地址对应的变量大小,否则我们没法对这个地址取值,也没办法进行前面提到的数组寻址操作
  • 其次,这样写的代码可读性很差
  • 再者,不同平台的地址大小不同,如果我们在32位系统中使用int来存储地址,那么这部分代码就无法在64位的系统中运行
    因此,定义一种新的变量来存储和操作地址是很有必要的。

指针的基本操作

  • 赋值和初始化
    1
    2
    int 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
2
//返回类型 (*指针名)(函数参数列表);
int (*funcPtr)(int, int);

这里的funcPtr就是一个指向函数的指针,接受两个int参数并返回int值。

赋值与调用

  • 函数名在大多数情况下都会被编译器解释为函数的地址,因此可以直接用函数名给函数指针赋值。
  • 函数指针的调用方式与函数名的调用方式一致
  • 基于提高可读性的目的,可以使用typedef来封装函数指针
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

int add(int a, int b) {​
return a + b;​
}​

// 封装后的函数指针类型​
typedef int (*FuncPtr)(int, int);​

int main() {​
// 声明一个函数指针变量​
FuncPtr fp = add;​

// 通过函数指针调用函数​
int result = fp(3, 4);​
printf("Result: %d\n", result); // 输出: Result: 7​

return 0;​
}​

指针函数

指针函数是返回指针的函数。

1
int * add(int a, int b);

注意和函数指针的区别在于是否有括号将*+标识符包裹起来,关于C/C++中的声明规则,可以阅读C Right-Left Rule (Rick Ord’s CSE 131

回调函数

通过回调函数,程序在运行时可以动态地决定调用哪个函数。这常常用于事件处理和异步编程的场景中,例如GUI编程中的按钮和ROS中对topic的订阅都会绑定对应的回调函数。在C语言中,回调函数通过函数指针来实现。

回调函数的实现

  1. 设计包含回调函数参数的函数接口
  2. 调用1.中的函数并传递合适的回调函数
1
2
3
4
5
6
7
8
9
void process(int x, int y, FuncPtr callback) {​
// ... 执行一些操作 ...​
int result = callback(x, y);​
// ... 使用回调函数的返回值进行进一步处理 ...​
}​
int main() {​
process(3, 4, add);​ //可以传递div,multi等等函数,只要函数的参数和返回值与函数指针一致即可
return 0;​
}

void *

  • 任何类型的指针都可以直接赋值给void指针,且无需进行强制类型转换
  • void指针不能直接赋值给其它指针,必须进行显示类型转换
  • void指针可以和其他指针比较存放的地址是否相同
  • void指针只有在进行强制类型转换后才可以对其进行正常的指针操作

最佳实践

  • 避免野指针:始终确保指针在使用前已经正确初始化,并且在不再需要时将其设置为nullptr
  • 避免内存泄漏:动态分配的内存在使用完毕后一定要使用free释放。
  • 有效性检查:在解引用前检查它是否是nullptr
  • 使用智能指针:C++11引入的智能指针可以自动管理内存,减少内存泄露的风险。

C++中的指针
https://guts.homes/2025/06/06/cpp-pointer/
作者
guts
发布于
2025年6月6日
许可协议