AS3 值类型和引用类型的区别

来源:互联网 发布:linux shell $() 编辑:程序博客网 时间:2024/05/20 05:03

转自《Actionscript3.0殿堂之路》

2.2.4 *重要:值类型和引用类型的区别

相比把数据类型分为基元数据类型和复杂数据类型,笔者认为,使用值类型和引用类型的分类,更加直观。在C#中,数据类型就分为值类型和引用类型。值类型直接存储值;而引用类型存储引用,指向要操作的对象。C#中的值类型正好对应于ActionScript3 中的基元数据类型,C#中的引用类型正好对应于Action Script3中的复杂数据类型。那么在ActionScript3中怎样区分数据到底是值类型和引用类型呢?ActionScript3 中基本类型只有Boolean、int、Number、String 和uint。那么,很简单,ActionScript3 中值类型只有这几种,其余的数据类型就全是引用类型。

除此之外,还有一个典型的特征:值类型的数据不用new 关键字来创建;必须使用new关键字创建的一定不是值类型。最关键的问题来了:值类型和引用类型在使用上有什么区别呢?以值类型int和引用类型Array来做说明。先看值类型int的例子代码:
//值类型的例子var a:int = 3; //声明变量a,赋值为3var b:int = a; //声明变量b,并将a 的值赋值给bb = 9; //改变变量b的值为9trace ("a现在的值是:"+a);trace ("b现在的值是:"+b);//输出结果://a现在的值是:3//b现在的值是:9
根据结果可以发现,把a的赋值给b后,改变了b的值,a的值并没有因为b 值的改变而变化。这就是值类型的特点:直接存值。每个变量的值不因为其他变量值的改变而改变。再看引用类型的情况,以Array为例:
//引用类型的例子var a:Array = new Array(1,2,3); //声明变量a,新建一个数组[1,2,3]赋值给它var b:Array = a; //声明变量b,把变量a引用赋值给bb[0] = 4; //改变b数组第一个元素的值为4trace ("a现在的值是:"+a);trace ("b现在的值是:"+b);//输出结果://a现在的值是:4,2,3//b现在的值是:4,2,3

同样是把a 的值赋给b 的值,但b 的值改变后,a 的值也跟着改变了。很多初学者都不会想到改变b 值会影响到a 的值,因此大量的此种类型的程序臭虫(Bug)就会出现。那么,最后一个问题:为什么两种类型的行为有这么的区别?

原因就在于,引用类型数据存储的是引用。引用,也就是我们之前所说的遥控器,它指向一个对象。对象都是通过引用来操纵的。当我们操纵一个引用数据类型时,比如说上例中的Array数据类型,其实并没有直接操作数据,而是和这个遥控器在打交道。当“var b:Array=a;”这一句执行时,实际上是新创建了一个数组变量b,将a 持有的引用(而不是值)赋值给了b。因此遥控器a和遥控器b 同时指向了一个数组对象,那么任何一个变量做了什么操作,另外一个变量看起来也就受到了影响。下面我们来逐句解释在这些代码后真实发生的事儿。在本例中,第一行代码告诉Flash Player在内存中创建一个数组[1,2,3]。然后设置变量a持有的遥控器指向这个数组,用正式的话说就是,把这个数组的引用赋值给变量a。第二行代码中,当我们把a 的值赋给b 时,其实Flash Player并没有在内存中再创建一个新的数组[1,2,3],而是直接把变量a 的引用又给了b。因此,b 的引用和a 的引用完全一样,都是指向原来的数组[1,2,3]。所以,当我们通过b 变量改变数组值时,就改变了原来的那个唯一的数组。由于b 和a 遥控器控制的都是同一个对象,所以,理所当然,a 的输出结果也变了。好,理解后,我们再看这种情况:

var a:Array = new Array(1,2,3);var b:Array = a;b = new Array(4,5,6); //新建一个数组[4,5,6],将引用赋值给bb [0] = 100;trace ("a现在的值是:" + a);trace ("a现在的值是:" + a);//输出结果://a 现在的值是:1,2,3//b 现在的值是:100,5,6

我们看到a、b 的值居然互不干扰了。原因是什么呢?请看第三句“b=newArray(4,5,6)”,原来用new关键字让Flash Player有创建了一个新的数组[4,5,6],并让b 这个遥控器指向了它。所以a、b 的引用指向不再一样了,分别指向内存中两个不同的数组。因此,改变b 的内容并不会影响a 的内容,双方终于互不干涉内政了。上面说过,ActionScript3中变量持有引用,指向要操作的对象。和Java不同,ActionScript3 变量本身是不能持有值的。在

ActionScript3 中,值类型变量持有的是指向值类型数据的引用;引用类型变量持有的是指向引用类型数据的引用。不要忘记,不论值类型数据还是引用类型数据,其实质都是对象。值类型即前面说过的基元数据类型:引用类型即前面说过的复杂数据类型。所不同的是,值类型数据是一种不变对象,解释详见下面这一节。


2.2.5 基元数据类型的深入讨论*

在上一节中提到,值类型数据和引用类型数据的操作有着那么大的不同。但是,这两种数据类型的变量持有的不都是引用吗?

既然是引用,就都是指向对象的,那么为什么会有这么大的差别呢?在Java 中,值类型并不是以对象形式存在的。值类型的变量,存储的不是引用,而是直接容纳了具体的值(Value)。在ActionScript3中则不一样,因为本质上,值类型仍然是对象。那么,即使是值类型变量,存储的仍然是引用,而不是直接持有值。

但是,值类型是一种特殊的对象,叫做不变对象(immutable object)。正是这种对象的特殊行为导致了我们对值类型的使用方式和引用类型并不相同。不变对象,顾名思义,一旦被建立后,就不能再被更改。有些操作看起来似乎是要更改了不变对象中的内容,但实际上不是。一旦虚拟机发现指向一个对象的引用要改变该不变对象的值,就会另行创建一个新的不变对象来接受新的值。比如说下面这个例子:

var aname:int = 1;aname = aname + 2;
第一行创立了一个int 类型的不变对象A出来,它的值是1,并赋给了变量aname。第二行加上了一个整型值2,改变了aname 的值。但是,不变对象A并没有改变。实际上发生的事儿是:第二行的结果是导致了一个新的整型值不变对象B 被创建,B 的值为3。然后不变对象B 的引用被赋给变量aname。换句话说,此时aname 持有的引用不载指向不变对象A,而是指向新创建的不变对象B。这就是不变对象工作原理的一个示范。不变对象A怎么办?由于它不再被使用,会被AVM(ActionScript虚拟机)自动回收。
基本类型int、uint、boolean、Number、String都是不变对象。除String实现上稍有特殊外,其余的原理相同。

那么为什么要设计出这么一种不变对象,而不采用直接存值的方式呢?这有多方面的考虑。

一方面,将基元数据类型用不变对象的方法来实现,使得引用的效率和传值一样高。

另一方面,由于变量持有的是引用,而不是直接持有值,导致不变对象可以被重复引用。而引用的内存消耗一般比值要小很多,所以对内存的使用率也会大大提高。

举个例子,如果有10000 个字符串变量,值都是“abcde”。那么实际上只有一个值为“abcde”的不变对象在内存中被创建。10000 个字符串持有的都是对这个不变对象的引用而已。相比在内存中创建10000 个同样的字符串,这10000 个引用的内存消耗是相当低的。我们来想象一下,如果这个字符串值是一个很长的字符串呢?比如说长度为10000 个字母的字符串。可想而知,这种设计对内存的节省会是多么巨大。

因此,由于这种不变对象的机制,基本数据的执行效率是相当高的。一般比复杂类型快数倍。在Java 中,有人做过专门测试,得出的结论是要快10 倍左右。在一些特色的对性能要求高的场合,基元数据类型的优先使用可以时效率大大提高。

原创粉丝点击