学过C、C++ 的人都知道,指针是它们的灵魂也是难点,理解起来比较困难,想得心应手的应用更加是难点,现在我详细的讲解一下,希望对各位有所帮助!
一、指针到底是什么?
很多初学者把指针理解成"存储地址的变量"。这个说法没错,但还不够深入。真正理解 C++ 指针,需要同时掌握:地址 → 类型 → 解引用 → 指针运算 → 生命周期 → 内存管理 → 对象模型 → 多态。
int a = 100;
int* p = &a;
核心关系:
a = 100 ← *p p = &a → a
a:对象本身
&a:a 的地址
p:指针变量,保存 a 的地址
*p:访问 p 指向的对象
因此 *p = 200; 实际上就是修改 a = 200。
二、为什么指针必须有类型?
例如:
int* p;
double* q;
它们虽然本质上都是地址,但指向的对象类型不同。指针类型决定了指针如何解释目标内存,以及指针运算一次移动多少字节。
假设 sizeof(int) = 4:
p → 1000 p + 1 → 1004 p + 2 → 1008
p + 1 并不是简单让地址 +1,而是向后移动 sizeof(int) 个字节。
三、数组和指针
int arr5 = {10, 20, 30, 40, 50};
在很多表达式中,arr 会转换成 &arr0。
因此:
arr2 == *(arr + 2)
数组可以横向理解为:
arr → arr0 → arr1 → arr2 → arr3 → arr4
10 20 30 40 50
四、指针最核心的能力:间接访问
void change(int* p)
{
*p = 100;
}
int a = 10;
change(&a);
关系:
a → &a → p → *p → a
函数虽然没有直接拿到 a,但拿到了 &a,因此可以通过 *p 修改 a。这叫间接访问(Indirection)。
五、为什么函数需要指针?
void swap(int* a, int* b)
{
int temp = *a;
*a = *b;
*b = temp;
}
int x = 10;
int y = 20;
swap(&x, &y);
调用关系:
x → &x → a y → &y → b
↓ ↓
*a *b
最终:x = 20,y = 10。
指针让函数能够直接操作调用者的对象。
六、指针和引用
指针:
int* p = &a;
可以:
p = &b;
p = nullptr;
关系:
p → a
p → b
p → nullptr
引用:
int& r = a;
绑定之后不能重新绑定:
r → a
可以简单理解:指针可以重新指向;引用一旦绑定就不能换对象。
七、nullptr
现代 C++ 推荐:
int* p = nullptr;
表示 p 当前不指向任何有效对象。
可以:
if (p)
{
std::cout << *p;
}
不能:
int* p = nullptr;
*p = 10;
因为这是通过空指针访问不存在的对象。
八、指针真正危险的地方:生命周期
错误示例:
int* getPointer()
{
int x = 100;
return &x;
}
执行过程:
函数内部 x = 100 → 返回 &x → 函数结束 → x 生命周期结束 → p 仍保存旧地址 → 悬空指针
关键认识:地址存在 ≠ 对象仍然存在。
指针保存的是地址,但不会自动保证那个地址对应的对象仍然有效。
九、动态内存
传统 C++:
int* p = new int(100);
关系:
p ─→ 堆上的 int 对象 = 100
使用:
std::cout << *p;
释放:
delete p;
p = nullptr;
忘记 delete:可能造成内存泄漏。
重复 delete:可能造成重复释放。
delete 后继续使用:属于使用已经释放的内存。
十、为什么现代 C++ 推荐智能指针?
现代 C++ 更推荐:
auto p = std::make_unique<int>(100);
而不是:
int* p = new int(100);
常见智能指针:
unique_ptr → 独占所有权
shared_ptr → 共享所有权
weak_ptr → 弱引用,不拥有对象
可以记住:裸指针主要解决"指向哪里",智能指针进一步解决"谁负责释放"。
十一、二级指针
int a = 100;
int* p = &a;
int** pp = &p;
关系:
pp → p → a
*pp 得到 p。
**pp 得到 a。
因此:
**pp = 200;
最终:
pp → p → a = 200
十二、二级指针有什么用?
典型情况:函数需要修改一个指针本身。
void allocate(int** p)
{
*p = new int(100);
}
int* p = nullptr;
allocate(&p);
调用后:
p → int(100)
因为函数需要修改的是 int*,所以需要 int**。
十三、三级指针
int*** ppp;
关系:
ppp → pp → p → int
***ppp 最终访问 int。
三级甚至多级指针主要出现在底层系统编程、C API、复杂数据结构以及某些库接口中,日常 C++ 并不常见。
十四、函数指针
指针不仅可以指向数据,也可以指向函数。
int add(int a, int b)
{
return a + b;
}
int (*p)(int, int) = add;
关系:
p → add()
调用:
int result = p(10, 20);
函数指针常用于回调函数、事件处理、C API、系统编程和插件机制。
十五、this 指针
class Person
{
public:
int age;
void setAge(int age)
{
this->age = age;
}
};
this 是当前对象的指针。
例如:
Person p;
p.setAge(20);
可以理解为:
p → Person对象 ← this
this->age 就是当前对象的 age。
十六、-> 到底是什么?
Person p;
Person* ptr = &p;
ptr->age
等价于:
(*ptr).age
因此:
ptr → Person对象
↓
age
-> 本质上就是"先解引用指针,再访问成员"。
十七、指针与多态
class Animal
{
public:
virtual void speak()
{
std::cout << "Animal";
}
virtual ~Animal() = default;
};
class Dog : public Animal
{
public:
void speak() override
{
std::cout << "Dog";
}
};
Animal* p = new Dog();
p->speak();
输出:Dog
这里:
p 的类型 → Animal*
实际对象 → Dog
通过虚函数机制调用 Dog::speak(),这就是运行时多态。
十八、指针与内存布局
可以粗略理解程序内存:
Code │ Global/Static │ Stack │ Heap
例如:
int global = 10;
int main()
{
int local = 20;
int* p = new int(30);
}
可以理解为:
Global:global = 10 │ Stack:local = 20, p │ Heap:30
其中:
p → Heap 中的 30
因此必须区分:
p = 地址
*p = 地址对应的对象
十九、& 和 * 的真正理解
不要只死记"& = 取地址,* = 解引用"。
更好的理解:
&a→ a 在哪里
p → 保存这个地址
*p → 根据地址找到 a
例如:
int a = 10;
int* p = &a;
*p = 20;
过程:
a = 10 ← &a → p → *p
↓
修改 a
↓
a = 20
二十、指针知识地图
学习顺序:
地址 → 指针 → & → * → 指针类型 → 指针运算 → 数组
→ 函数参数 → const 与指针 → 二级指针 → 函数指针
→ this 指针 → 动态内存 → 生命周期 → 悬空指针
→ 智能指针 → 多态 → 虚函数表 → C++ 对象模型
学习阶段:
基础:&、*、指针、nullptr
进阶:数组、指针运算、函数参数
深入:const、二级指针、函数指针
内存:new/delete、生命周期、悬空指针
现代 C++:unique_ptr、shared_ptr、weak_ptr
高级:this、多态、虚函数表、对象模型
总结:真正理解指针
真正理解指针的关键,不是记住各种语法,而是始终保持这个模型:
指针 p 保存地址→ 对象 实际存储数据→ value
↑ ↑
&obj *p
一旦这个模型彻底建立起来,数组、引用、类、this、智能指针、虚函数、多态和内存管理都会容易很多。