函数与指针:指针与引用
核心要点速览
- 指针:存储内存地址的变量,支持空值(nullptr)、动态指向、多级访问,存在野指针风险
- 引用:变量的别名,必须初始化、不可改指向、无空引用,语法简洁安全
- 访问效率:两者基本一致
- 适用场景:需空值 / 动态指向 / 多级访问用指针;需简洁语法 / 确保有效 / 避免误操作引用
一、指针
指针本质是 “存储内存地址的变量”,支持对内存的间接访问。
1. 类型与特性
(1)野指针
- 定义:未初始化、已释放或越界访问的指针(指向随机 / 无效内存)。
- 风险:访问野指针会导致内存错误(崩溃、数据错乱),是常见 bug 来源。
- 示例(错误):
1 | int* p; // 未初始化,野指针 |
- 规避:指针定义时立即初始化(如
int* p = nullptr;),释放后及时置空(p = nullptr;)。
(2)空指针(nullptr)
- 定义:C++11 标准的空指针常量,替代旧版
NULL(NULL本质是0,易引发歧义)。 - 用途:表示指针 “无指向”,避免野指针,可通过条件判断安全使用。
- 示例:
1 | int* p = nullptr; |
(3)函数指针
- 定义:指向函数的指针,语法:
返回类型 (*指针名)(参数类型列表)。 - 用途:作为函数参数 / 返回值,实现回调函数(如排序算法中的比较器)。
- 示例:
1 | // 目标函数:两数相加 |
(4)指针数组与数组指针
原则:[]优先级高于*,()可提升*的优先级,需通过语法结构判断本质。
| 类型 | 语法格式 | 逻辑(优先级分析) | 示例 |
|---|---|---|---|
| 指针数组 | 类型* 数组名[数组长度]; |
[]优先级高,先构成数组,元素是指针 |
int* arr[3];(3 个 int * 指针的数组) |
| 数组指针 | 类型 (*指针名)[数组长度]; |
()提升*优先级,先是指针,指向 “固定长度的数组” |
int (*p)[3];(指向含 3 个 int 的数组) |
- 示例解析:
1 | int a[3] = {1,2,3}; |
2. 指针的操作
- 解引用:
*p,访问指针指向的变量值。 - 取地址:
&变量,获取变量的内存地址,赋值给指针。 - 算术运算:仅对 “指向数组的指针” 有意义,步长 = 指向类型的大小(如
int* p,p++移动 4 字节)。
二、引用
引用本质是 “变量的别名”,语法上无独立内存(编译器通常用指针实现,但对外隐藏细节),核心优势是简洁安全。
1. 特性
必须初始化:定义时必须绑定已存在的变量(无空引用),否则编译报错。
1
2
3
4int a = 10;
int& r = a; // 正确:绑定变量a
// int& r; // 错误:未初始化
// int& r = nullptr; // 错误:无空引用不可改指向:绑定后无法切换到其他变量,赋值操作是修改 “绑定变量的值”。
1
2int b = 20;
r = b; // 不是更改绑定,而是将a的值改为20(a=20,r仍绑定a)内存特性:
sizeof(r) = sizeof(原变量)(无额外内存开销)。1
cout << sizeof(r); // 4字节(与int类型大小一致)
2. 应用
(1)函数参数(避免拷贝 + 修改实参)
- 替代指针,语法更简洁,无野指针风险,适合大对象传递(无拷贝开销)。
- 示例:
1
2
3void update(int& x) { x *= 2; }
int a = 5;
update(a); // a=10(直接修改实参)
(2)函数返回值(避免拷贝)
可返回 “全局变量、静态变量、类成员变量” 的引用(生命周期长于函数),避免返回对象的拷贝开销。
禁忌:禁止返回局部变量的引用(函数结束后局部变量销毁,引用变为悬垂引用)。
示例(正确):
1
2int g_val = 10;
int& get_global() { return g_val; } // 返回全局变量引用,安全示例(错误):
1
2
3
4int& get_local() {
int x = 20;
return x; // 错误:x是局部变量,函数结束后销毁
}
三、本质区别
- 指针:独立的变量,存储目标变量的内存地址(占用 4/8 字节内存,与系统位数相关)。
- 引用:非独立变量,是目标变量的 “别名”(语法层面无独立内存,编译器通常用指针实现,但对外隐藏指针操作)。
四、特性对比表
| 对比特性 | 指针(Pointer) | 引用(Reference) |
|---|---|---|
| 初始化 | 可延迟初始化(但未初始化是野指针) | 必须定义时初始化,且绑定已存在变量 |
| 指向可修改性 | 可重新指向其他变量(p = &b; 合法) |
绑定后不可更改指向(r = b; 是修改原变量值) |
| 空值支持 | 支持(int* p = nullptr;) |
无空引用(必须绑定有效变量) |
| 解引用操作 | 需显式用 *(*p = 10;) |
无需解引用(r = 10; 直接修改绑定变量) |
| 算术运算 | 支持(步长 = 指向类型大小) | 不支持(非独立对象,无地址可运算) |
| 多级嵌套 | 支持(int** p 指向指针的指针) |
不支持(int&& r 是右值引用,非 “引用的引用”) |
| 语法复杂度 | 较高(需关注*/&/ 空指针 / 野指针) |
较低(简洁直观,无指针操作风险) |
五、补充
1. 作为函数参数的传递机制
(1)指针传参
- 本质:“值传递地址”—— 函数接收的是实参地址的拷贝,修改指针本身(如
p = &b;)不影响实参指针;但修改指针指向的内容(*p = 10;)会影响实参变量。 - 示例:
1
2
3
4
5
6
7void func(int* p) {
*p = 20; // 影响实参变量(修改指向的内容)
p = nullptr; // 不影响实参指针(仅修改拷贝)
}
int a = 10;
int* ptr = &a;
func(ptr); // a=20,ptr仍指向a(未变)
(2)引用传参
- 本质:传递 “变量别名”—— 函数内修改引用(
r = 20;)直接修改实参;且无法修改引用的指向(语法禁止)。
2. 返回值的 “悬垂问题”
- 指针与引用的共同禁忌:禁止返回局部变量(生命周期随函数结束而销毁)。
- 返回局部变量的指针:得到野指针(指向已释放内存),访问即未定义行为。
- 返回局部变量的引用:得到悬垂引用(绑定已销毁变量),访问即未定义行为。
3. const 修饰
(1)const 修饰指针(左定值,右定向)
- 口诀:
const靠近谁,就限制谁 —— 左定值(限制指向的内容不可改),右定向(限制指针本身不可改)。1
2
3const int* p; // 常量指针(左定值):指向的内容不可改(*p不能赋值),指针可改指向
int* const p; // 指针常量(右定向):指针本身不可改指向,指向的内容可改(*p可赋值)
const int* const p; // 指向常量的指针常量:内容和指向均不可改
(2)const 引用(const T&)
- 特性:不可通过引用修改绑定变量,但变量本身可被其他方式修改。
- 用途:可绑定临时对象(字面量、表达式结果),常用于函数参数(接收任意类型参数,避免拷贝)。
- 示例:
1
2
3
4
5
6const int& r1 = 10; // 合法:const引用绑定字面量(临时对象)
const int& r2 = 3 + 4; // 合法:绑定表达式结果
int a = 5;
const int& r3 = a;
// r3 = 10; // 错误:const引用不可修改绑定变量
a = 10; // 合法:变量本身可通过其他方式修改(r3的值同步变为10)
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 肖恩的博客!
评论

