1. 指针的本质:从内存地址理解起
指针在C/C++中之所以让初学者感到恐惧,很大程度上是因为教材和课程往往一上来就抛出"指针是存储内存地址的变量"这样的定义。这种抽象描述对没有底层基础的学习者来说,就像直接告诉一个没见过汽车的人"发动机是内燃机"一样毫无意义。
让我们换个角度理解:假设你住在一栋公寓楼里,每个房间都有唯一的门牌号。指针其实就是记录这些门牌号的便利贴。比如你朋友住在305房间,你在便利贴上写下"305",这就是一个指针。通过这个便利贴,你能快速找到你朋友的住处,而不需要记住房间里的具体陈设。
在计算机中,内存被划分为无数个"小房间"(字节),每个房间都有唯一的门牌号(地址)。当我们声明一个变量时:
c复制int num = 42;
系统会做三件事:
- 分配一个足够大的"房间"(通常是4个字节)
- 给这个房间贴上标签"num"
- 在房间里放入值42
而指针变量存储的就是这个房间的门牌号。通过&运算符可以获取这个地址:
c复制int* ptr = # // ptr现在存储的是num的地址
关键理解:指针本身也是一个变量,它也需要占用内存空间来存储地址值。在32位系统上指针通常占4字节,64位系统占8字节。
2. 指针操作的四重境界
2.1 第一重:基础指针操作
指针的基本操作符就两个:
&取地址运算符*解引用运算符
常见误区是混淆声明和使用时的*符号:
c复制int num = 10;
int *p = # // 声明时的*表示p是指针类型
int val = *p; // 使用时的*表示解引用
2.2 第二重:指针与数组的关系
数组名在大多数情况下会退化为指向首元素的指针:
c复制int arr[5] = {1,2,3,4,5};
int *p = arr; // 等价于 &arr[0]
但要注意两个关键区别:
sizeof(arr)返回整个数组的字节大小,而sizeof(p)返回指针的大小&arr和&p的类型不同(前者是int(*)[5],后者是int**)
2.3 第三重:多级指针
二级指针(指针的指针)常用于修改指针本身:
c复制void allocate(int** pp) {
*pp = malloc(sizeof(int));
**pp = 42;
}
int main() {
int *p = NULL;
allocate(&p);
printf("%d", *p); // 输出42
free(p);
}
2.4 第四重:函数指针
函数指针允许我们将函数作为参数传递:
c复制int compare(int a, int b) {
return a - b;
}
void sort(int *arr, int n, int (*cmp)(int,int)) {
// 使用cmp函数指针进行比较
}
sort(array, 10, compare);
3. 现代C++中的智能指针
3.1 为什么需要智能指针
原始指针最大的问题是内存管理。看这个典型错误:
cpp复制void leak() {
int *p = new int(10);
// 忘记delete
}
C++11引入了三种智能指针:
unique_ptr:独占所有权,不可复制shared_ptr:共享所有权,引用计数weak_ptr:不增加引用计数的观察者
3.2 unique_ptr的使用
cpp复制#include <memory>
void safe() {
auto p = std::make_unique<int>(10);
// 离开作用域自动释放
}
3.3 shared_ptr的陷阱
循环引用会导致内存泄漏:
cpp复制s
