指针和引用是 C++ 中两个强大的特性,它们都提供了间接访问数据的能力,但在语法和语义上有重要区别。
一、指针详解
1. 基本概念
指针是一个变量,其值是另一个变量的内存地址。
cpp
int x = 10;
int* p = &x; // p 存储 x 的地址
2. 指针操作
-
声明指针 :
type* pointerName
-
取地址 :
&variable
-
解引用 :
*pointer
-
指针运算 :
++
,--
,+
,-
cpp
int arr[5] = {1, 2, 3, 4, 5};
int* p = arr; // 指向数组首元素
cout << *(p + 2); // 输出3,等同于 arr[2]
3. 多级指针
指针可以指向另一个指针:
cpp
int x = 10;
int* p = &x;
int** pp = &p; // 指向指针的指针
4. 指针与数组
数组名在大多数情况下会退化为指针:
cpp
int arr[3] = {1, 2, 3};
int* p = arr; // 合法
cout << p[1]; // 输出2
5. 指针的const限定
有三种const指针形式:
cpp
int x = 10, y = 20;
const int* p1 = &x; // 指向常量的指针(值不能改)
p1 = &y; // 合法
// *p1 = 30; // 非法
int* const p2 = &x; // 常量指针(指向不能改)
*p2 = 30; // 合法
// p2 = &y; // 非法
const int* const p3 = &x; // 指向常量的常量指针
二、引用详解
1. 基本概念
引用是变量的别名,必须在初始化时绑定到一个变量。
cpp
int x = 10;
int& r = x; // r 是 x 的引用
r = 20; // 等同于 x = 20
2. 引用特性
-
必须初始化:不能先声明后赋值
-
不可重新绑定:一旦绑定就不能改变
-
无独立内存:编译器通常将引用实现为常量指针
3. 引用作为函数参数
最常见的用途是函数参数传递:
cpp
void swap(int& a, int& b) {
int temp = a;
a = b;
b = temp;
}
int main() {
int x = 5, y = 10;
swap(x, y); // 直接传递变量
}
为什么不能直接用 int a
?
1) 值传递的问题(直接 int a
)
cpp
void swap(int a, int b) { // 值传递
int temp = a;
a = b;
b = temp;
}
问题在于:
-
当调用
swap(x, y)
时,函数会创建a
和b
的副本 -
你只是在交换函数内部的副本,原变量
x
和y
的值不会改变 -
函数结束后,副本被销毁,原值保持不变
2) 用引用时:
cpp
void swap(int& a, int& b) { // 引用传递
int temp = a;
a = b;
b = temp;
}
优势:
-
a
和b
是原始变量x
和y
的别名(不是副本) -
对
a
和b
的操作直接作用于x
和y
-
函数结束后,
x
和y
的值确实被交换了
3) 内存角度解释
值传递 (int a ) |
引用传递 (int& a ) |
|
---|---|---|
内存使用 | 创建新变量,复制值 | 不创建新变量,直接使用原变量 |
对原变量的影响 | 不影响原变量 | 直接影响原变量 |
函数调用后结果 | x 和 y 不变 |
x 和 y 值交换 |
4. const引用
可以绑定到临时对象或不同类型:
cpp
const int& r1 = 10; // 合法
double d = 3.14;
const int& r2 = d; // 合法,创建临时int对象
5. 引用与指针的区别
操作 | 指针 | 引用 |
---|---|---|
声明 | int* p = &x; |
int& r = x; |
空值 | 可以 p = nullptr |
必须绑定有效对象 |
重新绑定 | 可以改变指向 | 一旦绑定不能改变 |
访问 | 需要 *p 解引用 |
直接使用 r |
地址操作 | 可以 &p 获取指针地址 |
&r 得到原变量地址 |
数组 | 可以指针运算访问数组 | 不能用于数组访问 |
多级间接 | 支持多级指针 | 不支持引用的引用 |
三、高级主题
1. 指针与引用的转换
cpp
int x = 10;
int* p = &x;
int& r = *p; // 通过指针创建引用
int& r2 = x;
int* p2 = &r2; // 通过引用获取指针
2. 函数返回引用
可以返回引用实现链式调用:
cpp
class MyArray {
int data[10]; // 私有成员,存储10个整数的数组
public:
// 重载下标运算符[]
int& operator[](size_t index) {
return data[index]; // 返回内部数组元素的引用
}
};
MyArray arr;
arr[3] = 42; // 返回引用可直接赋值
关键概念解析
1) 运算符重载 (operator[]
)
-
允许我们自定义类对象使用
[]
运算符时的行为 -
当写
arr[3]
时,实际上调用的是arr.operator[](3)
2) 返回引用 (int&
)
-
返回类型是
int&
(整数的引用)而不是int
(整数值) -
这使得我们可以修改返回的元素
3) 为什么需要返回引用
对比两种返回方式:
返回类型 | 示例 | 效果 |
---|---|---|
int |
int operator[] |
返回副本,无法修改原数组元素 |
int& |
int& operator[] |
返回实际元素的引用,可以修改原值 |
工作流程分析
cpp
MyArray arr; // 创建MyArray对象
arr[3] = 42; // 使用重载的下标运算符
-
arr[3]
被解析为arr.operator[](3)
-
operator[]
方法返回data[3]
的引用 -
= 42
操作通过这个引用直接修改data[3]
的值
3. 右值引用 (C++11)
cpp
int&& rref = 10; // 右值引用
4. 指针与引用的性能
在底层实现上,引用通常通过指针实现,但编译器可以优化:
-
引用通常没有运行时开销
-
指针可能因为间接寻址有轻微性能损失
四、使用建议
-
优先使用引用:
-
函数参数传递
-
操作符重载
-
不希望参数为null的情况
-
-
必须使用指针:
-
需要处理动态内存
-
需要重新指向不同对象
-
需要表示可选参数(可能为null)
-
-
避免:
-
返回局部变量的引用或指针
-
指针和引用混用导致代码混乱
-
过度使用多级指针
-
五、综合示例
cpp
void process(int* ptr, int& ref) {
*ptr *= 2; // 通过指针修改
ref += 5; // 通过引用修改
}
int main() {
int a = 10, b = 20;
process(&a, b);
cout << "a: " << a << endl; // 输出20
cout << "b: " << b << endl; // 输出25
int& r = a;
int* p = &b;
r++; // a变为21
(*p)++; // b变为26
cout << "a: " << a << endl; // 输出21
cout << "b: " << b << endl; // 输出26
return 0;
}
理解指针和引用的关键在于:
-
指针是显式的内存地址操作
-
引用是隐式的别名机制
-
两者都可以修改原值,但抽象层次不同
-
现代C++中引用使用更广泛,但指针在底层操作中不可替代