目录
一、前言
在 C++ 中,指针(Pointer) 与 引用(Reference) 是两个密切相关又容易混淆的概念。
二者都能实现“间接访问变量”的功能,但在语法、安全性与底层机制上却有根本区别。
与此同时,const 修饰指针时的三种情况(修饰内容、本身、两者皆修饰)也是面试高频考点。
本文将从 语法 → const 修饰 → 底层机制 → 实例验证 → 八股总结 全面讲解这三个主题。
指针:指针是一个变量,存储了另一个变量的内存地址。指针的声明需要在类型前加上星号 *。
引用:引用是另一个变量的别名,它在声明时必须初始化,并且一旦初始化,就不能更改引用的对象。引用的声明需要在类型前加上 &。
二、基础回顾:引用与指针的定义
1)指针(Pointer)
指针是一个变量,存储了另一个变量的地址。可以修改它的指向,也可以操作地址中的内容。
int a = 10;
int *p = &a; // p 保存 a 的地址
*p = 20; // 通过解引用修改 a 的值
2)引用(Reference)
引用是变量的 别名,定义时必须绑定已有变量且不能更改绑定对象。
int a = 10;
int &r = a; // r 是 a 的别名
r = 20; // 等价于 a = 20;
⚠️ 引用不是“真正的变量”,而是编译器在底层用“指针常量”实现的语法糖。
3)指向改变
#include <iostream>
using namespace std;
int main() {
int a = 10, b = 20;
int* p = &a; // 指针指向 a
int& r = a; // 引用绑定 a
cout << "初始状态:" << endl;
cout << "*p = " << *p << ", r = " << r << endl;
p = &b; // ✅ 合法:指针可以改向
// &r = b; // ❌ 不合法:引用不能改绑
r = b; // ✅ 实际是 a = b
cout << "修改后:" << endl;
cout << "*p = " << *p << ", r = " << r << endl;
}
输出:
初始状态:
*p = 10, r = 10
修改后:
*p = 20, r = 20
🧠 实际上:
-
p = &b;→ 指针现在指向 b; -
r = b;→ 给 a 赋值为 b 的值(不是改绑)。
4)指向改变总结
| 项目 | 指针 | 引用 |
|---|---|---|
| 可否改向 | ✅ 可以用 ptr = &x; | ❌ 不可重新绑定 |
| 初始化 | 可延迟 | 必须立即初始化 |
| 是否可为空 | ✅ 可以为 nullptr | ❌ 不可为空 |
| 是否需解引用 | ✅ 需要 *p | ❌ 直接使用即可 |
三、引用与指针的九大区别(八股文总结🔥)
| 对比项 | 引用(Reference) | 指针(Pointer) |
|---|---|---|
| 1️⃣ 定义 | 变量别名 | 存储变量地址 |
| 2️⃣ 初始化 | 必须初始化 | 可不初始化 |
| 3️⃣ 是否可重新指向 | ❌ 不可更改绑定 | ✅ 可改变指向 |
| 4️⃣ 可否为 NULL | ❌ 不存在空引用 | ✅ 可为 nullptr |
5️⃣ sizeof 结果 | 返回被引用类型大小 | 固定为地址大小 (4B/8B) |
| 6️⃣ 自增运算 | 操作对象值(r++) | 移动地址(p++) |
| 7️⃣ 多级形式 | ❌ 无多级引用 | ✅ 可多级 (int**) |
| 8️⃣ 安全性 | ✅ 安全简洁 | ⚠️ 易空悬/野指针 |
| 9️⃣ 用途 | 参数传递、返回值 | 动态内存、数组操作等 |
四、const 修饰指针的三种形式
这里承接我上篇文章关于const顶层、底层高频高点
顶层 vs 底层 const(高频考点)
-
顶层 const:修饰变量自身(“我不可变”);拷贝时常被忽略。
-
底层 const:修饰经由指针/引用访问到的对象(“对方不可变”)。
| 写法 | 顶层/底层 | 含义 |
|---|---|---|
int* const p | 顶层 | p 的指向不可变(常量指针) |
const int* p 或 int const* p | 底层 | 经 p 访问的 int 不可改(内容常量) |
const int* const p | 顶 & 底 | 指向和所指都不可改 |
const int& r = x | 底层 | 通过引用只读(可绑定右值) |
口诀:离变量名最近的
const往往是顶层(如int* const p),指针星号左边的const多为底层。
const可修饰 指针所指向的内容、指针本身、或两者同时修饰。
三者位置不同,含义完全不同。
1️⃣ 修饰“所指向的内容” → 内容不可改
const int* ptr = &a; // 指向 const int
// *ptr = 20; ❌ 错误:不能改内容
ptr = &b; // ✅ 可以改指针方向
✅ 关键点:
-
“指向的内容”是常量,不能通过 ptr 修改;
-
但 ptr 自身可指向别处。
📘 示例:
#include <stdio.h>
int main() {
const int a = 10;
int b = 20;
const int* ptr = &a; // 内容常量
ptr = &b; // 指针可变
printf("*ptr = %d\n", *ptr);
}
2️⃣ 修饰“指针本身” → 地址不可改
int* const ptr = &a; // ptr 是常量指针
*ptr = 20; // ✅ 可以改内容
// ptr = &b; ❌ 不可改地址
✅ 关键点:
-
指针是常量(常量指针);
-
内容可变;
-
常用于固定指针指向。
3️⃣ 同时修饰两者 → 既不能改地址,也不能改内容
const int* const ptr = &a;
✅ 特点:
-
既不能改内容 (
*ptr = ...) -
也不能改地址 (
ptr = &b)
这种写法最安全,常用于 函数参数保护:
void print(const int* const data);
五、深入理解 const 修饰组合顺序(口诀)
const 在 * 左边 → 修饰内容,指针指向(地址)可变;
const 在 * 右边 → 修饰指针,内容(数值)可变。
| 声明方式 | 含义 |
|---|---|
const int *p | 指向 const 内容,指针可变 |
int const *p | 同上(位置不同,效果一致) |
int *const p | 指针常量,指针不可变 |
const int *const p | 双重常量,地址与内容都不可变 |
六、引用与指针底层关系(编译器实现)
引用底层实现本质上是一个指针常量。
示例:
int i = 5;
int &r = i;
r = 8;
对应汇编(VS Debug模式):
mov dword ptr [i],5
lea eax,[i]
mov dword ptr [r],eax
mov eax,dword ptr [r]
mov dword ptr [eax],8
说明:
-
r实际保存了变量i的地址; -
编译器用“指针常量”实现引用。
即:
int &r = i;≈int *const r = &i;
七、const 与引用的结合
引用本身不能重新绑定,但可以使用 const 修饰来防止通过引用修改内容。
void print(const int& x) {
// x 只读,不能修改
}
✅ 用途:
-
防止函数内部意外修改参数;
-
可接受右值(如字面常量
print(5))。
八、函数参数传递三种方式对比
| 方式 | 定义 | 特点 |
|---|---|---|
| 值传递 | void f(int a) | 拷贝一份,互不影响 |
| 指针传递 | void f(int *a) | 需取地址传入,可为 NULL |
| 引用传递 | void f(int &a) | 不拷贝,安全简洁,不可空 |
九、底层可视化总结(图解思维)
┌────────────┐
a ───▶│ 10 │
└────────────┘
▲
│
│(1) int* p = &a; // p 存地址
│(2) int& r = a; // r 是别名
-
指针是“看得见地址”的引用。
-
引用是“语法糖化的指针常量”。
十、实战理解与代码验证
1)示例
#include <iostream>
using namespace std;
int main() {
int a = 10, b = 20;
// ① 内容常量(const int*)——内容不可改,指向可改
const int* p1 = &a;
// *p1 = 30; // ❌ 错误:不能改内容
p1 = &b; // ✅ 合法:可以改指向
cout << "p1 指向 b, *p1 = " << *p1 << endl;
// ② 指针常量(int* const)——内容可改,指向不可改
int* const p2 = &a;
*p2 = 40; // ✅ 改变 a 的值
// p2 = &b; // ❌ 错误:指针本身是常量,不能改向
cout << "p2 固定指向 a, *p2 = " << *p2 << endl;
// ③ 双重常量(const int* const)——内容和指向都不可改
const int* const p3 = &a;
// *p3 = 50; // ❌ 错误:内容不可改
// p3 = &b; // ❌ 错误:指针不可改
cout << "p3 固定指向 a, *p3 = " << *p3 << endl;
// ④ 引用(int&)——别名,不可改绑
int& r = a;
r = 100; // ✅ 修改 a 的值(a = 100)
// &r = b; // ❌ 不合法:引用无法改绑
cout << "r 是 a 的引用, r = " << r << endl;
cout << "\n最终结果:" << endl;
cout << "a = " << a << ", b = " << b << endl;
return 0;
}
2)运行输出:
p1 指向 b, *p1 = 20
p2 固定指向 a, *p2 = 40
p3 固定指向 a, *p3 = 40
r 是 a 的引用, r = 100
最终结果:
a = 100, b = 20
3)行为总结对比表
| 名称 | 定义 | 内容是否可改 | 指向是否可改 | 备注 |
|---|---|---|---|---|
p1 | const int* p1 | ❌ 不可改 | ✅ 可改 | 指向常量的指针(保护内容) |
p2 | int* const p2 | ✅ 可改 | ❌ 不可改 | 常量指针(固定地址) |
p3 | const int* const p3 | ❌ 不可改 | ❌ 不可改 | 双重常量(最严格) |
r | int& r | ✅ 可改内容 | ❌ 不可改绑 | 引用是别名(语法糖) |
4)底层理解
| 概念 | 底层类似 |
|---|---|
const int* p1 | int const *p1 → 指针指向的内存内容是常量 |
int* const p2 | int * const p2 → 指针本身是常量 |
const int* const p3 | 内容和地址都不可改 |
int& r | 底层相当于 int* const r = &a;,自动解引用 |
5)总结口诀
📗 const 在 * 左边 → 内容不变;
📘 const 在 * 右边 → 指针不变;
📙 双 const → 内容和地址都不变;
📕 引用是别名 → 无法改绑。
十一、引用 vs 指针 vs const 指针 总表对比
| 特性 | 引用 | 普通指针 | const int* | int* const | const int* const |
|---|---|---|---|---|---|
| 是否必须初始化 | ✅ | ❌ | ❌ | ✅ | ✅ |
| 是否可改指向 | ❌ | ✅ | ✅ | ❌ | ❌ |
| 是否可改内容 | ✅ | ✅ | ❌ | ✅ | ❌ |
| 是否可为NULL | ❌ | ✅ | ✅ | ✅ | ✅ |
| 底层实现 | 指针常量 | 变量指针 | 指向常量 | 常量指针 | 双常量指针 |
| 典型应用 | 参数传递 | 动态内存 | 保护内容 | 固定地址 | 完全只读 |
十二、面试与八股高频问答
| 面试问题 | 答案简要 |
|---|---|
| const int* 与 int const* 是否相同? | ✅ 完全相同 |
| int* const 与 const int* 区别? | 前者锁定地址,后者锁定内容 |
| 引用可以为空吗? | ❌ 不能 |
| 可以有引用数组吗? | ❌ 不行(语义歧义) |
| 引用可以改绑吗? | 语言规范不允许(底层可黑魔法修改) |
| const 引用的作用? | 防止修改,被绑定常量/右值 |
| sizeof 引用返回多少? | 被引用类型的大小 |
| const 修饰符生效顺序? | *左修内容,*右修地址 |
十三、结语
💡“引用是语法层的糖,指针是语义层的利器。”
理解引用与指针的区别,是掌握 C++ 内存管理、函数传参、类成员语义的关键。
而 const 的正确使用,则是安全与可读性的保证。
如果你能完全理解:
-
const int* -
int* const -
const int* const -
int&与底层的int* const关系
那你已经掌握了 C++ 指针引用体系的核心逻辑。
441

被折叠的 条评论
为什么被折叠?



