Skip to content

指针(Pointer)

指针是 C/C++ 最核心也最容易出错的机制。理解指针是理解移动语义、动态内存和 GSRL 代码的基础。


1. 什么是指针

指针是一个存地址的变量。普通变量存的是值(42、3.14、'A'),指针存的是"某个数据在内存中的门牌号"。

int  x  = 42;      // x 是一个 int,存的值是 42
int* p  = &x;      // p 是一个指针,存的是 x 的地址
内存示意图:

地址:    0x1000       0x1008
         ┌──────┐    ┌──────┐
内容:    │  42  │    │0x1000│
         └──────┘    └──────┘
变量名:     x            p
类型:      int         int*

2. 核心操作符:&*

操作符 名称 作用 示例
& 取地址 获取变量的内存地址 int* p = &x;
* 解引用 通过地址访问实际数据 *p = 100; 等价于 x = 100;
int x = 42;
int* p = &x;       // p 指向 x

*p = 100;          // 通过指针修改 x 的值
// 现在 x == 100

int y = *p;        // 通过指针读取 x 的值
// y == 100

3. 指针的类型

3.1 裸指针(Raw Pointer)

这是最基础的指针,直接存储内存地址。

int*    pi;    // 指向 int 的指针
float*  pf;    // 指向 float 的指针
PIDParam* pp;  // 指向 PIDParam 结构体的指针
void*   pv;    // 万能指针,可以指向任何类型(但不能直接解引用)

指针的类型决定了"解引用时读多少字节":

int* pi = ...;
*pi;   // 读取 4 个字节(假设 int 是 4 字节)

double* pd = ...;
*pd;   // 读取 8 个字节

3.2 空指针(nullptr

int* p = nullptr;  // p 不指向任何有效内存
// *p = 42;        // ❌ 解引用空指针 → 程序崩溃!

使用指针前,应该检查:

if (p != nullptr)
{
    *p = 42;  // 安全
}

3.3 指向成员的指针(-> 操作符)

struct Point { float x, y; };

Point pt = {1.0f, 2.0f};
Point* pp = &pt;

pp->x = 10.0f;   // 等价于 (*pp).x = 10.0f
pp->y = 20.0f;   // 等价于 (*pp).y = 20.0f

-> 是"先解引用再访问成员"的简写。

3.4 指针与数组

int arr[5] = {10, 20, 30, 40, 50};
int* p = arr;         // 数组名退化为指向首元素的指针
// 等价于 int* p = &arr[0];

p[0];  // 10
p[1];  // 20
p[2];  // 30
// p[i] 等价于 *(p + i)

3.5 常量与指针

const int* p1;        // 指向常量的指针——不能通过 p1 修改数据
int* const p2 = &x;   // 常量指针——p2 不能指向别处
const int* const p3;  // 两者都不能改

4. 智能指针(Smart Pointer)

裸指针的问题是容易忘记 delete,导致内存泄漏。C++11 引入了智能指针来自动管理生命周期。

4.1 std::unique_ptr——独占所有权

#include <memory>

std::unique_ptr<int> p1 = std::make_unique<int>(42);
// *p1 == 42

// std::unique_ptr<int> p2 = p1;  // ❌ 编译错误:unique_ptr 不可拷贝!
std::unique_ptr<int> p2 = std::move(p1);  // ✅ 移动:p2 接管,p1 变为 nullptr

// 离开作用域时自动 delete,不会泄漏

一句话:unique_ptr 保证同一时刻只有一个指针拥有这块内存。 所以它不能拷贝,只能移动。这正是移动语义的核心应用场景。

4.2 std::shared_ptr——共享所有权

std::shared_ptr<int> p1 = std::make_shared<int>(42);
std::shared_ptr<int> p2 = p1;   // ✅ 可以拷贝,引用计数 +1
// p1 和 p2 指向同一块内存,引用计数 = 2
// 最后一个 shared_ptr 销毁时自动释放内存

4.3 为什么 GSRL 中 unique_ptr 重要

当你有一个对象不能被随意复制(比如它持有独占资源),使用 unique_ptr 可以:

  • 编译期阻止意外的拷贝
  • 明确表达"我独占这个资源"的意图
  • 通过 std::move 显式转移所有权

5. 指针的危险操作

5.1 野指针(Dangling Pointer)

int* getPointer()
{
    int x = 42;
    return &x;  // ❌ 返回局部变量的地址!
}  // x 被销毁

int* p = getPointer();
*p = 100;  // ❌ 访问已释放的栈内存 → 未定义行为

5.2 重复释放(Double Free)

int* p = new int(42);
delete p;
delete p;  // ❌ 重复释放 → 未定义行为,通常崩溃

5.3 内存泄漏(Memory Leak)

void func()
{
    int* p = new int[1000];
    // 忘记 delete[] p
}  // 1000 个 int 永远丢失

6. GSRL 中的指针实例

6.1 行为树中的裸指针

// alg_behavior_tree.hpp
BTNode *m_children[MaxChildren];  // 指针数组,存储子节点的地址

这里用裸指针是因为: - 节点生命周期由行为树统一管理 - 不需要智能指针的额外开销 - 嵌入式环境追求最小开销

6.2 动态分配的数组指针

// SimplePID 中
fp32 *m_errorHistory;  // 指向堆上动态分配的数组

// 构造时
m_errorHistory = new fp32[historySize];

// 析构时
delete[] m_errorHistory;

6.3 空指针初始化

// alg_behavior_tree.hpp
memset(static_cast<void *>(m_children), 0, sizeof(m_children));
// 将指针数组全部置零(即 nullptr)

7. 指针 vs 引用

特性 指针 引用
语法 int* p = &x; int& r = x;
可否为空 可以 nullptr 不可以,必须绑定有效对象
可否重新绑定 可以 p = &y; 不可以,绑定后终身不变
访问方式 *pp-> 直接用 r,像普通变量
使用场景 动态内存、可选参数、数组遍历 函数参数传递(避免拷贝)

8. 总结

概念 一句话
指针是什么 存储内存地址的变量
& 取地址
* 解引用(通过地址访问数据)
-> 通过指针访问成员
nullptr 空指针,不指向任何东西
裸指针 最基础的指针,需手动管理内存
unique_ptr 独占所有权的智能指针,不可拷贝只能移动
shared_ptr 共享所有权的智能指针,引用计数管理
野指针 指向已释放内存的指针

作者: Qing | 修改日期: 2026-07-18