第六章:指针与引用——C++的灵魂所在
说实话,指针和引用这个话题,是很多C++初学者最头疼的部分。我记得自己刚学的时候,也被绕得晕头转向。但等你真正理解了,你会发现——指针就是C++最强大的武器,没有之一。
这一章,我会带你从最基础的概念开始,一步步深入到指针的各种用法。别急,慢慢来。
核心提示:指针存的是地址,引用是别名。这两个概念搞清楚了,后面就顺了。
6.1 指针基础
指针是什么?说白了,指针就是一个变量,它存的是另一个变量的内存地址。你可以通过这个地址,间接访问那个变量。
int a = 10;
int* p = &a; // p 存的是 a 的地址
cout << *p; // 输出 10,*p 是解引用
我个人习惯把指针声明写成 int* p,这样一眼就能看出「p 是一个指向 int 的指针」。当然也有人写成 int *p,语法上没问题,但我觉得前者更清晰。
小技巧:声明指针时,最好同时初始化。不初始化的指针是野指针,非常危险。我见过太多因为野指针导致的崩溃了。
6.2 指针运算
指针也能做加减运算,但要注意——它加减的是「指向类型的大小」,不是字节数。
int arr[] = {10, 20, 30, 40};
int* p = arr; // 指向 arr[0]
p++; // 现在指向 arr[1]
cout << *p; // 输出 20
为什么会这样?因为 p++ 实际上移动了 sizeof(int) 个字节(通常是4字节)。这个设计很巧妙,让你不用关心具体字节数,只管「跳到下一个元素」就行。
我曾经在做一个图像处理项目时,用指针运算遍历像素数组,速度比下标访问快了不少。嗯,性能敏感的场景,指针运算是个好选择。
警告:指针运算越界是常见 bug。C++ 不会帮你检查边界,全靠你自己把控。写代码时一定要确认指针还在合法范围内。
6.3 指针与数组
数组名其实就是一个常量指针,指向数组的第一个元素。所以 arr 和 &arr[0] 是等价的。
int arr[5] = {1, 2, 3, 4, 5};
int* p = arr; // 等价于 int* p = &arr[0];
cout << p[2]; // 输出 3,指针也能用下标
你想想看,既然指针能用下标,数组名也能用指针运算——*(arr + 2) 就是 arr[2]。这两者其实是同一回事。
我记得有一次面试,面试官问我:「数组名和指针有什么区别?」我回答:「数组名是常量指针,不能修改;指针是变量,可以指向别处。」他点了点头。嗯,这个区别很重要。
6.4 指针与函数
指针可以作为函数参数,实现「传址调用」——在函数内部修改外部变量的值。这在需要返回多个结果时特别有用。
void swap(int* a, int* b) {
int temp = *a;
*a = *b;
*b = temp;
}
int x = 5, y = 10;
swap(&x, &y); // 现在 x=10, y=5
还有一种更高级的用法——函数指针。你可以把函数当作参数传递,实现回调机制。
int add(int a, int b) { return a + b; }
int sub(int a, int b) { return a - b; }
int operate(int (*func)(int, int), int x, int y) {
return func(x, y);
}
cout << operate(add, 10, 5); // 输出 15
我在写一个插件系统时,大量使用了函数指针。每个插件注册自己的处理函数,主程序通过函数指针调用。灵活得很。
6.5 动态内存管理(new/delete)
动态内存分配,说白了就是「在程序运行时,按需申请内存」。C++ 用 new 和 delete 来管理。
int* p = new int; // 申请一个 int 大小的内存
*p = 42;
delete p; // 释放内存
int* arr = new int[10]; // 申请数组
arr[0] = 1;
delete[] arr; // 释放数组,注意用 delete[]
重要提醒:用 new 申请的内存,必须用 delete 释放。否则会造成内存泄漏。我曾经在一个服务器程序里,因为忘记释放一个循环中 new 出来的对象,导致内存持续增长,最后服务器挂了。从那以后,我每次 new 都会立刻写对应的 delete。
还有一个常见问题:new 和 malloc 有什么区别?new 会调用构造函数,malloc 不会。所以在 C++ 中,优先用 new。
6.6 引用
引用是 C++ 对指针的「友好封装」。它本质上是一个别名,一旦绑定就不能改变。
int a = 10;
int& ref = a; // ref 是 a 的引用
ref = 20; // 现在 a 也变成了 20
引用和指针的区别,我总结一下:
| 特性 | 指针 | 引用 |
|---|---|---|
| 是否可以为空 | 可以(nullptr) | 不可以,必须初始化 |
| 是否可以重新绑定 | 可以 | 不可以 |
| 是否需要解引用 | 需要(*p) | 不需要,直接使用 |
| 语法简洁性 | 较复杂 | 简洁 |
我个人习惯:能用引用就用引用,语法更干净。但需要「重新指向」或「可能为空」时,就用指针。
避坑指南:不要返回局部变量的引用!局部变量在函数结束后就销毁了,返回它的引用会导致悬空引用。我曾经在代码审查时抓到过这种 bug,排查了好久才找到原因。
知识体系总览
下面这张图,把本章的核心知识点串起来了。你可以把它当作复习提纲。
好了,指针和引用就讲到这里。这些东西需要多练,光看是记不住的。打开你的 IDE,把上面的代码都敲一遍,改一改,看看会发生什么。实践出真知。