C++引用概念及用法全解

 更新时间:2026年04月06日 08:08:18   投稿:mrr  
本文详细介绍了C++中的引用概念,总结了引用和指针的关键差异,并强调引用更安全但灵活性较低,指针则更灵活但需注意空指针等问题,感兴趣的朋友跟随小编一起看看吧

一、什么是引用?

引用是C++中的一种语法特性,可以理解为变量的别名。引用本身不占用独立的内存空间,它和被引用的变量共享同一块内存。

引用的基本用法

int main() {
    int a = 10;
    int& ra = a;  // ra是a的别名
    
    a += 10;
    cout << ra << endl;  // 输出20
    
    ra += 10;
    cout << a << endl;   // 输出30
    
    return 0;
}

关键点:对引用的操作就是对原变量的操作,两者完全等价。

二、引用的三大特点

1. 定义引用时必须初始化

int& ra;      // ❌ 错误:引用必须初始化
int& ra = a;  // ✅ 正确

原因:引用是变量的别名,别名必须依附于某个已存在的变量,不能独立存在。

2. 不存在空引用

int& ra = NULL;  // ❌ 错误:不能给引用赋空值
int* p = NULL;   // ✅ 指针可以为空

原因:引用必须绑定到一个有效的变量,不能指向空地址。指针是独立的变量,可以存储空地址,但引用不是变量,它只是别名。

3. 引用一旦绑定,不能改变指向

int a = 10, b = 20;
int& ra = a;
ra = b;  // 这不是让ra指向b,而是把b的值赋给a(即a = 20)

原因:引用在初始化时绑定了目标变量,之后所有对引用的操作都直接作用于该变量,无法重新绑定到其他变量。

对比指针

int a = 10, b = 20;
int* p = &a;   // 指针可以改变指向
p = &b;        // 现在p指向b
*p = 100;      // 修改b的值为100

三、引用作为函数参数

引用的本质:引用是变量的别名,不是副本。对引用的操作就是对原变量的操作。

基于这个特性,引用最常用的场景是作为函数参数,主要解决两个问题:

1. 在函数内部修改外部变量

C语言中,函数参数默认是传值:形参是实参的副本,函数内部修改形参,外面的实参不受影响。

// C语言:传值,交换失败
void Swap(int a, int b) {
    int tmp = a;
    a = b;
    b = tmp;
}

int main() {
    int x = 10, y = 20;
    Swap(x, y);
    // x和y还是10和20,没变
}

想要修改实参,必须传指针

// C语言:传指针,交换成功
void Swap(int* a, int* b) {
    int tmp = *a;
    *a = *b;
    *b = tmp;
}

C++的引用可以做到同样的事,语法更简洁:

// C++:传引用,交换成功
void Swap(int& a, int& b) {
    int tmp = a;
    a = b;
    b = tmp;
}

int main() {
    int x = 10, y = 20;
    Swap(x, y);     // 调用时和传值一样,但实参被改了
    cout << x << " " << y << endl;  // 输出20 10
}

优点:调用时不用取地址,函数内部不用解引用,代码更清晰。

2. 避免拷贝,提高效率

当参数是大型对象(如结构体、类)时,传值会拷贝整个对象,开销很大。传引用只传递地址(4/8字节),效率更高。

// 传值:拷贝整个Student对象(几十字节)
void PrintStudent(Student s) { ... }

// 传引用:只传递地址(4/8字节),不拷贝
void PrintStudent(const Student& s) { ... }

注意:对于 intchar 这类小型数据,传值反而更快,因为直接复制到寄存器,不需要间接访问。所以小型数据用传值,大型对象用const引用

3. 三种参数传递方式对比

方式本质能否修改实参效率(小型数据)效率(大型对象)
传值副本❌ 不能
传指针地址✅ 能
传引用别名✅ 能

四、普通引用 vs const引用

1. 普通引用(可读可写)

int a = 10;
int& ra = a;   // 普通引用
ra = 100;      // ✅ 可以修改
cout << ra;    // ✅ 可以读取

2. const引用(只读)

int a = 10;
const int& cra = a;  // 常引用,只读
cout << cra;         // ✅ 可以读取
cra = 100;           // ❌ 错误:不能通过常引用修改

3. 权限规则:看原变量的权限

引用的权限不能超过原变量。

  • 原变量是普通变量(可读可写)→ 引用可以是普通引用,也可以是常引用
  • 原变量是常量(只读)→ 引用只能是常引用
int a = 10;        // 普通变量:可读可写
const int b = 20;  // 常量:只读

int& ra = a;        // ✅ 普通引用引用普通变量
const int& cra = a; // ✅ 常引用引用普通变量

int& rb = b;        // ❌ 错误:普通引用不能引用常量(权限超了)
const int& crb = b; // ✅ 常引用引用常量

一句话:变量是什么权限,引用就不能超过这个权限。

五、const引用的万能特性

const引用可以引用任何东西:普通变量、常变量、字面常量,甚至临时对象。

1. 引用普通变量

int a = 10;
const int& cra = a;  // ✅

2. 引用常变量

const int b = 20;
const int& crb = b;  // ✅

3. 引用字面常量

const int& crx = 30;  // ✅ 可以!

背后原理

当 const 引用绑定到字面常量时,编译器会生成一个临时变量来保存这个常量,然后让引用指向这个临时变量。

// 编译器实际做的事:
int tmp = 30;
const int& crx = tmp;

注意:引用本身不占内存,但被引用的对象(临时变量)需要占内存。这个临时变量由编译器自动创建和管理,它的生命周期和引用一致。

4. 引用表达式结果

int a = 10, b = 20;
const int& cr = a + b;  // ✅ 表达式结果是临时变量,const引用可以绑定

六、引用的常见误区

误区1:引用可以重新绑定

int a = 10, b = 20;
int& ra = a;   // ra是a的别名

ra = b;        // ❌ 不是让ra指向b,而是把b的值赋给a(a变成20)

误区2:引用占用空间

引用本身不占用独立内存,它只是原变量的别名。sizeof 引用的结果是原变量的大小。

int a = 10;
int& ra = a;
cout << sizeof(ra);  // 输出4(int的大小),不是指针的大小

原因sizeof 作用于引用时,返回的是被引用对象的大小。

误区3:引用可以指向引用

int a = 10;
int& ra = a;
int&& rra = ra;  // ❌ 错误:不存在"引用的引用"

原因:引用是别名,不是对象。不能给别名再起别名吗?语法上不允许。

七、不能返回局部变量的引用或地址

先看一个错误示例

Student& func() {
    Student sa{ "S001", "Alice", "F", 20  };
    return sa;   // 返回局部变量的引用
}

int main() {
    Student& p = func();
    printf("%s", p.s_name);  // 可能崩溃,可能乱码,可能碰巧正确
}

为什么错了?

困惑1:返回地址本身有什么错?

返回地址本身没错。return &sa 返回的是一个数字(内存地址),这个操作是合法的。错的是函数结束后你还去用这个地址访问内存

困惑2:为什么函数结束后就不能用了?

函数调用时,系统会在栈上给局部变量分配空间。函数结束后,这块空间被系统回收,标记为“可重用”。但回收不是清零,这块内存还在,只是不再属于你了。

下次调用其他函数时,系统可能会把这块空间分配给别的变量,里面的内容随时可能被覆盖。所以你去访问它,结果是不可预测的。

困惑3:返回值和返回引用有什么区别?

// 返回引用:返回地址,危险
Student& func1() {
    Student sa{ ... };
    return sa;   // 返回地址,sa销毁后地址失效 ❌
}

// 返回值:返回副本,安全
Student func2() {
    Student sa{ ... };
    return sa;   // 返回副本,sa销毁不影响副本 ✅
}

区别

  • 返回引用:把原件的地址告诉别人,原件没了,地址就成了空地址
  • 返回值:复印一份交给别人,原件没了,复印件还在

困惑4:返回局部变量的地址,和返回局部变量本身,有什么区别?

看返回类型:

  • 返回类型是 Student& 或 Student*:返回的是地址(危险)
  • 返回类型是 Student:返回的是副本(安全)
Student& func1() { return sa; }  // 返回类型是引用 → 地址
Student* func2() { return &sa; } // 返回类型是指针 → 地址
Student func3() { return sa; }   // 返回类型是对象 → 副本

类比理解

你在酒店开房:

  • sa 就是你住的房间
  • 返回引用或地址:你把房间号告诉别人
  • 退房(函数结束):房间被回收,不再属于你
  • 别人拿着房间号回去找:房间可能住了别人,可能在打扫,可能已经被改成仓库
  • 去用这个房间号 → 结果不可预测

返回值:你退房时把房间里的东西复印一份带走,原来的房间怎么变都跟你无关。

正确的做法

1. 返回值(拷贝)

Student func() {
    Student sa{ ... };
    return sa;  // 返回副本
}

2. 返回静态局部变量

Student& func() {
    static Student sa{ ... };  // 静态变量,生命周期贯穿整个程序
    return sa;
}Student& func() {
    static Student sa{ ... };  // 静态变量,生命周期贯穿整个程序
    return sa;
}

3. 在堆上分配

Student* func() {
    Student* p = new Student{ ... };  // 堆上分配,手动管理
    return p;
}

一句话总结

返回局部变量的地址本身没错,错的是函数结束后你还去用这个地址。因为这块内存已经被系统回收,不再属于你了。

八、能不能定义引用数组?

结论:不能定义“元素为引用的数组”,但可以定义“数组的引用”。

1. 不能定义元素为引用的数组

int& rbr[5];  // ❌ 错误:不能定义引用数组

原因:数组是一段连续的内存空间,每个元素都需要占用内存。引用本身不占内存,它只是别名,无法作为数组元素存在。

2. 可以定义数组的引用

int ar[5] = {1,2,3,4,5};
int(&rar)[5] = ar;  // ✅ 正确:rar是数组ar的引用

含义rar 是整个数组 ar 的别名,不是元素为引用的数组。

3. 对比

写法含义是否正确
int& rbr[5]元素为引用的数组❌ 错误
int(&rar)[5]数组的引用✅ 正确

记忆技巧:括号位置决定一切。& 靠近数组名,表示引用整个数组;& 靠近类型,表示元素是引用。

九、数组名退化问题

1. 数组名退化的规则

数组名在三种情况下不会退化,保持为整个数组:

  • sizeof(ar)
  • &ar(取数组地址)
  • 引用数组 int(&rar)[n] = ar

其他情况数组名退化为数组首元素的地址。

int ar[5] = {1,2,3,4,5};
cout << sizeof(ar);      // 20(5*4,未退化)

int(*p)[5] = &ar;        // 取数组地址,未退化
int(&rar)[5] = ar;       // 引用数组,未退化

int* q = ar;             // 退化:ar变成int*

2. 数组名作为函数参数:退化问题

C++中,数组名作为参数传递给函数时,会退化为指针,丢失数组大小信息。

void funa(int br[5]) {
    cout << sizeof(br);   // 输出8(指针大小),不是20
    // 函数内部不知道数组有多大,只能通过额外参数传入长度
}

如果希望函数内部能知道数组的大小,可以用数组的引用作为参数:

void funb(int (&rar)[5]) {
    cout << sizeof(rar);  // 输出20,保留了数组大小
    // 函数内部可以直接用sizeof获取数组长度
}

关键点:用数组的引用作为参数,可以保留数组的大小信息,避免退化。

十、引用与指针的区别

1. 语法规则上的区别

对比项引用指针
本质变量的别名存储变量的地址
内存分配不分配独立内存分配4/8字节内存
使用方式直接使用需要解引用(*)
能否改变指向不能
必须初始化
是否可为空不能可以(NULL/nullptr)
sizeof结果原变量的大小指针本身的大小(4/8)
多级无(无引用的引用)有(二级、n级指针)
++操作改变原变量的值改变指向的地址
// 演示++操作的区别
int ar[5] = {10,20,30,40,50};
int& ra = ar[0];
int* p = &ar[0];

++ra;   // ar[0]变成11
++p;    // p指向ar[1]

2. 汇编层面的本质

从汇编角度看,指针和引用没有区别,都是地址。

int a = 10;
int& ra = a;
int* p = &a;
// 底层实现:ra和p都是存储a的地址

引用在底层也是用指针实现的,只是编译器做了语法糖,让使用更方便、更安全。

3. 一句话总结

维度引用指针
语法层面别名,更安全地址,更灵活
汇编层面都是地址都是地址

使用建议:能用引用就用引用,需要改变指向或可为空时再用指针。

总结

知识点核心要点
引用本质变量的别名,不占独立内存
三大特点必须初始化、不能为空、不能改指向
const引用只读,可引用普通变量/常变量/字面常量
权限规则引用权限不能超过原变量
const引用万能可绑定字面常量和临时对象,编译器自动生成临时变量
引用作为参数可修改外部变量、避免大对象拷贝
返回局部变量不能返回局部变量的引用或地址,函数结束后内存被回收
数组的引用可定义数组引用 int(&rar)[5],不能定义引用数组 int& rbr[5]
数组名退化sizeof/&/数组引用 三种情况不退化,函数参数会退化
引用 vs 指针引用更安全(不能为空、不能改指向),指针更灵活

到此这篇关于C++引用全解的文章就介绍到这了,更多相关C++引用内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • C语言实现大整数加减运算详解

    C语言实现大整数加减运算详解

    大数运算,顾名思义,就是很大的数值的数进行一系列的运算。本文通过实例演示如何进行C语言中的大整数加减运算,有需要的可以参考借鉴。
    2016-08-08
  • 基于C++字符串替换函数的使用详解

    基于C++字符串替换函数的使用详解

    本篇文章是对C++字符串替换函数的使用进行了详细的分析介绍,需要的朋友参考下
    2013-05-05
  • C语言二分法求解方程根的两种方法

    C语言二分法求解方程根的两种方法

    这篇文章主要为大家详细介绍了C语言二分法求解方程根的两种方法,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2020-06-06
  • C语言预处理预编译命令及宏定义详解

    C语言预处理预编译命令及宏定义详解

    这篇文章主要为大家介绍了C语言预处理预编译命令及宏定义的详解,其中包含运行环境命名约定条件及#under等基础详解,有需要的朋友可以借鉴参考下
    2021-10-10
  • 深入了解C语言结构化的程序设计

    深入了解C语言结构化的程序设计

    这篇文章主要介绍了C语言编程中程序的一些基本的编写优化技巧,文中涉及到了基础的C程序内存方面的知识,非常推荐!需要的朋友可以参考下
    2021-07-07
  • c++冒泡排序示例分享

    c++冒泡排序示例分享

    冒泡排序是一种计算机科学领域的较简单的排序算法,这篇文章主要介绍了c++冒泡排序示例,需要的朋友可以参考下
    2014-03-03
  • Qt自定义Widget实现互斥效果详解

    Qt自定义Widget实现互斥效果详解

    在使用Qt时,可能会遇到这种问题:多个控件互斥,类似于QRadiButton控件,但又不是单纯的QRadioButton控件,互斥的可能是一个窗口,也可能是几个按钮,等等多种情况。本文将介绍利用Qt自定义Widget实现的互斥效果,需要的可以参考一下
    2022-01-01
  • 使用C++实现FTP上传和下载

    使用C++实现FTP上传和下载

    当在Windows上使用C++进行FTP上传和下载时,您可以使用libcurl库来简化操作,本文将为大家详细介绍具体步骤,感兴趣的小伙伴可以跟随小编一起学习一下
    2023-12-12
  • 总结UNIX/LINUX下C++程序计时的方法

    总结UNIX/LINUX下C++程序计时的方法

    本文总结了下UNIX/LINUX下C++程序计时的一些函数和方法,对日常使用C++程序的朋友很有帮助,有需要的小伙伴们可以参考学习,下面一起来看看吧。
    2016-08-08
  • C语言哈希表概念超详细讲解

    C语言哈希表概念超详细讲解

    哈希是一种很高效的存储数据的结构,它是利用的一种映射关系,它也是STL中unordered_map和unordered_set 的底层结构。本文,主要讲解哈希的原理,哈希的实现,里面关键点在于如何解决哈希冲突
    2023-02-02

最新评论