赞
踩
引用是对变量起别名。用代码来说明:
int main()
{
int a = 0;
int& b = a;
b = 10;
cout << a << endl;
cout << b << endl;
return 0;
}
这段代码中是对引用的基本使用。
定义变量a = 10;在栈中分配一个4字节空间,空间里的值赋值为10。
int& b = a;相当于给a这个空间取个别名,这个时候b和a就是指同一个空间,相当于对a这个空间起个别名叫做b(这里与引用的实质有关,在下面给出解释)。
所以这个时候对a和b的操作都是对a这个空间的操作。所以代码中b = 10,就是将a这个空间的值修改为10,所以这个时候打印a和b都是10。
1.定义引用的时候必须初始化。
即我们既然是引用取别名我们就得知道是谁的别名,不可能说连个空间都没有,我们就直接凭空冒出来个别名。通俗点说,起外号得知道是给谁起的。
int a;//可以
int& b;//error
2.一旦初始化后就不能让这个别名变为其他的别名
意思是,我们给一个空间起了一个别名,不可能让这个别名变成其他空间的别名。
int a = 10;
int& b = a;
int c = 10;
b = c;
在第四句代码中,由于b是a空间的别名,所以这句是我们给a空间赋值,是一个赋值语句,并不是说将b变成c的别名。其实这些也跟引用的实质有关。
引用的本质在C++内部是一指针变量。
int a = 10;//①
int& b = a;//②
b = 30;//③
int c = 20;//④
b = c;//⑤
其实上面这些代码就相当于下面这些代码:
int a = 10;
int* const b = &a;
*b = 30;
int c = 20;
*b = c;
我们定义一个引用的时候,就是定义一个必须初始化的指针int* const b = &a;,这也是我们第一点说的为什么引用不能修改为其他空间的别名,因为底层用了const来修饰。
当我们给引用赋值的时候,b = 30;,编译器发现b是引用,在底层会用*b = 30来实现这一句。b = c也是一样的。所以说引用在底层就是用指针来实现的,而且用const修饰,不能修改指针的指向,所以不能修改为其他空间的别名。
在这里我们用交换函数来说明这一点。
void myswap1(int a, int b) { int tmp = a; a = b; b = tmp; } void myswap2(int* a, int* b) { int* tmp = a; a = b; b = tmp; } void myswap3(int* a, int* b) { int tmp = *a; *a = *b; *b = tmp; } int myswap4(int& a, int& b) { int tmp = a; a = b; b = tmp; }
对于第一个交换函数肯定是没办法实现我们目的的。因为第一个交换函数形参是值传递,值传递的情况下,形参的改变并不会影响实参的改变。
对于第二个交换函数也没有办法实现我们的目的。a和b分别指向a和b的地址,函数体里面一通交换,连个解引用都没有,所以只是交换了形参的两个指针变量的指向而已。
对于第三个交换函数可以达到我们的交换目的。形参改变导致实参的改变要达到两个条件:传指针,解引用。我们把a和b的地址传进去,在函数体里面对指针解引用交换,就是交换a和b的值。
对于第四个交换函数本质和第三个交换函数一样,我们把引用语句换成对应的指针语句,就发现和第三个交换函数是一样的。但是我们传实参的时候,只需要传(a,b)而不需要对a和b取地址。即调用函数四的话用语句myswap(a,b)即可。
1.不要以引用的形式接收函数返回的局部变量
int& fun()
{
int a = 10;
return a;
}
int main()
{
int& a = fun();
cout << a << endl;
cout << a << endl;
return 0;
}
执行结果:
当被调用的函数返回一个局部变量的值的时候,我们要用一个变量及时接收这个值,因为被调用的函数调用完毕后,这个被调用的函数内部的临时变量的空间都会被释放,空间里的值都会变成随机值。所以我们不要继续有和被调用的函数里的变量的空间有关的操作。(除非不是栈空间里的变量,比如没有被释放的堆空间我们可以继续使用)。
引用我们说过了,本质上是指针,返回局部变量的引用,就是返回这个局部变量空间的地址。
但是为什么第一次打印的是正确的,第二次打印的是随机值呢?这是因为函数结束后,编译器会对函数的局部变量的空间进行一次保护,编译器考虑到函数可能会返回临时变量,我们需要接收使用,但是要求我们定义一个变量,以赋值的形式接收,而不是以获取临时变量的栈空间接收。所以我们不要以引用的形式来接受函数返回的临时变量。
不管函数是以引用的形式返回还是以正常的int形式返回,我们都用int接收,比如上面函数我们可以用语句int a = fun();接收函数的返回值,因为a是我们定义的变量,函数返回值会以赋值的形式将返回值拷贝到a变量中。
2.函数的调用可以用作左值
还是以上面代码为例,只不过我们肯定不能以局部变量的形式返回,因为局部变量的空间在函数结束后得不到保障。我们将局部变量修改为静态变量(静态变量储存在全局区,等程序结束后空间才回收)。
int& fun() { static int a = 10; return a; } int main() { int& a = fun(); cout << fun() << " " << a << endl; fun() = 200; cout << fun() << " " << a << endl; a = 300; cout << fun() << " " << a << endl; return 0; }
运行结果:
注意我们这里函数返回的是静态变量,存储在全局变量区,变量空间的值不会因为fun()函数的结束而失效,所以在这里我们可以用引用接收。我们发现函数返回值可以用作左值。
1.右值和左值的基本概念
我们先来看下面两行代码:
int& a = 10;//error
int&& b = 10;//OK
何为左值何为右值?
我们定义的变量能够被其他值赋值的变量就称为左值;右值和左值相对应的,所以右值就是我们说的只能出现在等号右边的,我们熟悉的数字就是右值。
借助其他地方的解释:左值和右值是表达式的属性。C++中的表达式,要么就是左值,要么就是右值。左值可以位于赋值语句的左侧,而右值却不可以。总结来说,当一个对象被用作右值的时候,用的是对象的值(即将这个对象的值赋值或者拷贝给其他空间);当一个对象被用作左值的时候,用的是对象的身份,就是在内存中的位置(即我们是对这个对象的空间里的东西进行操作)。需要右值的地方可以用左值替代,但不能把右值当成左值使用。
要注意:变量都是左值!右值就是一个单纯的数据!右值只能绑定到将要销毁的对象上!
2.左值引用和常量引用
int fun() { int a = 10; return a; } int main() { int& a1 = fun();//编译通不过 const int& a2 = fun();//可以 int& a3 = a2;//编译通不过 int&& a4 = a2;//编译通不过 return 0; }
函数返回的是临时变量值是a变量空间里的值,是10,注意这是个右值。并不是说fun()函数返回的是a变量,fun()函数返回的是a变量空间的数据值。
左值引用不可以引用右值的根本原因在于,正常的左值引用可以修改空间里的值,而右值是不能也是不允许被修改的。所以我们用正常的引用接收函数返回值编译通不过,但是常量引用是可以通过的,因为常量引用不可能有修改空间的值的操作。
常量引用不能修改值,你更不可能给常量引用的空间起一个别名,而且这个别名可以修改这个常量引用的空间值。试想一下,我自己的空间我都不能修改,肯定也不允许其他有修改意图的东西来接近我的空间,所以int& a3 = a2是肯定不行的。
右值引用本质是int tmp = a2,int& a4 = tmp;这样想的话,按道理说int&& a4 = a2是可以通过的,因为这个语句先创建一个空间,将a2空间的值放进空间里,再给这个空间起个别名。a2这个空间的值这里只是赋值用一下,a4也不是a2这个空间的别名。你确实不允许有任何可能修改你空间的东西靠近或者说是共享,但是连拿过来赋值用一下都不行了吗?这里是因为右值引用有规定,右值引用变量是左值(任何变量都是左值,不管是不是可修改或者不可修改,只要是变量就是左值)作为等式左边,等式右边必须必须是右值,就是说右值引用必须是和右值一起用的,而a2是左值引用类型的变量,是左值,所以语句编译通不过。
我们再来看一下下面代码:
int main()
{
int a = 30;//①
int& p = a;//②可以
const int& pp = a;//③
const int& ppp = pp;//④可以
int b = 30;//⑤
const int& tt = b;//⑥可以
//int& t = tt;//⑦编译通不过
int& t = b;//⑧可以
return 0
}
同样是空间值修改权的问题,我们将语句前后顺序换一下就不可以了。
a变量的值是可以修改的,所以a允许左值引用和常量引用。
b变量的值也是可以修改的,那么语句⑦最终是引用b变量的这个空间,凭什么就不行呢?这是因为语句⑦要经过tt这个变量,在平常我们知道不能把一个const常量赋值给一个变量,在这里依旧适用,虽然实质上语句⑦的t最终是b变量空间的别名,但是它不能通过常量引用类型 的tt来当作b变量空间的别名。通俗点说,tt觉得它不能修改b空间的值,而t想要通过tt作为b变量空间的引用别名,tt发现t可以修改b变量空间的值,tt就会想着它都修改不了b变量空间的值,凭什么同意让可以随时修改b变量的值的你来当作b变量空间的别名,所以⑦这句代码行不通。
但是终究有办法的,比如语句⑧,t可以不通过tt而是直接问b可以引用不,b变量空间的值可以任意修改,所以b当然允许t来引用。
3.右值引用
对于右值引用,记住以下几点就好了:
(1)他的等式右边一定得是个右值。而且右值引用实际上是新创建一个空间,将右值拷贝复制进去,作为这个新空间的引用。比如int&& a = 10;实际上是两个步骤int tmp = 10;int& a = tmp;。
(2)右值引用类型的变量的空间,允许被左值引用引用(常量引用不能被普通引用引用)。因为右值引用引用完右值后,有自己的空间,而且右值引用类型的变量是一个左值,所以允许左值引用引用。
(3)左值引用只是单纯的给空间起个别名,所以空间的值不允许被修改时,只能用常量左值引用引用,如果是右值,可以用右值引用,而且右值引用只为右值而生。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。