1. 引用的概念
引用不是定义一个新的变量,而是给已有的变量取一个别名,编译器不会给引用变量开辟新的内存空间,而是和它引用的变量共用同一块内存空间
使用规则:类型& 别名 = 引用实体
举个栗子:
图一
我们发现,对于变量a,我们用ra当作他的别名,然后我们又定义了一个引用变量x,他的引用实体是ra,这说明了同一变量,可以有多个引用。然后我们注意右边的调试窗口,可以发现a,ra,x,y的地址都是相同的,所以他们共用同一块内存空间。
那么,我们要怎么使用呢?举个栗子,我们之前学习C语言的时候写过一个Swap函数
void swap(int* x, int* y) { int tmp = *x; *x = *y; *y = tmp; }
这里需要传地址给Swap函数,才能实现交换变量值的功能,但是如果使用引用,就可以省略解引用的操作。
void Swap(int& x, int& y) { int tmp = x; x = y; y = tmp; }
注意:引用类型必须和引用实体是同种类型的,否则会报错,在VS2022环境下报如下错误:error C2440: “初始化”: 无法从“int”转换为“char &”
2. 引用的特性
- 引用在定义时必须初始化
- 一个变量可以有多个引用
- 引用一旦引用一个实体,再不能引用其他实体
对于特性1:
图二
对于特性二:在图一中已经很好的体现出来。
对于特性三:假设我们想改变引用的实体,我们应该会使用类似与“ra = b”这种语句,但是如下图,我们看到第31行代码并没有报错,那么是不是表示引用变量引用的实体是可以改变的呢?注意右边的监视窗口,ra的地址和b的地址并不相同,这说明ra不是b的引用,ra还是a的引用,所以第31行的语句是改变a的值,而不是改变ra的引用。
图三
3. 常引用
常引用就是在定义引用变量时加上const修饰。
我们看下面一段代码
void TestConstRef() { const int a = 10; int& ra = a; const int& ra = a; int& b = 10; const int& b = 10; double d = 12.34; int& rd = d; const int& rd = d; }
这段代码有什么问题呢?
图四
运行之后发现,第40行因为我们定义的变量a是用const修饰的,而ra是没有用const修饰的,是可以更改的,这样的话,引用变量的权限还变大了,这是不合理的。第41行中,ra的类型和a的类型相同,都是const int类型,所以不会报错。对于第42行,变量b引用的是一个常量,是不可修改的,但是b本身的权限是可修改的,也造成了权限的扩大,所以报错。后面的第45行也是同理,因此我们得出结论:指针和引用赋值中,权限可以缩小,可以平移,但是不能扩大。
那么,常引用有什么使用场景呢?我们看下面一个例子
void Func(int& x)//假设传一个参数 { //做一些不需要修改传入的值的操作 //... } int main() { int a = 10; int& ra = a; const int& rra = ra; Func(a); Func(ra); Func(rra);//const修饰的参数,传参的时候会出现权限放大的问题 return 0; }
对于这种情况,我们传一个const修饰的参数,就会出现问题,因此,在这个地方我们定义函数时应该使用"const int& x"来代替"int& x"。
const引用还有一个点需要注意,我们看下面一段代码
int main() { const int& b = 10; double d = 12.34; int i = d;// 这句代码有问题吗? int& ri = d;// 这句代码有问题吗? return 0; }
答案是第一句没有问题,第二句有问题。为什么呢?因为在第二句中,ri的类型时int&,但是d的类型是double,所以在指定引用实体的时候,指定的并不是d的空间,即ri并不是d的别名,而是在转换之前开辟了一块临时变量存储int类型的数据,这个数据是d中存放的值转换而来的,而语法规定:临时变量具有常性,可以理解为用const修饰过,所以这句代码就造成了权限扩大,所以这句代码我们应该改为"const int& ri = d"
注意:强转,截断,提升都会产生临时变量。
4. 使用场景
做参数
1. 输出型参数
图五
2. 普通参数
图六