前言
引用,作为 C++ 区别于 C 语言的一种特性,其重要性不言而喻,今天小黄就和大家共同学习一下引用的具体用途,优势以及用法,期待大家相互指点!
目录
一、 概念
引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空 间,它和它引用的变量共用同一块内存空间,是不额外分配实际的内存空间的。
使用方法:类型 + & + 引用名 = 引用实体
例如:
#include<iostream> using namespace std; void Test() { int a = 10; int& ra = a; printf("%p\n", &a); printf("%p\n", &ra); } int main() { Test(); }
二、 特性
2.1 引用初始化
对于任何一个引用,都需要在定义的时候初始化。就像起名,不能凭空起名,名字必须要依附于某个实际物体,引用也需要依附于某个实际元素。
例如:
#include<iostream> using namespace std; void Test() { int a = 10; int& ra = a; // int& pa; 不可行,编译器会报错 printf("%p\n", &a); printf("%p\n", &ra); } int main() { Test(); }
存在野指针,但是不存在野引用,上图中注释行的写法就不可行,会产生如下报错:
2.2 一个变量可以有多个引用
对于同一元素,可以有多个引用,但是由于引用实际上不会分配内存,因此不存在引用的引用。
例如:
#include<iostream> using namespace std; void Test() { int a = 10; int& ra = a; int& pa = ra; printf("%p\n", &ra); printf("%p\n", &pa); a = 1; printf("%d\n", a); ra = 2; printf("%d\n", a); pa = 3; printf("%d\n", a); } int main() { Test(); }
乍一看,很多人会认为上图中 pa 不就是 ra 的引用吗?看似很像引用的引用,但是实际上并不是,ra 已经是 a 的引用了,此时 ra 是 a 的内存地址,pa 依附于 ra 的内存,实际上等同于依附在 a 的内存上,也就是 pa 实际上也是 a 的引用。上图运行结果如下:
2.3 引用不可更改性
从上面的例子中我们看到引用直接赋值的话实际上是在改变引用实体的值。实际上,引用在初始化的时候就已经确定好了,在程序之后的运行中无法改变该引用所引用的实体对象。
例如:
#include<iostream> using namespace std; void Test() { int a = 10, b = 20; int& ra = a; printf("%p\n", &ra); ra = b; printf("%p\n", &ra); // &ra = b; } int main() { Test(); }
如上图,当我们直接对 ra 赋值时,ra 的地址没有发生改变,而当我们试图使用注释行的时候,系统给出了如下报错,即引用依附于实体的内存,且无法被更换引用实体。
三、 使用场景
3.1 函数传参
既然引用可以直接修改引用实体,那么作为函数传参就相当于指针的传址调用,而且不需要一直解引用。
例如:
#include<iostream> using namespace std; void swap(int& a, int& b) { int t = a; a = b; b = t; } int main() { int a = 1, b = 2; swap(a, b); cout << a << endl << b << endl; }
对于上面的 swap 函数就起到了交换值的作用,而且是对主函数中的 a,b 交换。
而当引用作为函数参数的时候,主要有两个优点:
1. 引用相当于别名,在函数中可以对引用实体直接操作赋值等;
2. 引用没有分配实际的内存空间,可以节省大量的内存空间,且加上 const 修饰后可保证不会误改引用参数。
3.2 函数返回值
我们在 C 语言的学习中已经知道,函数中的局部变量在函数结束后就会销毁,那么怎么能进行引用的返回呢?其一 static 关键字修饰的变量,这类变量是存储在静态区内,因此不会在函数结束后销毁,返回该变量的引用是可以对其在函数之外进行操作的。
例如:
#include<iostream> using namespace std; int& func() { static int i = 10; cout << i << endl; return i; } int main() { int& ri = func(); ri = 0; func(); return 0; }
如图可见,我们可以在 main 函数中修改函数中的 static 修饰的变量。同时,我们需要知道,函数的返回值,本质上是对函数内的变量进行一个临时拷贝,然后赋值给函数返回值的接收变量。
因此引用作为函数返回值,主要有以下两个优点:
1.减少对返回值的拷贝,提高程序效率;
2.函数内变量若未被系统回收,则可以在函数结束后修改返回值。
四、 引用和指针的区别
1. 引用概念上是定义一个变量的别名(不占据实际的内存空间),指针存储一个变量地址(占据了实际的内存空间);
2. 引用在定义时必须初始化,指针则不是必须;
3. 引用在初始化时引用一个实体后,之后就无法修改其引用的对象,而指针可以在任何时候指向任何 一个同类型实体;
4. 没有NULL引用,但有NULL指针;
5. 在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数( 32 位平台下 4 字节, 64 位平台下 8 字节);
6. 引用自加即引用的实体增加 1 ,指针自加即指针向后偏移一个类型的大小;
7. 有多级指针,但是没有多级引用;
8. 访问实体方式不同,指针需要显式解引用,引用则由编译器自己处理;
9. 引用比指针使用起来相对更安全,不存在野指针等不安全访问。
五、 const 修饰的引用
在 C 语言中我们已经了解到 const 修饰的变量是只读的,不能写入,那么同样的,当 const 修饰引用之后,引用就无法对引用实体进行写入操作。
5.1 const 修改变量权限
例如:
#include<iostream> using namespace std; int main() { int a = 0; const int& ra = a; // ra = 2; const int b = 1; // int& rb = b; return 0; }
上图中两个注释行都是不可行的操作,对于第一行注释,即上面说到的 const 修饰的引用无法修改引用实体,而对于第二行注释,则是一条语法规则:
对于指针和引用而言,权限可以缩小但是不能增大。
所谓权限,可以理解为 “读” 和 “写” 的能力(是否可以进行)。
例如:
#include<iostream> using namespace std; void func_1(int a) { cout << a << endl; } void func_2(int& a) { cout << a << endl; } int main() { int a = 0; const int& ra = a; const int b = 1; func_1(a); func_1(ra); func_1(b); func_2(a); // func_2(ra); // func_2(b); return 0; }
对于 func_2 函数传参是 int&,表示在函数中是可以对参数本体进行修改的,但是两个注释行中都是 const 修饰的变量,它们的权限只有读没有写,而函数参数 int& 权限是可以读也可以写,权限变大了,因此系统会进行报错。那么对于 func_1 而言,为什么可以用 ra 和 b 进行传参呢?这就是上面提到的一个前提 “对于指针和引用而言” ,而 func_1 函数参数是 int 类型,因此不会有以上规则。可以换个角度理解,func_! 函数的传值调用,是对实参做了一份临时变量,这个临时变量的值的改变,当然不会影响到实参的值,因此实参是不是 const 修饰都不无所谓了。
5.2 const 修饰的引用常量
既然 const 表示一个不可被更改的值,那么似乎有点近似于常量的属性,可读但不可被更改。事实上, const 确实可以修饰常量。
例如:
#include<iostream> using namespace std; int func() { int n = 10; return n; } int main() { // int& a = 1; const int& ra = 2; float b = 3; // int& rb = b; int rrb = b; // int& rn = func(); const int& rrn = func(); return 0; }
上图中第一行注释很好理解, 1 是一个常量,因此无法作为 int& 类型的引用实体,而利用 const 修饰之后便可以了。而第二行注释,则涉及到强制类型转换的一个隐式过程,即类型转换时,实际上是创建了一个新的临时变量,而对于原变量并没有做修改,而临时变量具有一定常属性,即不可被修改,因此同样无法作为 int& 类型的引用实体。同样,在之前我们已经提到,非引用的函数返回值也是会进行一份临时拷贝,然后赋值给接收的实体,所以第三行注释也不可行。
欢迎大家一起来学习呀~