引用是个神奇的存在,以前一直泛泛的接触,没有深入的了解总结下。后果也很简单,一知半解,不敢用。我一直相信一句话:未知才是最恐惧的。为了不在惧怕,所以我特意翻阅博文,资料,总结一下引用的相关知识。
2015/1/23日更新:
关于数组引用相关知识的重新认识。
1..引用的概念:
变量的别名。至少目前我是这样理解的。看好了是变量的别名。
若想对常量进行引用(因为常量连名字都没用,可以认为是临时对象,既然名字都没有了,哪来的引用别名?,除非引用名就是它的本名了.),那么要用非常规手段,详情请往下看。
2..声明方法:
typed var; type& ref=var;语法不用多说。区分下一点:这个地方的&符号不在是取地址了(但是我个人还是认为和引用和地址有很联系,只是我一时间没找到证据)。
引用和指针的区别:无空引用,声明就要绑定,而且不能改了,用做其他变量的别名的,并且可以改变指向。自行领会意思即可。
#include <iostream>
using namespace std;
int main(){
int var1 = 10;
int &ref = var1;
ref = 11;//通过别名来修改源。
int var2 = 21;
//int& ref = var2;报错。
cout <<"The value of ref is "<< ref << endl;
cout << "The value of var1 is " << var1 << endl;
cout << "测试地址引用变量和源变量的地址: " << endl;
cout << "&ref :" << &ref<<endl;
cout << "&var1 " << &var1 << endl;
system("pause");
return 0;
}
看到地址都一样就更好理解了。
Tips:关于数组的引用:更正一下,是存在数组的引用的!!!不存在的是引用的数组。一开始翻看很多博文,很多都说是不存在数组的引用的。后来看书的时候,发现是存在数组的引用。其实也是可以接受的,因为数组是占据内存空间的,为那个内存空间起个别名是无伤大雅的。关于引用:引用是是对象,这个地方所说的对象是有占据内存的行为。如果你非要说引用占据内存,那么那个内存是源对象的。引用别名自身并没有开辟内存空间。参考数组的定义我们可以知道,数组是同类对象的集合。既然引用都不是对象,那么根本就不存在引用的数组。
最后一点感悟:1.要学会甄别信息,网络上的信息真真假假。2.要多看经典权威书籍,比如C++Primer等。
如果你妄想建立数组的引用那么你可以参考如下的例子:
#include <iostream>
using namespace std;
int main(){
int a[4] = { 0 };
int (&ref)[4]=a; //这个地方就是数组的引用。对别名的操作等价于对a的操作。
system("pause");
return 0;
}
如果你想建立常量的引用可以参考如下代码:
#include <iostream>
using namespace std;
#include <string>
int main(){
const int& a = 1;
const string& str = "Hello World";
//这个地方的字符串是常量,我们通过使用const来建立引用。
cout << str << endl;
system("pause");
return 0;
}
这个地方我讲一下自己的理解,仅供参考。
首先讲为什么用const。因为常量字符串是const 的,所以这个地方也需要用const。
讲一下对引用的理解:因为引用是别名,那么对任何一个名字进行修改,那么正常情况下是会影响到另一方的。我们把左方称为叫引用名,右方就源名。在常量字符串的引用中,由于源名是const的,也就是说无法通过源来进行修改值了,那么这个时候如果通过引用名来修改值会发生什么?明显的不合理嘛。所以干脆双边等价,我没法修改,那么引用名也别想修改我了。这里似乎有点平等意思。
在看一下普通的非常量引用,因为我们是可以通过源名来修改值的,那么在非人为的情况下,我们也应该给引用名同样的权利去修改值(人为情况可以看一些底下的const引用)。所以这个地方也展现出了一种平等对称的意思。
说不定当初涉及的时候也考虑到这一点。
3..引用的应用:
1)引用作为参数:
在C语言中,一般函数的形参是传值,大块的数据就传递指针地址。C++多出了一种引用类型,更棒了。传统的传值方式是拷贝传递进来的变量的副本的,必然造成浪费。但是如果你传递进来的是变量的别名,上面我们也测试过了,对引用的操纵相当于变量自身的操纵了,省去了拷贝的过程。而且比指针更好看一点吧,用用指针必然会涉及到解引用,取地址等操作,不如引用方便。
测试:
#include <iostream>
using namespace std;
void Swap(int& a, int &b){
a = a + b;
b = a - b;
a = a - b;
} //奇技淫巧,偶然看见的,和大家分享下。不建议用,不好阅读。
void Swap(int& a, int& b){
int temp = a;
a = b;
b = temp;
}
void Swap(int* a, int* b){
int temp = *a;
*a = *b;
*b = temp;
}
void Swap(int a, int b){
int temp = a;
a = b;
b = temp;
}
int main(){
int a = 10;
int b = 11;
//Swap(a, b);重载多了,产生二义性了。
cout << a << endl << b;
return 0;
}
2)常引用的诞生:
首先辨别一下什么是常引用:
举个例子: int b=10; const int& a=b;那么我们称a为b的常引用。为什么需要常引用?看到const大家应该是有点感觉的。const只读的,也就是无法写入进行修改的。这个地方这样理解可能有助于你理解指针那边的const修饰。让&符号和变量名结合起来,让const无论从哪个角度看都是修饰int 或者是修饰&a的。也就是说a的值是不能修改的。注意好了,我只是说a不能修改而已,意思也就是无法通过别名来修改源了,也就是禁了一个端口而已,一开始他们是双向的,现在不是了,我可以通过源进行修改,但是别名却是无法修改了。不要简单的理解为双端都不可以修改了,你还是可以通过源修改变量的值的。这个地方经常用在函数的形参表里。譬如打印元素,肯定是不需要修改值的,那么我们就可以声明为const引用,安全又节省。
例子:
#include <iostream>
using namespace std;
string foo(){
}
void bar(string& s);
int main(){
//看看下面的语句是否正确
//bar(foo());
//bar("Hello World");
//这个地方两个语句都是错误的!
}
为什么是错误的??
首先分析下bar(foo()); foo()函数返回的是什么东西??想清楚这个就知道为什么错了。
foo()返回到额只可能是一个string 之类的临时对象拷贝之流的,也就是const的,但是你的bar(string& s)函数里面的形参却是非const的。这个就涉及到了const和非const之类的转化,你要上想让foo()函数的返回值可以被接受,那么你的形参表就要改成bar(const string& s),这样就可以传递进去了。
这个地方还是涉及到临时对象的问题!抽空一定 要总结下。
看一下第二句为什么也不对。"Hello World" 是什么?一个常量字符串。把这个字符串传递进去就会调用构造函数进行转换成string类型的(还是涉及到了临时对象的问题)但是转换的过程的产生的是临时对象啊!!又是const的,好了,回到上面的圈子了。所以还是要形参表。
Tips:引用型参数尽可能定义为const的.
3)引用作为返回值:
意思也就是返回一个引用,引用就是别名,所以可能是返回自身了。好处之一就是不会产生副本拷贝。要知道一般的函数返回值返回的是副本拷贝,并不是本身。
举个例子说明下:
#include <iostream>
using namespace std;
int Add(int a, int b){ //传递进来的是a,b本身嘛?不是!是a,b的拷贝,又是临时对象。
int temp = a + b;
//return temp; //这个情况下返回的是temp本身嘛?不是!!返回的只是一个temp变量的拷贝.
//return a + b; //这个情况返回的是啥?我理解的还是临时对象。
}//想想这个地方返回的是啥东西?
int& Add(int a, int b){
int temp = a + b;
//return temp; //这个地方就要返回局部变量的引用
//如果直接返回a+b会怎么样?
return a + b; //报错了!!为什么?这个地方返回的临时对象,而临时对象是const的。而返回值
} //类型的要求却是int&。又涉及到了const到非const的转换问题了。
int& Add(int& a, int& b){
//return a + b; 错误的。
int temp = a + b;
return temp;//返回了临时变量的引用,不推荐,不至于崩溃,貌似我的编译器会进行优化。
}
int& Add(int& a, int& b, int& Result){
Result = a + b; //推荐写法。
return Result;
}
int& Add(int a, int b, int& Result){
Result = a + b; //次推荐写法。
return Result;
}
int main(){
//不测试了。
}
1.在Effective C+里面写到:不要返回局部变量的引用。因为局部变量在函数调用结束后就会被销毁。但是我的编译器或许进行优化了,让临时对象先不要被销毁(延迟他的销毁时间),所以编译是可以通过的,但会警告你返回
了临时变量的地址。
2.不能返回函数内部new分配的内存使用。如果被函数返回的引用只是作为一个临时变量出现,如果临时变量没用被赋予给一个实际变量,那么引用所指的空间也就无法释放了,造成Memory Leak!
3.返回类成员的引用最好是const的。
4.<< 和>> 必须返回引用具体的解释可以翻看前一篇博文。
4..引用和多态:
引用是除指针之外的另一个可以产生多态效果的手段。其实这个地方有点验证我之前的猜测了。指针和引用有着说不清道不明的关系。上面的话意味着一个基类的引用可以指向子类的实例。
举个例子:
A→B(A为父类,B为子类)那么这样的语句是成立的:B b; A& ref=b;是不是和指针很接近啊。
根据深度探索C++对象模型里面的解释:父类的指针会被编译器解释,然后只是涵盖一定的内存空间,那么引用是不是也是如此呢?具体的自己去摸索下吧。感兴趣可以翻看前面的博文。
5..总结下:
c++为什么要返回引用?不用返回副本,返回副本需要调用拷贝函数。实际返回的是结果的副本,而不是返回结果自身,要返回自身,用 引用,但是不要返回局部变量的引用,推荐的写法已经在上面的例子中给了。
maybe 返回局部变量的引用编译器会进行优化或者是C++中存在一些特殊的规定,允许的它的生命周期比较特殊。谁验证过了请 告诉我。
Tips :
一个判断左右值的技巧。看看能不能取地址,不能取就是右值,能取的就是左值。典型的a++就是右值,而++a却是左值,不信自己验证下。
简单的解释下:这个地方还是涉及到了临时对象的问题!临时对象真是无处不在。
a++我们知道是具有连带作用的,先返回a的值,注意是值,从未说过是a本身。这个地方返回的是a的一份拷贝。又是临时工,const的,当作是无法当作左值的,因为左值是可以被写入修改的,你都是只读的,怎么进行修改。好像临时工没编制的感觉。。。
同理可以解释解释下++a。先把a的值加上1,然后返回的是a,是a本身。这个地方内部会不会产生一些东西呢?比如你要先把a的值加上1,那么是否需要一个temp=a+1,来临时存储a+1的值,然后a=temp?
具体的我没研究过,也许学过汇编了可以去看看。一般来说++a的效率会比a++高一点,但是不排除编译器进行优化。
参考文献:
CSDN的博文,博客园的博文。因为篇幅太多了,所以没有附上地址。但是这些东西绝对是自己总结过一遍手打的,绝对不是拷贝别人。正如临时对象一样,简单的拷贝而来,生命周期太短,终究不是自己的知识。
End.
下面可能会总结一下临时变量的相关知识,到底C++还有多少坑,但是这种填坑的刺激行为其实挺好玩的。希望自己坚持下去。加油共勉。