指针和引用
C++ 中指针可指向动态内存或局部变量,引用是变量别名不可空。引用高效、易用,适合修改外部对象或避免大对象拷贝;指针适合动态内存和可空场景。
指针和引用
指针和引用
指针
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <iostream>
using namespace std;
int main() {
// 实际变量的声明
int var = 20;
// 指针变量的声明
int *addr;
// 在指针变量中存储 var 的地址
addr = &var;
cout << "var = " << var << endl;
// 输出在指针变量中存储的地址
cout << "地址addr = " << addr << endl;
// 访问指针中地址的值
cout << "地址addr中存的值 = " << *addr << endl;
}
在变量声明的时候,如果没有确切的地址可以赋值,为指针变量赋一个 nullptr 是一个良好的编程习惯。
引用
引用变量是一个别名,也就是说,它是某个已存在变量的另一个名字。一旦把引用初始化为某个变量,就可以使用该引用名称或变量名称来指向变量。
引用很容易与指针混淆,它们之间有三个主要的不同:
- 不存在空引用。引用必须连接到一块合法的内存。
- 一旦引用被初始化为一个对象,就不能被指向到另一个对象。指针可以在任何时候指向到另一个对象。
- 引用必须在创建时被初始化。指针可以在任何时间被初始化。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <iostream>
using namespace std;
int main() {
// 声明普通的变量
int i;
// 声明引用变量
int& r = i;
i = 5;
cout << "i = " << i << endl;
cout << "r = " << r << endl;
r = 6;
cout << "r = " << r << endl;
}
引用作为参数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <iostream>
using namespace std;
// 函数声明
void swap(int& x, int& y);
int main() {
// 局部变量声明
int a = 100;
int b = 200;
cout << "交换前,a 的值:" << a << endl;
cout << "交换前,b 的值:" << b << endl;
// 调用函数来交换值
swap(a, b);
cout << "交换后,a 的值:" << a << endl;
cout << "交换后,b 的值:" << b << endl;
// 成功交换。如果把引用都换成普通变量,则会交换失败
}
// 函数定义
void swap(int& x, int& y) {
int temp;
temp = x;
x = y;
y = temp;
}
引用作为返回值
当函数返回一个引用时,则返回一个指向返回值的隐式指针。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <iostream>
using namespace std;
double vals[] = {10.1, 12.6, 33.1, 24.1, 50.0};
double& setValues(int i) {
double& ref = vals[i];
// 返回第 i 个元素的引用,ref 是一个引用变量,ref 引用 vals[i]
return ref;
}
// 要调用上面定义函数的主函数
int main() {
cout << "改变前的值" << endl;
for (int i = 0; i < 5; i++) {
cout << "vals[" << i << "] = ";
cout << vals[i] << endl;
}
setValues(1) = 20.23;
setValues(3) = 70.8;
cout << "改变后的值" << endl;
for (int i = 0; i < 5; i++) {
cout << "vals[" << i << "] = ";
cout << vals[i] << endl;
}
return 0;
}
当返回一个引用时,要注意被引用的对象不能超出作用域。所以返回一个对局部变量的引用是不合法的,但是,可以返回一个对静态变量的引用。
1
2
3
4
5
6
int& func() {
int q;
//! return q; // 在编译时发生错误
static int x;
return x; // 安全,x 在函数作用域外依然是有效的
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <iostream>
using namespace std;
// 返回对静态变量的引用
int& getStaticRef() {
static int num = 5; // 静态变量
return num;
}
int main() {
int& ref = getStaticRef(); // 获取对静态变量的引用
cout << "初始值:" << ref << endl;
ref = 10; // 修改静态变量的值
cout << "修改后的值:" << ref << endl;
cout << "再次调用函数后的值:" << getStaticRef() << endl;
return 0;
}
引用的优点
- 操作效果等同于指针
- 将引用传递给函数时,形参成为实参的别名。
- 在函数内对形参的操作就是直接操作原来的实参对象。
- 避免拷贝,提高效率
- 引用传参不会生成实参副本,直接操作原对象。
- 普通按值传参会为形参分配存储单元,必要时还会调用拷贝构造函数。
- 对于较大的对象或类,使用引用可以显著提高效率并节省内存。
- 使用更简洁、易读
- 指针参数也能实现修改外部对象的效果,但需要使用
*
解引用,并且在调用点必须传地址。 - 引用无需解引用,调用和操作都更直观,代码可读性更高,同时减少出错的可能。
- 指针参数也能实现修改外部对象的效果,但需要使用
场景 | 用法 | 优点 |
---|---|---|
大对象参数传递 | const Type ¶m | 避免拷贝 |
修改外部值 | void f(Type ¶m) | 能修改实参 |
返回引用 | Type& f() | 直接操作原始对象 |
循环遍历 | for (Type &x : container) | 原地修改容器元素 |
别名简化 | Type &alias = variable | 语义清晰 |
使用场景对比
返回函数内局部变量的内存
- 引用:不适合返回局部变量。引用是已存在变量的别名,函数结束后局部变量会被销毁,返回引用会产生悬空引用(dangling reference),可能导致程序崩溃或不可预期的结果。
- 指针:可以通过动态分配(
new
)生成内存,返回指针时可以将所有权交给调用者,避免悬空指针问题。但调用者需要负责释放,否则会造成内存泄漏。
对栈空间敏感(如递归)
- 使用引用传参不需要创建临时副本,开销较小,节省栈空间。
避免拷贝大对象或类,提高效率
- 使用引用可以直接操作实参,避免复制开销。
- 指针也可以修改外部对象,但需要注意内存分配和指针有效性,否则可能导致崩溃或未定义行为。
按引用传递的选择依据
- 只读取数据,不修改
- 小型数据类型(如内置类型或小结构体):按值传递即可。
- 数组:使用指向
const
的指针。 - 大型结构体或类对象:使用
const
引用,提高效率。
- 需要修改数据
- 内置类型:使用指针。
- 数组:使用指针。
- 结构体:使用指针或引用。
- 类对象:使用引用。
本文由作者按照 CC BY 4.0 进行授权