指针(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; |
不可以,绑定后终身不变 |
| 访问方式 | *p 或 p-> |
直接用 r,像普通变量 |
| 使用场景 | 动态内存、可选参数、数组遍历 | 函数参数传递(避免拷贝) |
8. 总结
| 概念 | 一句话 |
|---|---|
| 指针是什么 | 存储内存地址的变量 |
& |
取地址 |
* |
解引用(通过地址访问数据) |
-> |
通过指针访问成员 |
nullptr |
空指针,不指向任何东西 |
| 裸指针 | 最基础的指针,需手动管理内存 |
unique_ptr |
独占所有权的智能指针,不可拷贝只能移动 |
shared_ptr |
共享所有权的智能指针,引用计数管理 |
| 野指针 | 指向已释放内存的指针 |
作者: Qing | 修改日期: 2026-07-18