C++ 中指针和引用的主要区别是什么?
在 C++ 中,指针(Pointer)和引用(Reference)都可以用来间接访问内存中的对象,但它们在语法、底层机制以及使用场景上有非常显著的区别。
为了方便记忆,你可以将核心区别总结为一句话:指针是一个实体变量,存储着内存地址;而引用只是一个已经存在的变量的“别名”。
以下是它们的详细区别:
1. 核心区别对照表
| 特性 | 指针 (Pointer) | 引用 (Reference) |
|---|---|---|
| 本质 | 存放某个对象地址的实体变量,有自己的内存空间。 | 某个已知变量的别名,不占用额外的逻辑内存空间(底层实现通常是常量指针)。 |
| 初始化 | 可以在定义时不初始化(虽不推荐,但合法)。 | 必须在定义时初始化。 |
| 重新赋值 (Reassignment) | 可以随时改变指向,指向不同的对象。 | 一旦绑定到一个对象,就不能再改变为其他对象的引用。 |
| 空值 (Nullability) | 可以为空(nullptr 或 NULL)。 |
不能为空。必须总是引用一个有效的对象。 |
| 多级使用 | 支持多级指针(如 int**,指向指针的指针)。 |
不支持多级引用(没有引用的引用 int& &,不过 C++11 引入了右值引用 &&)。 |
| 算术运算 | 支持指针算术运算(如 p++,移动到下一个内存位置)。 |
不支持算术运算。对引用进行 ++ 操作,实际上是对它引用的原始变量进行 ++。 |
sizeof 的结果 |
sizeof(指针) 得到的是指针本身的大小(通常是 4 或 8 字节)。 |
sizeof(引用) 得到的是被引用对象的大小。 |
| 解引用 | 需要显式使用 * 操作符来解引用(如 *p)。 |
不需要解引用,直接使用引用名即可(编译器在底层自动处理)。 |
2. 代码示例对比
指针的行为
cpp
int a = 10;
int b = 20;
int* p; // 合法:可以不初始化(此时是野指针)
p = &a; // 指向 a
*p = 15; // a 的值变成了 15
p = &b; // 合法:指针可以改变指向,现在指向 b
*p = 25; // b 的值变成了 25
p = nullptr; // 合法:指针可以为空
引用的行为
cpp
int a = 10;
int b = 20;
// int& r; // 错误:引用必须初始化
int& r = a; // 合法:r 是 a 的别名
r = 15; // a 的值变成了 15
r = b; // 注意!这不是改变引用的绑定,而是把 b 的值(20)赋值给 r 所引用的对象(a)
// 此时 a 的值变成了 20,r 依然是 a 的别名。
3. 深层细节区别
1. 内存占用
- 指针本身是一个变量,在栈(或堆)上确确实实占用 4 个字节(32位系统)或 8 个字节(64位系统)。
- 引用在 C++ 语法层面上被描述为别名,没有自己的地址(你对引用取地址
&r,得到的是原始变量a的地址)。但在编译器底层,引用通常是通过**指针(常量指针Type* const)**来实现的,因此在底层它实际也会占用内存,只是 C++ 语言层面对程序员隐藏了这一点。
2. 常量(Const)修饰符
指针和 const 的结合有多种形式,容易混淆;而引用比较简单:
- 指针:
const int* p(或int const* p):底层 const,指针指向的值不能通过指针修改,但指针的指向可以变。int* const p:顶层 const,指针的指向不能变,但指向的值可以修改。const int* const p:指向和值都不能变。
- 引用:
const int& r:常引用,不能通过引用修改原始变量的值。(没有int& const r这种写法,因为引用本身就天生自带“指向不能变”的属性)。
4. 最佳实践:什么时候用哪个?
在 C++ 社区中有一个著名的经验法则:“能用引用时尽量用引用,必须用指针时才用指针。”
推荐使用【引用】的场景:
- 函数参数传递: 为了避免对象拷贝带来的性能损耗(尤其是
std::string,std::vector等大对象),通常使用常引用const T&。 - 函数返回值: 比如重载运算符
operator=或operator[]时,返回引用以支持链式操作或修改容器内部元素。 - 确定对象一定存在: 如果你需要传递一个变量,并且这个变量在逻辑上绝对不可能为空,用引用可以省去判空的麻烦和开销。
必须使用【指针】的场景:
- 需要表示“可选”或“空(Null)”语义: 如果一个函数参数是可选的,传入
nullptr代表没有传入该参数,此时必须用指针。 - 动态内存管理: 使用
new分配在堆上的内存,必须使用指针来接收(如现代 C++ 中的智能指针std::unique_ptr,std::shared_ptr)。 - 底层数据结构实现: 在实现链表、树、图等数据结构时,节点之间的连接需要改变指向,必须使用指针。
- C 语言接口兼容: 当你需要调用 C 语言编写的库(API)时,C 语言没有引用,只能传递指针。
- 数组操作和指针算术: 遍历 C 风格数组等需要指针偏移的场景。