文章

指针和引用

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;
}

引用的优点

  1. 操作效果等同于指针
    • 将引用传递给函数时,形参成为实参的别名。
    • 在函数内对形参的操作就是直接操作原来的实参对象。
  2. 避免拷贝,提高效率
    • 引用传参不会生成实参副本,直接操作原对象。
    • 普通按值传参会为形参分配存储单元,必要时还会调用拷贝构造函数。
    • 对于较大的对象或类,使用引用可以显著提高效率并节省内存。
  3. 使用更简洁、易读
    • 指针参数也能实现修改外部对象的效果,但需要使用 * 解引用,并且在调用点必须传地址。
    • 引用无需解引用,调用和操作都更直观,代码可读性更高,同时减少出错的可能。
场景用法优点
大对象参数传递const Type &param避免拷贝
修改外部值void f(Type &param)能修改实参
返回引用Type& f()直接操作原始对象
循环遍历for (Type &x : container)原地修改容器元素
别名简化Type &alias = variable语义清晰

使用场景对比

返回函数内局部变量的内存

  • 引用:不适合返回局部变量。引用是已存在变量的别名,函数结束后局部变量会被销毁,返回引用会产生悬空引用(dangling reference),可能导致程序崩溃或不可预期的结果。
  • 指针:可以通过动态分配(new)生成内存,返回指针时可以将所有权交给调用者,避免悬空指针问题。但调用者需要负责释放,否则会造成内存泄漏。

对栈空间敏感(如递归)

  • 使用引用传参不需要创建临时副本,开销较小,节省栈空间。

避免拷贝大对象或类,提高效率

  • 使用引用可以直接操作实参,避免复制开销。
  • 指针也可以修改外部对象,但需要注意内存分配和指针有效性,否则可能导致崩溃或未定义行为。

按引用传递的选择依据

  • 只读取数据,不修改
    • 小型数据类型(如内置类型或小结构体):按值传递即可。
    • 数组:使用指向 const 的指针。
    • 大型结构体或类对象:使用 const 引用,提高效率。
  • 需要修改数据
    • 内置类型:使用指针。
    • 数组:使用指针。
    • 结构体:使用指针或引用。
    • 类对象:使用引用。
本文由作者按照 CC BY 4.0 进行授权