【c++笔记四】深入浅出的谈谈:引用(&)
来源:互联网 发布:ios 模仿淘宝地址选择 编辑:程序博客网 时间:2024/06/07 04:47
2015年1月25日 周日阴雨
昨天因为评测了win10就没有更新笔记,今天刚好周日时间比较多,就好好来说下“引用”吧。
个人觉得引用还是有很多知识的,也有很多值得注意的地方。
——————————————————————————华丽的分割线——————————————————————————————————
一、什么是引用?
引用,即别名。
什么是别名?举例:关云长、关二爷都是关羽的别名;9527、华安都是唐伯虎的别名等等。
虽然是别名,但最终都是同一个东西。如上左图,b和c都是a的引用,也即是别名(同右图)。
形如:int& b = a,c = a;b 和 c就是a的引用。也就是在变量名前加上小麻花一样的符号“&”。
二、引用的使用
1.引用的定义:
格式:数据类型 & 对象名 = 目标对象;
如:int & a = 100; const string & c = "hello world";
2.引用必须初始化!
int& b; // error!!!千万不要这么写
int& b = 20;
3.引用不能为空!
int& a = NULL; // error!!!
4.引用不能更换目标!
int& a = 10;
引用就是一个对象的别名,所以在内存中只有一份,所以引用是不占内存的。不信?看程序:
#include <iostream>using namespace std;int main(){ int a = 2; int& b = a; cout<<&a<<" "<<&b<<endl;//打印a和b的地址 b = 100;<span style="white-space:pre"></span> //通过b改变a的值 cout<<a<<" "<<b<<endl; //打印a和b的值 return 0;}
由程序我们很好的证明了这一点:引用只是一个变量的别名,本质上是同一个东西,是没有自己的内存空间的。
三、引用类型的参数
1.引用型参数
让你写一个最简单的程序,交换a和b的程序。你肯定马上就能写出来,那让你用调用子函数的方法交换呢?你说,简单,看程序:
#include <iostream>using namespace std;void swap1(int a,int b){ a = a ^ b; b = a ^ b; a = a ^ b;}int main(){ int a = 100,b = 20; swap1(a,b); cout<<a<<" "<<b<<endl; return 0;}
结果一运行就傻眼了,怎么没有交换呢?如果你有这样的疑问,说明你对函数还不够理解,你还不能很好的区分实参和形参的区别(建议回去好好补补课本吧)。懂的人应该知道,swap1接受的两个形参a和b的值的确是交换了。可是形参只是实参的副本,并不是实参本身,就算他们的值发生了改变也不会影响实参。
可我们上面说的引用就不一样了。如果我们使用引用类型的参数,那我们操作的形参,实际就是在操作实参!所以这里引入一个概念:引用型参数。
我来帮大家写一下吧:
#include <iostream>using namespace std;void swap1(int& a,int& b){ a = a ^ b; b = a ^ b; a = a ^ b;}int main(){ int a = 100,b = 20; swap1(a,b); cout<<a<<" "<<b<<endl; return 0;}我们注意看swap1这个子函数。他的两个参数都是引用类型的参数。仅仅只是在形参前加上了&号就达到了我们的预期效果
在函数中改变实参的值。这只是引用型参数的一个好处,他还有一个好处:避免对象复制带来的开销!看程序:
#include <iostream>using namespace std;void acc(int a,int& b){ cout<<&a<<" "<<&b<<endl;}int main(){ int a = 100,b = 200; cout<<&a<<" "<<&b<<endl; acc(a,b); return 0;}
子函数中的a是形参,是主函数中a的一个副本,所以两个变量的地址不一样。既然是副本,就会发生对象的复制,开辟新的内存空间并且赋值,由此可见开销如何。但是子函数中的b是引用型的形参,实际就是主函数中b的别名,本质上是同一个东西,所以他们的地址一样。说明程序直接拿b过来用,这就省去了上面所说的复制带来的开销啦。
2.常引用型参数
但是我们还得提一个概念:常引用型参数。(const修饰的引用型参数)
格式:const 类型& 变量名= 值;
有时候你可能想把实参的值传入函数但不改变实参的值,又能避免复制带来的开销,你就可以选用常引用型参数。例如:
#include <iostream>using namespace std;void acc(int& a,const int& b){ a = a+b;// b = 100;//error,对b的修改是非法的}int main(){ int a = 100,b = 200; acc(a,b); cout<<a<<" "<<b<<endl; return 0;}
acc函数中的形参b就是:常引用型参数。既可以不改变b的值,也可以省去复制带来的开销。
当然,常引用型参数的第二个作用是:接受常量型的实参。这也是一种兼容性的考虑,我们来举例说明:
#include <iostream>using namespace std;void acc(int& a){}int main(){ const int a = 100; acc(a); return 0;}
acc函数的形参a只是一个普通的引用,但是主函数中的a却是const int类型的,编译器不干了,它说:你不能引用const int的实参!这里我们只要把形参改为:const int & a编译器就会放过你了。在实际编程中,你可能会接受的参数const类型的变量,使用常引用型参数就既可以接收普通变量和常属性的变量,这其实是出于一种兼容性的考虑。
四、引用类型的返回值
在说这个知识点之前,我们先来回顾一下c语言的函数返回值类型能干嘛:c语言中非指针类型的函数返回值只能作为右值,不能作为左值。
不知道大家了不了解左值和右值,我还是简单的讲下吧。说简单点,能放在等号左边的就叫左值,只能放在等号右边的就叫右值。
比如:我们可以写a=123;但是不能写123=a。这里,123就是一个右值,a就是一个左值。一般的,不能改变的都是右值,一般的变量都能做左值。我们还是继续体会第一句话吧:
我们可以看见只有13行报错,因为add函数返回的是int类型而不是指针类型,所以编译器报错了,它说add(2,3)不是一个左值。
但fun函数返回的是int*类型的指针。这就证明了那就话:c语言中非指针类型的函数返回值只能作为右值,不能作为左值。
我们回到正题,先给出一个定论吧:c++ 中如果一个函数返回引用类型,则代表这个函数的返回值可以作为左值。
我先来考你一个题目吧,写出下列函数的输出结果:
#include <iostream>using namespace std;int& imax(int& a,int& b){ return a>b?a:b;}int main(){ int x = 1,y = 2; imax(x,y) = 100; cout<<x<<" "<<y<<endl; return 0;}我们来看看你的答案对不对吧:
这段程序的功能就是把两个数中大的那个数字的值变成100。因为,imax函数返回的是引用型形参a和b中值最大的那个变量的引用。上面那句话说了,引用型的返回值可以作为左值,所以将这个值修改为100。懂了不?
但是,一定要注意这个但是!!!并不是所有类型都能作为引用型返回值的!!!!
为什么呢?因为引用型的返回值,从函数中返回的目标的引用,一定要保证在函数返回以后,该引用的目标依然有效。换句话说,就是你返回的这个东西,要在函数结束之后仍然存在(不会因为生命周期结束而在内存中被释放了)。
因此,千万不要返回局部变量的引用!
因为,局部变量在函数结束之后因为生命周期结束,而被内存释放了,内存中已经没有这个变量了。在前面我们就说过了引用本身没有内存,不能为空。局部变量本身都被消灭了,这个引用也跟着没有了。
那么,我们能返回哪些类型的变量的引用呢?
- 全局、静态、成员变量的引用
- 在堆中动态创建的对象的引用
- 引用型参数本身
我们把他们三个都写出来你就明白了:
#include <iostream>using namespace std;int g_int = 1024; //全局变量struct Count{ int num; int& add(){ return ++num; //返回成员变量的引用 }}; //结构体对象int& g_fun(){ return g_int; //返回全局变量的引用}int& s_fun(){ static int a = 2048; return a; //返回静态变量的引用}int& n_fun(){ return * new int(512);}int& r_fun(int& num){ return num;}int main(){ int& a = g_fun(); //接受返回的全局变量的引用 cout<<a<<endl; int& b = s_fun(); //接受返回的静态变量的引用 cout<<b<<endl; int& c = n_fun(); //接受返回的动态建立的变量的引用 cout<<c<<endl; int tmp = 256; int& d = r_fun(tmp);//接受返回的引用变量本身的引用 Count cn; cn.num = 0; ++cn.add(); cout<<cn.num<<endl; return 0;}
还有不懂的,欢迎评论留言,我尽量解答。
五、引用与指针:
1.在实现层面,引用就是指针。
引用之所以拥有这么强大的功能,都是因为引用是通过指针实现的。
你可以自己想一想,引用是不是一种 可以修改所指向的值 但是不能修改所指对象 的指针?其实我们可以运用 * 和 const,模拟一下引用的实现。
你能说出const int* 和 int* const的 区别吗?
你还天真的以为他们是一样的?那你就错了。看程序:
我们可以观察一下编译器报错的位置:分别是第8行和第12行。
编译器说,*a这个指针是只读(read-only)的,b这个变量是只读的。
仔细观察一下:对a的操作,第8行是改变它所指向的值(报错),第9行是改变它的指向(没报错)。说明如果const在*之前的话,我们不可以改变指针所指向的变量的值但是能改变指针的指向。
对b的操作,第11行是改变它所指向的值(通过),第12行是改变它的指向(报错)。说明如果const在*之后的话,可以改变指针指向的值但是不能改变指针的指向。是不是很像我们所说的引用?
其实,引用就类似于Type* const
PS:学了编译原理的话我们知道,最右推导是规范推导,所以编译器判别一个变量的类型的时候是从右往左看到的。
比如:const int* a:编译器首先知道这个变量叫做a,然后读到”*“说明这是个指针,再读到”int“说明这是个整型的指针,最后读到”const“就下结论:这是一个常量型的指针,到头来还是个指针,只不过指向的是常量。所以地址可以改变,指向的值不能改变。
又如:int* const a:编译器首先知道这个变量叫做a,然后读到”const“知道这个变量是个常量,再读到”*“说明这是个指针型的常量,最后读到”int“就知道了,a是一个指针型的常量,说到底这是一个常量。所以这个指针的值(指针的地址,即指针的指向)不能变,但是指针所指向的值随你便。
2.在语言层面,引用不是实体类型,因此与指针存在明显的差别。
这里我们就要谈谈引用和指针的区别了:
(1)指针可以不初始化,其目标可在初始化后随意变更 (除非是指针常量),而引用必须初始化,且一旦初始化就无法变更其目标
这个我们在最开始就强调了,就不在过多的解释了。
(2)存在空指针,不存在空引用
最开始我们也说明了这一点。
(3)存在指向指针的指针,不存在引用引用的引用。
看看程序你就懂了:
(4)存在引用指针的引用,不存在指向引用的指针
依然来看程序:
编译器说了:不能定义指向int& 的指针。
(5)存在指针数组,不存在引用数组,但存在数组引用。
一切代码说了算!
编译器又说了,你定义了引用数组是不对滴!
关于数组引用,我想多说几句,因为这个很实用的哦。
比如,给你一个数组,怎么求数组的大小呢?
你肯定会想到这么做:sizeof arr / sizeof arr[0]。当然这么做没错,但是把数组传到子函数中能这么做吗?且看代码:
为什么在主函数里面显示的大小是3,在子函数里面显示的就是1呢?那是因为你在主函数定义的,arr数组名代表的是数组整体,而你把arr作为参数传到子函数中之后arr数组名仅仅代表数组的首地址。那我们如何在子函数中也能求数组大小呢?这就需要用到数组引用,把数组作为整体传入到子函数中去。且看代码:
#include <iostream>using namespace std;void ssize(int (&arr)[3]){ int len = sizeof arr / sizeof arr[0]; cout<<len<<endl;}int main(){ int arr[3] = {1,2,3}; int len = sizeof arr / sizeof arr[0]; cout<<len<<endl; ssize(arr); return 0;}
我们将sszie子函数中的形参改为int (&arr)[3],这是个数组引用,本质上是个引用,所以可以代表数组整体。
————————————————————————————————华丽的分割线———————————————————————————————————
不知道看完这篇之后你有什么收获呢?还有什么疑问呢?都欢迎评论给我留言。
如有不正确的地方还望各位不吝赐教啊!
引用还是很实用的,以后编程会经常用到,所以掌握它是很有必要的。
1 0
- 【c++笔记四】深入浅出的谈谈:引用(&)
- 《深入浅出MFC》笔记(四)
- 谈谈 C++ 的引用
- 谈谈 C++ 的引用
- 谈谈 C++ 的引用
- C++笔记之谈谈【引用】的那些事
- C/C++中关于地址、指针和引用变量的学习笔记(四) : 函数
- 四种引用的简单笔记
- [C++]指针和引用(四)
- C++学习笔记(四) 引用
- C#,深入浅出全接触(四)_C#教程
- C#,深入浅出全接触(四)_C#教程
- 深入浅出NodeJS笔记(四)--- 事件订阅与雪崩问题
- 深入浅出CUDA(四)
- 深入浅出Mysql(四)
- C++primer笔记(四)
- C语言笔记(四)
- C语言程序设计笔记(四)
- 2.16 求数组中最长递增子序列
- 简约而不简单——Android SimpleAdapter
- Canvas---Canvas版画图,坐标轴绘制,网格绘制,橡皮筋式画直线
- 关于object.innerHTML的一点发现
- Jsp页面中获取系统当前日期
- 【c++笔记四】深入浅出的谈谈:引用(&)
- ubuntu root 失败
- hdu 1102 & poj 2421 Constructing Roads
- PHP类的学习
- xamarin iOS地图开发
- PrintStream
- Tiny64开发板连接nfs,执行qt程序
- DateStream
- 基础类BaseViewController