今天在此开启一个新的系列:<<深入理解C++>>.

也开始我的学习之路,主要面向初学者和复习相关的知识.

由于C语言和C++有相当部分的知识重叠,故在此主要更新他们不同的和C++独特内容部分的知识.

持续更新,欢迎大家点赞关注.

前言

在 C++ 中,引用(Reference) 是一个非常核心且实用的特性,它为我们提供了一种更安全、更简洁的方式来操作变量。很多初学者会把它和指针搞混,其实两者有着本质的区别。今天,我们就从基础概念讲起,一步步深入理解引用的方方面面。

1.什么是引用?

1.1 &核心定义

形象来说,引用就是变量的 “别名”,就像一个人可以有大名和小名,不管叫哪个,指的都是同一个人。

int a = 10;
int &b = a;  // 定义引用,b 是 a 的别名

核心注意:

(1). ba 指向同一块内存地址.(即a和b本身就是一个东西)

(2). 修改 b 等价于修改 a.

(3). 引用必须在定义时初始化,不能 “空引用”(必须初始化已有变量).

1.2 从内存角度理解

我们可以通过打印出a和b的地址来验证:'

#include <iostream>
using namespace std;

int main() {
    int a = 10;
    int &b = a;

    cout << "a 的地址:" << &a << endl;
    cout << "b 的地址:" << &b << endl;
    cout << "a 的值:" << a << endl;
    cout << "b 的值:" << b << endl;

    b = 20;  // 修改 b
    cout << "修改后 a 的值:" << a << endl;

    return 0;
}

运行结果:

从结果中可以看出,变量a和引用b的地址完全相同(即它们本身其实是一种东西),修改其中一个,那么另一个当然也会跟着变化.

1.3 个人理解:

可能还有很多人对于"变量a和引用b本身是一个东西"有些不太理解.

但如果屏幕前的你学过C语言中的#define 宏定义,那么就应该很容易理解&的效果了.

它们非常相像:纯粹是别名,没有实体,不占独立空间

宏定义:

#define b a

你写 b编译器直接把它替换成 a.

引用&:

int a = 10;
int &b = a;

你写 b编译器也直接把它当成 a.

最终总结:

内存里只有一块空间,名字叫 a,而b 不是新变量,没有自己的盒子

引用就是变量的别名,和宏定义一样,只是换个名字,不是新东西。a 和 b 就是同一个东西,不是两个东西指向同一个地方!

2. 引用&的核心规则(重要)

2.1 引用必须初始化

引用在定义时必须绑定一个已存在的变量,不能定义 “空引用”:

int &b;       // ❌ 错误!未初始化
int a = 10;
int &b = a;   // ✅ 正确!绑定变量 a

2.2 一旦绑定,终身不变

引用绑定一个变量后,就不能再绑定其他变量了:

int a = 10, c = 20;
int &b = a;
b = c;  // ❌ 不是改绑!而是把 c 的值赋给 a,此时 a 变成 20

2.3. 不能绑定常量(除非是 const 引用)

前面看到的都是引用初始化是绑定一个变量,原来是一般不能直接绑定常量

int &ref = 10;        // ❌ 错误!不能绑定字面量常量
const int &ref = 10;  // ✅ 正确!const 引用可以绑定常量

const 引用会为临时常量创建一个临时变量,然后绑定到这个临时变量上。

3. 引用的常用应用场景

3.1 函数传参(最常用)

在将引用传参前我们先复习下之前在C语言中学过的的传值调用和传址调用.

3.1.1传值调用

函数会创建变量的副本,修改副本不会影响原变量.

通俗来说:

实参调用函数时,括号里真实传进去的值 / 变量形参函数定义时,括号里写的接收参数,只是个 “占位名字”.

也就是: 形参是实参的一份拷贝,在某函数中修改了形参并不会影响实参的值.

void func(int x) {
    x = 100;  // 修改的是副本
}

int main() {
    int a = 10;
    func(a);
    cout << a;  // 输出 10,原变量未被修改
    return 0;
}

3.1.2 传址调用(指针)

使用指针变量,向函数传入实参变量的地址,这样在函数中通过解引用改变变量的值,也就是直接改变了这个地址所存储变量的值.

指针和引用在一定程度上有点混淆,我们会在最后做个比较.

void func(int* x) //传入实参的地址
{
    *x = 100;  //直接修改地址内的值
}
int main(void)
{
    int a = 10;
    func(&a);
    cout << a << endl; //输出的是100
    return 0;
}

3.1.3 引用传递

函数直接操作原变量,无需拷贝,效率更高.

void func(int &x) {
    x = 100;  // 直接修改原变量
}

int main() {
    int a = 10;
    func(a);
    cout << a;  // 输出 100,原变量被修改
    return 0;
}

相信不用我强调大家都会自己记起了,变量和引用本质上就是一个东西,故传入a的引用就等于传入了a本身而不是实参a的拷贝(效果上与指针相同).

3.2 传递大对象时避免拷贝

对于大型对象(如结构体、类对象),值传递会拷贝整个对象,因为占用内存较大,所以效率极低,引用传递(直接传入大型对象本身,不需要拷贝)就可以避免这个问题:

struct Student {
    char name[100];
    int age;
    float score;
};

// 值传递:拷贝整个 Student 对象,效率低
void printStudent(Student s) {
    cout << s.name << endl;
}

// 引用传递:直接操作原对象,无需拷贝
void printStudent(const Student &s) {
    cout << s.name << endl;
}

注: 大家都知道const修饰的变量不可以被修改,这里使用 const 引用,既避免了拷贝,又保证了数据不会在函数中被意外修改。

3.3 函数返回引用

函数可以返回引用,但要注意不能返回局部变量的引用:

// 正确:返回静态变量的引用
int& getStaticVar() {
    static int x = 10;
    return x;
}

// 错误:返回局部变量的引用,局部变量在函数结束后会被销毁
int& getLocalVar() {
    int x = 10;
    return x;  // ❌ 危险!返回了无效内存的引用
}

int main() {
    int &ref = getStaticVar();
    cout << ref << endl;  // 输出 10
    ref = 20;
    cout << getStaticVar() << endl;  // 输出 20
    return 0;
}

根据局部变量的特性,在该变量离开它的作用域后就会自动销毁,也就是函数中的局部变量不能离开函数体.

那返回它的引用就毫无意义了,因为函数结束后这个局部变量的空间就已经被销毁了.

3.4 引用作为循环变量(遍历容器)

在遍历容器时,使用引用可以避免拷贝元素,同时可以修改元素:

#include <vector>
using namespace std;

int main() {
    vector<int> vec = {1, 2, 3, 4, 5};

    // 普通遍历:拷贝每个元素
    for (int x : vec) {
        x *= 2;  // 修改的是副本,不会影响 vec
    }

    // 引用遍历:直接操作原元素
    for (int &x : vec) {
        x *= 2;  // 修改 vec 中的元素
    }

    // const 引用遍历:只读,避免拷贝
    for (const int &x : vec) {
        cout << x << " ";
    }

    return 0;
}

vector相关知识,我们以后再慢慢介绍.这里大家先了解下.

3.5 数组的引用

可以定义数组的引用,语法如下:

int arr[5] = {1, 2, 3, 4, 5};
int (&ref)[5] = arr;  // ref 是数组 arr 的引用
ref[0] = 10;          // 修改 arr[0] 的值

4. 常引用(const&)详解

4.1 定义与作用

常引用就是用 const 修饰的引用,它禁止通过引用修改原变量

int a = 10;
const int &b = a;
b = 20;  // ❌ 错误!不能通过 const 引用修改变量
a = 20;  // ✅ 正确!可以直接修改原变量

4.2 常引用的特殊特性

(1). 可以绑定常量、临时变量:

const int &ref = 10;          // ✅ 绑定字面量常量
const int &ref = a + 5;       // ✅ 绑定表达式的临时结果

(2). 可以绑定不同类型的变量(会进行类型转换):

double d = 3.14;
const int &ref = d;  // ✅ 先把 d 转为 int,再绑定到临时变量

5. 引用 VS 指针 :联系与区别

很多初学者容易混淆引用和指针,这里用表格清晰对比两者的区别:

特性 引用(&) 指针(*)
定义方式 int &ref = a; int *p = &a;
是否必须初始化 必须 可以不初始化(可以为 nullptr)
是否可以改绑 不可以 可以指向其他变量
是否可以为 “空” 不可以 可以为 nullptr
操作方式 直接使用变量名 需要解引用 *p
安全性 更安全(无野引用) 容易出现野指针、空指针问题
内存占用 不占用额外内存(别名) 占用 4/8 字节(存储地址)

1. 指针可以实现引用的功能,但引用更安全

// 指针实现类似引用的效果
int a = 10;
int *p = &a;
*p = 20;  // 修改 a 的值

// 引用实现
int &ref = a;
ref = 20;  // 修改 a 的值

指针需要手动管理地址和解引用,而引用自动处理这些,代码更简洁安全。

2. 指针的灵活性是引用无法替代的

指针可以指向数组的不同元素:

int arr[] = {1, 2, 3, 4, 5};
int *p = arr;
p++;  // 指向 arr[1]

指针可以动态分配内存:

int *p = new int(10);

6. 总结

引用是 C++ 中非常实用的特性,它的核心优势在于:

  1. 简洁安全:无需手动管理地址和解引用,避免了指针的很多问题
  2. 效率高效:传递大对象时无需拷贝,性能接近指针
  3. 可读性好:代码更直观,不容易出错

引用最常用的场景是函数传参和遍历容器,尤其是 const 引用,既能保证效率,又能保证数据安全。

在此不胜感谢能够看到这里的同学,如果发现文章有错误还请不吝赐教.

后续内容会不断更新,感兴趣的同学可以关注点赞收藏慢慢观看.

万分感谢!

-------------------------------------------------------------------------------------------

  

                                                                                                           2026.5.11

                                                                                                              ---14:02

更多推荐