C++引用具体解释
引用是C++中新出现的。有别于C语言的语法元素之中的一个。
关于引用的说明,网络上也有不少。可是总感觉云遮雾绕,让人印象不深刻。
今天我就来深入解释一下引用。并就一些常见的观点进行说明,最后附带代码演示样例予以说明(注意。开发环境是vs2013)。
前面先摆出我的观点:
1 引用的出现纯粹是为了优化指针的使用,而提出的语法层面的处理。
2 引用实现原理上全然等价于指针。
3 引用对于传递对象參数有很大的优化和优点。
4 引用有其局限性,与指针相比,有时候可能与面向对象的设计有冲突。
以下给出我的样例。通过这个样例,我再来慢慢解释上面的观点:
- void intreference(int& i)
- {
- printf("[%s]i=%d\n", __FUNCTION__, i);
- i++;
- }
- void objectreference(std::string& str){
- printf("[%s]str=%s\n", __FUNCTION__, str.c_str());
- str += 'i';
- }
- class mystr :public std::string
- {
- public:
- mystr() :std::string(){}
- ~mystr(){
- }
- };
- void testvirtual(mystr&str){
- printf("[%s]str=%s\n", __FUNCTION__, str.c_str());
- }
- class mytest{
- public:
- mytest(){}
- ~mytest(){}
- virtual void test(){
- printf("father\n");
- }
- };
- class mysubtest:public mytest{
- public:
- mysubtest(){}
- ~mysubtest(){}
- virtual void test(){
- printf("hello!\n");
- }
- };
- void testpurevirtual(mytest& test)
- {
- test.test();
- }
- void main()
- {
- char* p;
- int i = 0;
- refint refintfunc = (refint)intreference;
- printf("i=%d\n", i);
- intreference(i);
- printf("i=%d\n", i);
- refintfunc(&i);
- printf("i=%d\n", i);
- refobj refobjfunc = (refobj)objectreference;
- std::string obj = "s";
- printf("str=%s\n", obj.c_str());
- objectreference(obj);
- printf("str=%s\n", obj.c_str());
- refobjfunc(&obj);
- printf("str=%s\n", obj.c_str());
- //int& j = i;
- printf("i %08x,j %08x\n", &i, &i);
- std::string* pstr = new mystr();
- //testvirtual(*pstr);//error C2664: “void testvirtual(mystr &)”: 无法将參数 1 从“std::string”转换为“mystr &”
- mysubtest t;
- testpurevirtual(t);
- getchar();
- }
样例里面我给出了两个引用測试函数和一个变量引用
样例说明了什么:
1 引用实现原理上全然等价于指针
请注意。函数intreference与函数objectreference是一个引用參数的函数
而函数指针refintfunc与函数指针refobjfunc是一个指针參数的函数指针
对于后者的调用。编译器会毫不迟疑的将i的地址传递给函数
假设引用參数实现原理与指针不全然等价,那么必定会导致函数调用出现故障
但结果却非常有趣,我发现两种方式,效果全然同样,没有不论什么差异。
以下是执行时的反汇编:
![]()
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="">
从反汇编能够清晰的看到,对于直接进行引用參数函数调用。和使用指针參数调用。两者的汇编代码全然没有什么差别
引用在实现的时候,传递的就是一个指针给函数!!
不不过对于简单数据类型如此,对于复杂数据类型这也是相同的:
能够看到,两者都是将obj的地址作为參数,放入到了eax,然后再推送到栈中去了
也就是说,在实现层面上面,两者是等同的
那么对于局部,非參数传递的引用呢?
以下是局部引用j和其引用对象i赋值时的反汇编:
注意第一条红线,j是有自己的栈空间地址的!并不是如同网络上所说的别名。不占用空间,等价等等。不是这种!
它仍然要占空间,占一个指针大小的空间。假设i和j是char和char引用,那么j占用的空间甚至比i还大!
在赋值的时候。系统将i的地址给eax。然后再通过eax寄存器将地址传入j,注意dword ptr,这表示指针j!
这和我前面提到的观点:引用实现原理上全然等价于指针 是全然一致的。
>
<
2 既然它在实现层面上全然等价于指针。那为什么还会有引用?
这就要回到我前面提出的第一个观点:引用的出现纯粹是为了优化指针的使用,而提出的语法层面的处理
假设这里使用指针。就会很麻烦!
首先,假设函数的參数是指针。开发者就必需要要验证指针!这个差点儿是无法避免的情况!
否则指针一旦为空,整个程序必定崩溃。
可是引用就避免了这个麻烦——通过语法层面上的干预——使得用户无法显式的传递空指针到函数中去
假设有空指针或者野指针,崩溃仅仅会发生在函数外部,而非内部。
其次。输入.比输入->更加让开发者开心一些,不论是长度还是安全性上,指针式的成员函数调用,总让人心惊胆颤
因此,引用全然是一种语法层面的处理。就是C++中的私有成员变量一样,仅仅是从语法上阻止用户去显式訪问——实际上能够利用指针,强制从内存中读写该变量。
当然引用不只不过这样,之所以面向对象要增加引用,另外一个作用还在于:
假设參数纯粹是一个对象,那么意味着程序须要频繁的在栈上面构造和析构对象。
而引用成功的攻克了这个问题。能够让开发者决定要不要在栈上面构造对象并自己主动析构它。
这样导致效率极大的提升了——非常多复杂的对象。其构造函数和复制构造函数可能异常复杂和耗时。
同一时候,另外一些对象可能并不希望调用者使用它们的构造函数。比方单例对象!
而引用非常好的攻克了这个矛盾。
3引用有没有限制?答案是有!
限制在哪里?我们知道。面向对象设计中有接口这个概念,而C++与之关联的是虚函数。
我们常常会持有一个父类的指针,而在当中填入各种子类的对象,然后通过虚函数去调用相应的子类接口实现。
可是这里使用引用却有限制。仅仅能在声明为父类引用的时候。使用子类,而无法在声明为子类引用的时候使用父类。
指针却能够不受此限制,进行自由的转化(当然这是有风险的!
)
以下给出了一个演示样例:
对于mystr和函数testvirtual,假设传入一个父类对象(实际上还是一个子类,仅仅是是一个父类指针),在语法上这是被禁止!
对于mytest和mysubtest以及函数testpurevirtual,这样又是能够的。
这种限制要求开发人员在设计的时候就必须很仔细,事先想好接口的统一性。否则后面代码就有的改了
当然,这样也有优点,能够避免一些问题。比方空指针或者对象不匹配异常(将一个非mytest或者其子类的对象指针强制转化过来。此时调用必定崩溃。)
- <pre code_snippet_id="1639674" snippet_file_name="blog_20160408_34_61346" name="code" class="cpp">class mystr :public std::string
- {
- public:
- mystr() :std::string(){}
- ~mystr(){
- }
- };
- void testvirtual(mystr&str){
- printf("[%s]str=%s\n", __FUNCTION__, str.c_str());
- }
- class mytest{
- public:
- mytest(){}
- ~mytest(){}
- virtual void test(){
- printf("father\n");
- }
- };
- class mysubtest:public mytest{
- public:
- mysubtest(){}
- ~mysubtest(){}
- virtual void test(){
- printf("hello!\n");
- }
- };
- void testpurevirtual(mytest& test)
- {
- test.test();
- }
- void main()
- {
- int i = 0;
- refint refintfunc = (refint)intreference;
- printf("i=%d\n", i);
- intreference(i);
- printf("i=%d\n", i);
- refintfunc(&i);
- printf("i=%d\n", i);
- refobj refobjfunc = (refobj)objectreference;
- std::string obj = "s";
- printf("str=%s\n", obj.c_str());
- objectreference(obj);
- printf("str=%s\n", obj.c_str());
- refobjfunc(&obj);
- printf("str=%s\n", obj.c_str());
- int& j = i;
- printf("i %08x,j %08x\n", &i, &j);
- std::string* pstr = new mystr();
- //testvirtual(*pstr);//error C2664: “void testvirtual(mystr &)”: 无法将參数 1 从“std::string”转换为“mystr &”
- mysubtest t;
- testpurevirtual(t);
- getchar();
- }
最后给出执行结果的截图:
能够看到。结果充分说明了引用事实上就是指针
这里补充说明一下i和j的问题:
当我声明了j的时候,能够看到函数栈的大小
而没有声明j的时候。函数栈明显变小了
小了12字节,非常奇怪,好像和指针的大小不一致啊
没有关系,我再声明一个指针,我们再看看
看到没有?栈又恢复到了14c了。而我仅仅是声明了一个char*p,而且没有做不论什么调用。
这说明j是占领空间的,大小正好是一个指针!!
C++引用具体解释的更多相关文章
- C++中的引用到底是什么
这也算是一个老生常谈的问题,写这个其实就是想趁着暑假把博客丰富一下. 咱随便在谷哥.度娘.病软引擎上搜搜都可以得到各种关于引用的解释,无非就是"引用不同于指针,引用是一个变量的别名" ...
- Perl解除引用:从引用还原到数据对象
使用引用可以指向数据对象,这似乎很简单. @name1=qw(longshuai wugui); @name2=qw(xiaofang tuner); $ref_name=\@name1; push ...
- C++ 星号* 与 引用&
星号 * 1. 声明的时候有*, 表示指针变量 int *p=&a;// '&'的作用就是把a变量在内存中的地址给提取出来 2. * +地址, 表示地址操作符 3. 数字*数字, 表示 ...
- php的变量引用与销毁机制
在php中,符号"&"表示引用. 1.看看不引用的情况是这样子: $a = "hello world";//定义一个变量,下面赋值给$b $b = $ ...
- 理解PHP的变量,值与引用的关系
--- title: 理解PHP的变量,值与引用的关系 createdDate: 2015-03-11 category: php --- PHP的变量与C++中的变量是两种截然不容的概念.如果没有理 ...
- Java杂谈2——引用与跟搜索算法
Java中的引用 Java“引用”的概念源于C++,原本的定义相当有限:一个引用(Reference)代表的内存通常用于指向另一块内存区域的起始地址.通过引用类型保存的起始地址,可以找到这个引用所指向 ...
- 关于C中指针的引用,解引用与脱去解引用
*,& 在指针操作中的意义 (1)* 大家都知道在写int *p 时,*可以声明一个指针.很少人知道*在C/C++中还有一个名字就是"解引用".他的意思就是解释引用,说的通 ...
- Sanboxie 5.14安装图解
Sanboxie, 即沙盘,引用官方解释:电脑就像一张纸,程序的运行与改动,就像将字写在纸上.而Sandboxie就相当于在纸上放了块玻璃,程序的运行与改动就像写在了那块玻璃上,除去玻璃,纸上还是一点 ...
- PHP代码重用与函数编写
代码重用与函数编写 1.使用require()和include()函数 这两个函数的作用是将一个文件爱你载入到PHP脚本中,这样就可以直接调用这个文件中的方法.require()和include()几 ...
随机推荐
- Ubuntu开机自动禁用无线网络
让ubuntu开机自动禁用无线网络. 1.自启动脚本 将下面这条禁用无线网络的命令添加到“启动应用程序“中,这样开机时无线网络就会被自动禁用. dbus-send --system --type=me ...
- Ruby 和 Python 分析器是如何工作的?
你好! 我作为一名编写Ruby profiler的先驱,我想对现有的Ruby和Python profiler如何工作进行一次调查. 这也有助于回答很多人的问题:“你怎么写一个profiler?” 在这 ...
- jQuery 操作cookie保存用户浏览信息
使用jQuery操作cookie之前需要引入jQuery的一个cookie小组件js,代码如下: /* jQuery cookie plugins */jQuery.cookie ...
- git eclipse 不标记修改后的文件(没有图标标明)
在使用Eclipse做开发的时候,已经修改了某个文件,但是文件的图标没有明显的标示,如图: 解决上面问题的办法如下:
- OpenSAML2.X 在SSO系统中的应用
背景 年底的时候有机会开发一个SPA(单页面应用)的项目,那时候须要用到票据的方式能够用Cookie的方式来登录.当是想到了OpenID或者是CAS的方式来做统一认证中心.后来一个安全界的大牛推荐让我 ...
- OSI各层的功能和主要协议(转载)
OSI各层的功能和主要协议: 物理层 物理层规定了激活.维持.关闭通信端点之间的机械特性.电气特性.功能特性以及过程特性.该层为上层协议提供了一个传输数据的物理媒体. 在这一层,数据的单位称为比特(b ...
- IIS目录禁止执行权限
IIS6: IIS7:
- HDU 1016:Prime Ring Problem
Prime Ring Problem Time Limit: 4000/2000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Other ...
- Bootloader和Root
1. Unlock Bootloader是解除系统启动加载器(Bootloader)的原厂限制, 让用户可以使用到更多的功能(如刷新内核.刷ROM.修改超频....) Bootloader(系统启动加 ...
- UIWindow的一点儿思考
转自:http://www.cnblogs.com/smileEvday/archive/2012/11/16/UIWindow.html 每一个IOS程序都有一个UIWindow,在我们通过模板简历 ...