前言
我们都知道,在c语言中,指针是一种功能十分强大的数据类型,它允许我们直接访问并且操作内存。然而,它在使用时稍有不慎,就会出错。对此,c++引入了一个新的概念:引用。引用可以实现部分类似于指针的功能,并且它比指针更加安全、简洁。
一、引用的概念和定义
引用,作为c++中的一种特殊别名机制,当我们定义引用时,并不是创建了一个新变量,而是
给原有的变量起了一个别名。它的定义方式如下:
(数据类型)& 引用名 = 引用对象;
代码举例:
using namespace std; int main() { int a = 0; int& b = a;//定义引用,给a取了一个别名 a++; cout << b << endl; b++; cout << a << endl; return 0; }
运行结果:
可以看到,无论是对a自增,还是对b自增,它们表示的值都会发生改变。我们再来打印一下它们的地址:
cout << &a << endl; cout << &b << endl;
结果显示它们的地址是相同的。这表明引用只是给变量起了一个别名,表示的还是同一块内存空间。
我们画图表示一下引用:
二、引用的特性
引用有以下三点语法特性:
1.引用在定义的时候必须要表明它所表示的对象。
2.由于引用是用作给变量取别名,所以一个变量可以有多个引用。当然,引用也可以有引用。
3.引用一旦指定了对象,就不能再指定其他对象。
三、引用的实用性
了解的引用的概念和特性之后,我们不禁会发出疑问:既然要对变量进行操作,直接修改不就好了嘛,何必还要取一个别名呢?
1.引用传参
举一个例子:
using namespace std; void Swap(int& x, int& y) { int tmp = x; x = y; y = tmp; } int main() { int a = 3; int b = 5; Swap(a, b);//交换a和b的值 cout << "a = " << a << endl; cout << "b = " << b << endl; return 0; }
运行结果:
可以看到,使用引用传参,通过函数交换了变量a和b的值。由于引用是给变量起别名,所以此时的x和y就表示a和b本身。因此,我们在函数内部就实现了这两个元素的交换。相比传址调用,它的写法更加简单,并且在语法层面,引用是不开辟新内存的,减少了拷贝,节省了内存空间。
2.引用做返回值
2.1 引用做返回值的作用
首先来看一段代码:
using namespace std; int fun() { static int a = 10; return a; } int main() { fun()++;//报错 cout << fun() << endl; return 0; }
很显然,以上代码是不可行的。函数的返回值是一个临时变量,而临时变量具有常性,我们无法对这个值进行修改。但是引用做返回值可以做到这一点:
using namespace std; int& fun() { static int a = 10; return a; } int main() { fun()++; cout << fun() << endl; return 0; }
我们将返回值改成int的引用类型,则此时函数返回静态变量a的别名,所以我们让它自增,就相当于改变了a的值。
有了引用做返回值,我们在调用函数的时候就可以通过这个返回值直接访问和修改原始对象,并且避免了临时变量的拷贝,增加程序运行的效率。
2.2 引用坍缩问题、悬挂引用问题
刚才引用做返回值的方法虽然好用,但是难免会出现一些问题。举个例子:
using namespace std; int& func1() { static int a = 0; return a; } int main() { int x = func(); return 0; }
虽然这里的func1函数返回了a的引用,但是我们却在主函数中以一个整形变量来接收,这里就会发生一种隐式的类型转换--引用坍缩,编译器会将这个返回值转换为它所引用的对象的一个临时拷贝,这将导致我们无法访问并修改a的值,达不到预期效果。所以一定要以相同类型的引用来接收引用返回值。
再举一个例子:
using namespace std; int& func2() { int a = 0; return a; } int main() { int& x = func(); return 0; }
在func2函数中,我们创建了一个局部变量a并且返回它的引用,但是由于a是一个局部变量,当函数栈帧销毁时,变量a的空间已经被释放了,此时的返回值就变成一个悬挂引用(野引用),也就是说这个引用的主体已经不存在了,程序的运行结果就是未定义的。所以我们在使用引用做返回值时,一定要注意返回后的引用主体是否还存在。
四、const修饰引用
我们在定义引用时,可以在变量名之前加上const修饰(称之为常引用)。当一个引用被const修饰时,该引用就无法修改其所表示的对象。例如:
using namespace std; int main() { int a = 0; const int& b = a; a = 10; b = 20;//报错 return 0; }
注意:对于一个被const修饰的变量,定义它的引用时也需要用const修饰。例如:
using namespace std; int main() { const int a = 0; int& b = a;//报错 const int& c = a;//正确 return 0; }
对于以下情况,定义引用时也必须要用const修饰:
using namespace std; int main() { int a = 5; const int& b = a * 3;//a*3是一个临时对象,具有常性,它的引用要用const修饰 float c = 5.5f; const int& d = c;//当引用和对象的类型不同时,由于隐式类型转换的结果是一个临时对象,定义引用也需要用const修饰 return 0; }
五、引用和指针的联系
引用和指针是相辅相成的,它们能够实现对方的部分功能,但是又不可完全替代。它们的一些联系和区别如下:
1.从语法层面上,引用是对一个对象取别名,不会开辟空间;而指针用于存储一个对象的地址,会开辟空间。
2.定义引用时,必须要指定表示的对象;而指针在定义时可以不指向对象。
3.引用不能改变表示的对象,而指针可以改变。
4.由于引用是对象的别名,所以可以直接通过引用访问对象;而指针则需要解引用才能访问对象。
5.相比指针,引用在使用上更加安全,不容易出现悬挂引用的问题。
6.在某些情况下,指针和引用在语义上可以相互替代,例如函数传参,都可以形成数据共享,并且避免内存的过度消耗。
总结
今天,我们学习了引用。引用的功能十分强大,同时也弥补了指针的一些不足。深入理解引用对我们之后学习并实现类的相关功能有很大帮助。如果你觉得博主讲的还不错,就请留下一个小小的赞在走哦,感谢大家的支持❤❤❤