一、引用(&):给变量起别名
基础语法
int a = 10; int& c = a; // c是a的别名,c和a代表同一块内存✅ 核心规则
- 定义引用时必须当场初始化,不存在空引用。引用从诞生那一刻就绑定一个变量,不能悬空。
- 一个变量可以拥有多个别名,也就是多个引用绑定同一个变量。
- 不能给别名再起别名,不存在「引用的引用」。
左值引用 & 右值引用
判断口诀:能取地址的是左值,不能取地址的是右值
int& c = a; // 左值引用,只能绑定左值 const int& ref3 = 10; // 特殊规则:const左值引用,可以绑定右值(临时值) int&& r = 10; // 右值引用,专门绑定右值- 普通左值引用
int&:只能绑定有内存、能取地址的变量; const修饰之后:只有可读权限,不能修改;不加const可读可写。
引用做函数传参
- 值传递:传入变量副本,会开辟独立新内存,函数内部修改副本,不会影响外部原变量。
- 引用传递:传入原变量的别名,共用同一块内存,函数内修改会直接改动外部原始变量。
推荐原则:能用引用就不用指针。 如果函数不需要修改变量,简单运算如
return x+y,直接值传递即可,开销更小。
二、指针 vs 引用(高频考点对比)
- 本质指针:存储变量 / 对象内存地址的独立变量; 引用:变量的别名,本身不分配独立内存。
- 内存指针:程序会给指针单独分配内存; 引用:没有独立内存空间,和原变量是同一个内存。
- 使用语法指针:访问目标需要解引用
*p; 引用:直接使用,写法和普通变量一样。 - 绑定可变性指针:可以修改,后续可以改成存储另一个变量的地址; 引用:初始化绑定变量后,终身不能更换绑定对象。
- 空值指针:可以是空指针(
nullptr); 引用:不允许空引用。 - 安全检查指针作为形参:必须判空,防止空指针崩溃; 引用:一定绑定有效变量,无需判空。
sizeof结果sizeof(指针):得到指针本身大小(64 位系统固定 8 字节);sizeof(引用):得到原变量本身占用内存大小。- 多级支持 指针:支持多级,存在指针的指针
int** p; 引用:只有一级,不存在引用的引用。 - 操作含义 操作指针:操作的是变量地址; 操作引用:直接操作原变量本身。
共同点:指针、引用作为函数参数,都可以在函数内部修改外部原始变量。
三、函数底层:局部变量返回注意事项
❌ 禁止:函数内部定义的局部变量,直接返回它的地址
int* badFunc(){ int temp = 100; return &temp; // 错误! }局部变量存放在栈内存。函数执行结束,栈帧销毁,这块内存会被系统回收。虽然短时间内数据还在(缓存延迟回收),但这块内存随时会被覆盖,访问会出现未定义行为。
四、inline 内联函数:空间换时间
原理(结合寄存器,老师上课重点)
普通函数调用完整流程:
- 保存当前寄存器的值,压入栈(函数执行完之后要恢复寄存器)
- 修改程序计数器 PC,跳转到被调用函数入口
- 创建栈帧,分配局部变量栈内存、传递参数
- 执行函数代码
- 销毁栈帧,恢复寄存器,PC 跳转回调用位置
👉 这些跳转、栈操作、寄存器保存恢复,都是函数调用开销。
inline 内联函数:编译阶段,直接把函数的代码原地复制、插入到调用位置。没有函数跳转、不需要保存恢复寄存器、没有栈帧创建销毁,省去调用开销。
inline int add(int a, int b) { return a + b; }inline 的限制(编译器不一定听你的!)
inline只是给编译器的建议,不是强制命令。 遇到下面情况,编译器会拒绝内联,当成普通函数处理:
- 函数体代码很长
- 函数包含循环、分支等复杂逻辑
- 递归函数
代价:空间换时间。多次调用内联函数,代码会重复拷贝,最终可执行文件体积变大。如果函数过大,代码膨胀反而降低性能。
五、内联函数 VS 宏定义 #define
表格
| 对比项 | 宏 #define | inline 内联函数 |
|---|---|---|
| 处理阶段 | 预处理阶段,纯文本替换 | 编译阶段,是真正函数 |
| 类型检查 | 无语法、类型检查,容易优先级 bug | 完整类型、语法检查,类型安全 |
| 能力限制 | 仅简单文本替换,不能访问类成员 | 支持完整函数逻辑,支持类成员函数 |
| 可控性 | 强制替换,容易产生意料之外错误 | 编译器自主决定是否内联,更安全 |