C++ 指针的深度理解与应用

学过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、智能指针、虚函数、多态和内存管理都会容易很多。

相关推荐
鲨鱼辣钊10 小时前
【FastAPI筑基-Day19】APScheduler定时任务全实战|自动执行、动态启停、后台常驻
java·spring·fastapi
不正经学生12 小时前
C语言预处理详解:编译器真正动手之前的那些事
c语言·开发语言·算法·面试·bug
毕竟是shy哥15 小时前
计算YOLO数据集中每个类的目标数
算法·yolo·机器学习
M78佐菲15 小时前
Linux学习笔记:TCP协议
linux·笔记·学习·tcp/ip·算法
恋恋西风15 小时前
C++ 理解 std::thread 在单核和多核上的行为差异
开发语言·c++
学长毕业设计15 小时前
基于SpringBoot的公益基金管理系统(源码+文档+讲解视频)
java·spring boot·后端
Brilliantwxx15 小时前
【Linux】 进程(9)程序与进程地址空间(基础+进阶+面试题)
linux·运维·服务器·开发语言·c++
东小西15 小时前
【SAA实战】第 3 篇 · 工具调用全攻略:把业务能力交给 Agent 自己调度
java·后端·spring
东小西15 小时前
【SAA实战】第 4 篇 · Agent 短期记忆:saver 让 Agent 跨轮记得住(threadId 隔离)
java·后端·spring
许彰午15 小时前
22-DataCenter报文序列化
java·低代码·架构·状态模式