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" >

从反汇编能够清晰的看到,对于直接进行引用參数函数调用。和使用指针參数调用。两者的汇编代码全然没有什么差别

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

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

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


时间: 2024-10-19 06:11:09

C++引用具体解释的相关文章

06 变量和引用

变量 linux 中变量分为, 本地变量, 环境变量, 位置变量 本地变量: 类似C 中的局部变量, 在新启动的shell中不存在, 只有当前shell中先定义了, 之后才能引用 环境变量: 适用于所有由登陆进程产生的子进程, 简言之, 环境变量在用户登陆后到注销之前的所有编辑器, 脚本和程序都有效 位置参数: 也属于变量, 它用于向shell脚本传递参数, 是只读的(可见, shell脚本也是传值) 变量赋值 如果变量值有空格, 则必须加"" 号, 所以这里强调一下, 在引用变量时,

PHP 的 返回引用(方法名前加&) 和 局部静态变量(static)

先阅读手册==========从函数返回一个引用,必须在函数声明和指派返回值给一个变量时都使用引用操作符 & : 例子 17-13. 由函数返回一个引用 <?php   function &returns_reference()   {       $someref = 0;       return $someref;   }      $newref = &returns_reference();//相当于 $newref = &$someref;   ?>

C++中的引用到底是什么

这也算是一个老生常谈的问题,写这个其实就是想趁着暑假把博客丰富一下. 咱随便在谷哥.度娘.病软引擎上搜搜都可以得到各种关于引用的解释,无非就是"引用不同于指针,引用是一个变量的别名""使用引用就是使用变量本身"""等等这些,于是大量的概念轰炸下,"引用不占用空间"这一言论貌似也是到处都有,流传甚广,几近泛滥,已经有淹没真想之势.于是本着追逐真理之精神,把引用究竟占不占空间这事儿解释清楚,至于引用与指针是不是不同的东西,这一哲学

php foreach使用引用的陷阱

最近工作中在foreach中使用引用的时候出现一个怪现象,使用2次foreach的时候数组值发生了改变,代码示例如下 1 <?php 2 $arr = array('1','2','3'); 3 foreach($arr as &$row){ 4 } 5 foreach($arr as $row){ 6 } 我的预期结果是1,2,3 但是实际结果输出1,2,2奇怪了,遍历数组难道还会改变数组的值么,猜测原因肯定出现在&row这个引用上.在第2个循环里打印$arrArray( [0]

理解PHP的变量,值与引用的关系

--- title: 理解PHP的变量,值与引用的关系 createdDate: 2015-03-11 category: php --- PHP的变量与C++中的变量是两种截然不容的概念.如果没有理解清楚,使用C++的方式来思考PHP就会遇到一些问题. C++中,变量与值是绑定的.值是内存的上的一块内存上的数据,而变量则是操作这块内存的名称.变量消失(比如超出作用域)值也会消失. 而PHP中,变量和值是两个概念.PHP是一种弱类型语言,值在PHP的内部(zend引擎),被存放在一个zval结构

关于C中指针的引用,解引用与脱去解引用

*,& 在指针操作中的意义 (1)* 大家都知道在写int *p 时,*可以声明一个指针.很少人知道*在C/C++中还有一个名字就是“解引用”.他的意思就是解释引用,说的通俗一点就是,直接去寻找指针所指的地址里面的内容,此内容可以是任何数据类型,当然也可以是指针(这就是双重指针,后面将会讨论).需要注意的是,在变量声明的时候,*不能当做解引用使用,只是表示你声明的变量是一个指针类型. example1: int a=50; int *p=&a;// '&'的作用就是把a变量在内存中

数据结构——时间复杂度

分析算法的时间复杂度: 算法的时间复杂度就是反应了程序执行时间随着输入规模增长而增长的量级,这个标准可以很好的反映出算法的优劣性质. 算法的频度: 一个算法执行所耗费的时间完全可以以程序执行的次数进行估算,程序执行的次数越多,时间复杂度也就越复杂,也就是说算法花费的时间与算法中语句执行的次数成正比例,因此,一个算法中语句执行的次数可以叫做时间频度.记为T(n). 时间复杂度: 由于n表示规模,当n不断变化的时候,T(n)也会随之不断变化,当我们需要知道他的变化趋势的时候,就要引入时间复杂度.一般

Volley的初步了解

Volley的介绍 Volley是什么? 2013年Google I/O大会上推出的网络请求和图片加载框架 其优点是api简单,性能优秀 非常适合数据量不大但是通信频繁的网络请求,而对于大数据量的操作,如文本下载,表现则没有那么好 Volley内部仍然是使用的HttpURLConnection和HttpClient进行网络请求的,只是对于不同的Android版本进行了响应的切换,2.3之前使用的HttpClient,2.3之后使用的是HttpURLConnection 这是因为在2.3版本之前,

零基础学python-8.1 列表

列表是python里面最具灵活性的有序集合对象类型 它可以包含其他任何类型的对象:数字.字符串.甚至是列表 特点:可变对象.可在原处修改.可通过偏移值.分片.方法调用 特性: 1.任意对象的有序集合 2.通过偏移读取 3.可变长度.异构和任意嵌套 4.属于可变序列 5.对象引用数组:列表包含0个或者多个对象的引用 操作 解释 L=[] 一个空列表 L=[0,1,2,3] 四项:索引从0到3 L=['abc',['123','abc']] 嵌套自列表 L=list('abc') 可迭代项目的列表