您的位置:首页 > 编程语言 > C语言/C++

C++引用详解

2016-04-08 22:07 225 查看
引用是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的地址传递给函数

如果引用参数实现原理与指针不完全等价,那么必然会导致函数调用出现问题

但结果却很有趣,我发现两种方式,效果完全相同,没有任何差异。

下面是运行时的反汇编:





从反汇编可以清晰的看到,对于直接进行引用参数函数调用,和使用指针参数调用,两者的汇编代码完全没有什么区别

引用在实现的时候,传递的就是一个指针给函数!!

不仅仅是对于简单数据类型如此,对于复杂数据类型这也是同样的:





可以看到,两者都是将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是占据空间的,大小正好是一个指针!!


                                            
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: