什么是引用变量?
引用实际上是已定义变量的别名,使一个变量拥有多个名字
c++给&符号赋予了另一个意义,将其用来声明引用
int a=9;
int&b=a;
此时b成为a的一个别名,a就是b,b就是a.它们均指向同一片内存
int a=99;
int&b=a;//b成为a的别名
cout<<&a<<endl;
cout<<&b<<endl;
//a,b的地址相同
cout<<a<<endl;
cout<<b<<endl;
//a,b的值相同
a,b的各种属性相同,说明了a,b是同一个变量,该变量有a,b两个名字
引用变量的注意点
必须在声明引用变量时将其初始化
比如
int a=99;
int&b;
b=a;//这是不行的,在声明引用时必须将其初始化
int&c=a;//这是对的
引用变量一旦和某个变量关联起来,就将一直效忠于它
比如
int a=99;
int&b=a;
int c=88;
b=c;//这代表将c的值88赋给名字为b的那个变量(它也叫a)
此时b=c代表把c的值88赋给名为b的变量(这个变量也叫a),所以此时a,b,c的值均为88,其中a,b代表同一个变量
这点和int* const b=&a;类似,*b可以改,b不能改
引用只能绑定在一个左值上面,不能与字面值或者某个表达式的计算结果绑定在一起
int&a=10;//这是不行的
int&b=2+3;//这是不行的
注意点4
除了两种例外情况,所有的引用的类型都要和与之绑定的对象严格匹配
例外情况1——对const的引用可能引用一个并非const的对象
第一种例外情况是在初始化常量引用时允许用任意表达式作为初始值,只要该表达式的结果能转换成引用的类型即可。
尤其,允许为一个常量引用绑定非常量的对象,字面值,甚至是个一般表达式:
int a=2;
const int&b=i;
const int&c=3;
const int&d=a*2;
上面这些都是允许的
要想理解这种例外情况的原因,最简单的办法是弄清楚当一个常量引用被绑定到另外一种类型上时到底发生了什么:
double dval = 3.14;
const int &ri = dval;
此处ri 引用了一个int型的数。对ri的操作应该是整数运算,但dval却是一个双精度浮点数而非整数。因此为了确保让ri绑定一个整数,编译器把上述代码变成了如下形式:
const int temp = dval; // 由双精度浮点数生成一个临时的整型常量
const int &ri = temp; // 让ri绑定这个临时量
在这种情况下,ri绑定了一个临时量(temporary)对象。所谓临时量对象就是当编译器需要一个空间来暂存表达式的求值结果时临时创建的一个未命名的对象。C++程序员们常常把临时量对象简称为临时量。
当ri不是常量时,如果执行了类似于上面的初始化过程将带来什么样的后果。如果ri不是常量,就允许对ri赋值,这样就会改变ri所引用对象的值。注意,此时绑定的对象是一个临时量而非 dval。程序员既然让 ri 引用 dval,就肯定想通过ri 改变dval的值,否则干什么要给ri赋值呢?如此看来,既然大家基本上不会想着把引用绑定到临时量上,C++语言也就把这种行为归为非法。
也就是说下面这种情况是非法的
const int a = 9;
int& b = a;
例外情况2——在公有继承中,我们可以将基类引用绑定一个派生类对象
class A{};
class B:public A{};
B b;
A&a=b;//这是可以的
注意是公有继承
正常情况
除了两种例外情况,所有的引用的类型都要和与之绑定的对象严格匹配
int a=0;
char b=4;
int&c=b;//这是不可以的
char&d=a;//这是不可以的
将引用用作函数参数
将引用作为函数参数,使得函数中的变量名成为调用程序中的变量的别名,这也叫按引用传递。
按值传递和按引用传递的区别
按值传递
void A(int a)//调用时会创建一个名为a的临时变量,并将传入的变量的值赋给a
{
a=a+1;
cout<<a<<endl;
}
int main()
{
int w=99;
A(w);
}
执行完后w的值不变
按引用传递
void B(int&a)
//调用时给传入进来的变量起个别名a,a就能直接修改传入的原始数据了
{
a=a+1;
cout<<a<<endl;
}
int main()
{
int w=99;
B(w);
}
执行完后w的值变为100,因为a是w的别名,a+1就是w+1
总结
按值传递需要通过创建临时变量来拷贝传入的变量,修改临时变量不会影响传入的参数
按引用传递通过给传入的变量起别名来使用原始数据,不必创建临时变量,节省了栈帧开销
临时变量,引用参数和const
按引用传递应该注意的点
#include<iostream>
using namespace std;
void A(int& a)
{
a=1;
cout << a << endl;
}
void B(int b)
{
cout << b << endl;
}
int main()
{
int a=9;
double b=9;
A(b);//这是不被允许的
A(a+1);//这也是不行的
B(b+2);//按值传递存在类型自动转换
B(b);//同上
}
我们试图将double类型传进A函数里,按值传参会通过自动类型转换将其转换为int类型的,按引用传递不支持参数自动类型转换,
我们又试图将a+1传入函数A(),但是按引用传参的要求更高一些,按照引用定义,A函数调用时给传进来的变量起个别名为a,但是a+1不是变量,所以c++禁止这种行为
如何解决上面这种情况呢?
我们可以将函数定义改为void A(const int& a),如果实参和引用参数不匹配,c++将生成临时变量,当且仅当参数为const引用时,c++才允许这么做
引用参数是const时,什么情况会生成临时变量?
1.实参的类型正确,但不是左值
左值:常规变量(变量,数组元素,结构成员,引用和解除引用的指针),const 变量
2.实参的类型不正确,但可以转换为正确的类型
过程:如果函数调用的参数不是左值或与之相应的const引用参数的类型不匹配,则c++将创建正确的匿名变量,将函数调用的参数的值传给该匿名变量,并让参数来引用该变量。
值得注意的是使用const引用参数,代表传入的参数不能被修改,同时可以接受const对象和非const对象
引用作为函数返回值
为什么要返回引用?
我们可以先看看下面这段代码
int A(int a)
{
return a-1;
}
int& B(int&b)
{
b=b-1;
return b;
}
int main()
{
int w=3;
int e=A(w);//调用时先把w-1的值先存储在一个临时位置,再赋给e
int r=B(w);//调用时直接把w-1的值复制到r上
}
我们返回引用意味着程序在内存中不产生返回值的副本,提高程序运行速度。
返回引用要注意的问题
1.最重要的是应避免返回函数终止时不再存在的内存单元引用(注意是避免,而不是禁止)
比如返回局部变量,指向局部变量的指针等
int& A()
{
int a=9;
return a;//a是局部变量,这是不能返回它
}
int& B()
{
int b=1;
int*c=&b;
return *c;//这也是不可以的,因为b是局部变量
}
我们可以试着调用一下它
int main()
{
int f=A();
cout<<f<<endl;
}
惊奇的发现f的值居然是 9,这是因为a虽然被销毁了,但是它的值还在,这个具体的原因这里不做探讨,感兴趣的可以去思考思考
2.其次就是要避免返回用new来分配的存储空间
int& B()
{
int*a=new int;
*a=9;
return *a;//避免返回这个,因为容易忘记用delete
}
这个就是非常容易忘记用delete来释放内存了。
返回引用的使用方法
首先看一下怎么用return返回啊
返回什么啊?
我们一般是返回传入的参数,确保不返回局部变量
int& A(int&a)//返回类型为int&
{
a=a+3;
return a;//返回的类型是int类型或者int&类型即可,注意只能变量
}
返回引用和不返回引用的区别
我们可能在实际操作中会遇到这个疑惑啊
#include<iostream>
using namespace std;
int& A(int& a)
{
a+=2;
return a;
}
void B(int& b)
{
b+=2;
}
int main()
{
int w=2;
A(w);
cout<<w<<endl;
B(w);
cout<<w<<endl;
}
运行程序,我们发现结果是4和6,有人就纳闷了,明明A函数是有返回类型的,为什么可以直接用A(W)这样调用?我们还发现A函数和B函数都能把w的值加2,那我们为什么又要给A函数加上返回类型呢?这不是多此一举吗?A函数和B函数有什么区别?
A函数可以这样调用是因为它的返回类型是引用类型int&。当函数返回一个引用时,实际上是返回了原始变量的别名,而不是一个新的副本。所以在调用A函数时,返回的引用被赋值给了w,即w和a都指向了同一个变量。这样的调用方式可以直接修改原始变量的值。
A函数和B函数的区别是A函数可以被嵌套使用,而B函数不可以
#include<iostream>
using namespace std;
int& A(int& a)
{
a+=2;
return a;
}
void B(int& b)
{
b+=2;
}
int main()
{
int w=2;
B(A(w));//这是可以的,因为A的返回类型刚好与B的参数类型一致
cout<<w<<endl;
A(B(w));//这是不可以的,因为A的参数类型是int&,而B的没有返回类型
cout<<w<<endl;
}
此外A函数还可以将返回的对象的值赋给另一个变量,B函数不可以
int main()
{
int w=2;
int e=A(w);//把w的值复制给e
}
实际上int e=A(w)和下面这个等价
A(w);
int e=w;
将const用于引用返回类型
我们在很多书上会看到类似下面这种代码
int& A(int& a)
{
a+=2;
return a;
}
void B(int& b)
{
b+=2;
}
int main()
{
int t=9;
int w=3;
A(w)=t;
//w先加2,然后t的值被赋给w
B(w)=t;
//这是不行的
}
为什么A函数的返回值可以放在赋值语句=的左边?而常规的不行?
这是因为A函数的返回值不是临时对象,是可以取地址的,而B的是临时对象,出了函数就销毁了
那我们如果不想给A()函数赋值的话,可以用const修饰它的返回值
const int& A(int& a)
{
a+=2;
return a;
}
int main()
{
int t=9;
int w=3;
A(w)=t;
//这是不行的
}
这样子就禁止了被赋值的情况,而其他功能又不丧失的情况
对象,继承和引用
基类引用可以接受基类引用和派生类引用