本章介绍了ActionScript3语言基本组成元素的语法知识,但并不简单。在本章所有节里,有大量对基础知识的深入探讨和延伸,也有和其他面向对象编程语言的横向比较。
本章导读
对于ActionScript3语言初学者,建议本章从头到尾,仔细放慢速度阅读。标题尾部带有星号的节,可以暂时跳过不看;和Java、C#语言比较的部分可以跳过不看。
对于ActionScript2和1的用户,建议阅读全章。在ActionScript3中,一些非常熟悉的概念也可能发生变化。比如数据类型默认值的变化,以及新数值类型,等等。和Java、C#语言比较的一部分,可以跳过不看,但有空时建议阅读,可以加深对ActionScript3语言设计思路的理解。
对于熟悉其他OOP语言的开发人员,建议快速略读本章,但对星号部分,以及和Java、C#比较部分应当放缓阅读。ActionScript3在语言上有一些独特的地方需要注意。
2.1 ActionScript3中数据的本质及重要性质
数据是一切编程语言的基石。在ActionScript中,我们非常熟悉的所有数值,比如MovieClip的帧数、舞台的大小及视频流播放的状态,都是通过数据来描述。我们给数据起了各种各样的名字,这些名字就是通常所说的变量(variable)。通过变量(variable)来调用数据。
那么数据又到底是什么?有哪些类型?变量又是怎样与数据发生联系呢?
2.1.1 *一切都是对象(Object)1
ActionScript3中所有的数据都是对象。
一切皆对象。
这句话很耳熟,英文原文是“Everything is an Object”。著名的Bruce Eckel在其经典著作《Thinking in Java》2 中说过这句话。ActionScript3和Java、C#一样,是纯粹的面向对象语言。
ActionScript3中最核心、最元老的类就是Object类3。Object类是根类,其余所有一切数据类型都是它的子类和子类的衍生类。
ActionScript3初学者注意,对Object类不明白可以跳过,没有关系。类和对象的概念,详细易懂的阐述见第5章。可以先看看相关解释,再来看本章。
2.1.2 ActionScript3中数据类型概述
和其他面向对象语言(Object-oriented Language)一样,ActionScript3的数据类型也分为基元数据类型和复杂数据类型。这两种数据类型不仅仅是概念上的区分,在使用方式上也很不一样。
基元数据类型(primitive data type)4是我们在编程时要频繁使用到的数据类型。比如说数字、文字、条件真假。有些编程经验的人都知道,这是语言的基本构成单元。ActionScript3中预定义的基元数据类型一共有:Boolean、int、Number、String和uint。
其中,int、Number、uint是处理数字的。Int用来处理整数,Number用来处理很长又有小数点的数字,uint处理很大的正整数。详见介绍2.4.2节。
String是处理文字的,称为字符串。
Boolean,又称布尔值,用来标识真假。这种类型的数据只有两个,一个是真(true),一个是假(false)。
复杂数据类型(complex data type)是相对于基元数据类型而言的。简单的复杂数据类型,往往是由一些基元数据类型构成的。如Array(数组),可以直接由一些数字(或者字符串)作为元素组成。更高级一点的复杂数据类型,其组成元素也是复杂数据类型。比如说一个Object对象,它含有3个Array。可以这样一直嵌套下去,组成很复杂的数据类型。经常用到的ActionScript3复杂数据类型有:Array、Date、Error、Function、RegExp、XML和XMLList。我们自己定义的类也全部属于复杂数据类型。
2.2 变量的声明和使用
初学者往往误以为变量就是数据,实际上并不是这样。变量好比是一个遥控器,指向我们要操纵的数据。对变量进行操纵,变量指向的数据就会发生相应的变化。
变量必须先声明再使用,不然编译器会报错。那么,为什么要先声明变量呢?道理很简单,我们必须要必须要先告诉Flash Player建立一个遥控器,才可能给这个遥控器起名字,并使用这个遥控器。不然,你觉得Flash Player能怎么做呢?遥控器连名字都没有,Flash Player怎么找到并操作它呢?
2.2.1 声明变量的语法
在ActionScript3中声明变量的格式如下:
var 变量名:数据类型;
var 变量名:数据类型=值;
var是一个关键字,用来声明变量。变量的数据类型写在冒号后。其次,如果要赋值,那么值的数据类型必须和变量的数据类型一致。
ActionScript3每行结尾不加上分号不会导致报错。但是为了代码标准化,应当加上。而且某些ActionScript IDE(开发环境)在写代码时就会将这种情况报错。
//错误的例子
i; //没有加var关键字,即没有声明变量,出错
i=3; //没有加var关键字,出错
// 声明变量的数据类型为int,却赋予了一个字符串的值,出错
var j:int = “String Value”;
//正确的例子
var i:int; //声明了一个int型变量,但没有赋值,只好使用默认值
var k:int = 100; //声明了一个int型变量,并赋值为100
var h; //声明变量h,但未指定类型,默认为untyped
var g; //声明变量g,效果同上行
与ActionScript2、ActionScript1对比
声明变量时如果不加上数据类型,那么ActionScript2中会默认该对象为Object。但在ActionScript3中再也不是这样了,不加上数据类型的声明,那么该变量就被归为未声明类型(untyped)。
ActionScript3中允许仅声明变量而不赋初值。在这种情况下,该变量将根据其类型赋予默认值。详细见2.4.8节“Null、NaN、undefined及各自应用对象;变量的默认值”。
2.2.2 变量命名规则
本书为强调变量名的重要性,特意写入本节。本节内容看似基础,但是其重要性易被人忽略。笔者平时看到不少ActionScript爱好者的变量命名很糟糕,而且还对变量的命名重要性一无所知,这让人很遗憾。其实,很多公司招聘程序员都非常看重个人变量命名习惯。个人命名习惯的好坏直接影响到团队合作的效率。因此,笔者觉得有必要花费些许笔墨好好强调一下。
命名规则不仅仅是为了让编写的代码符合语法,更重要的是增强自己代码的可读性。要做到自己看得清楚,别人也得看得明白。任何一个有经验的程序员都会叮嘱新手,对命名要认真对待。因为在团队开发中,规范命名关乎整体的工作交流和效率。因此,标准清晰的命名是优秀代码的必备条件。
那么要做到那几点,才可以算是标准的变量命名呢?
1. 尽量使用有含义的英文单词作为变量名
使用英文单词命名变量,已经是业界通用的做法。而且使用的英文单词的意义应说明变量的用途。比如:一个变量的名为address,这个变量存储的应该就是地址。这并不是语法的要求,而是实际交流的需要,工作上的标准。如果诸位写代码只是自己玩玩,不需要与别人合作,那么似乎可以不管这一条,使用汉语拼音亦可。但笔者仍然认为,不管什么情况尽量使用英文单词命名。因为,常用的命名也不过两三百个英文单词。就算完全没有英文基础,查查英文字典,咬咬牙就能坚持过来。
2. 变量名采用骆驼命名法
骆驼式命名法,是指混合使用大小写字母来构成变量和函数的名字。首字母小写,第二个词首字母大写。比如,highLevelFlag变量,就是high、level、flag 3个词连在一块,首单词首字母小写,其余单词首字母大写。
变量的名字应当使用“名词”或者“形容词+名词”构成。比如,width、height、maxHeight、oldWidth。
3. 变量命名符合“min-length&&max-information”原则
所谓“min-length&&max-information”原则,是指变量名的长度应当越短越好,对变量代表的含义描述得越清晰越好。举例,有一个变量,用来表示所能容忍的程序中最大组件的宽度。那么maxUIWidth肯定比maxmumWidthOfUIComponent要好记得多。
这也意味着,尽量使用大家能认可的单词缩写。比如max与maximum,id与identification,err与error,等等。
4. 尽量避免变量名中出现数字编号
除非逻辑上的的确需要编号,否则尽量避免命名字中出现数字编号。比如,id1、id_2、flag1等。用数字编号最省事,大部分时候程序员为了偷懒时才会这样做。这样不肯为命名动脑筋,就会导致产生无意义的名字。这些变量加数字所代表的含义很容易在日后被遗忘,也不会被别人所理解。
2.2.3 *变量的本质
之前说过,在ActionScript3中,所有数据都是对象。我们通过变量来操作对象。变量和对象是怎样的联系?变量持有引用(Reference),而引用则指向要操作的对象。因此,实际我们是通过引用来操作对象。这句话很抽象,其实我倒愿意用一个更易懂的比喻:引用好比是一个遥控器,变量是遥控器的名字。引用可以直接遥控到要操作的内存中的对象。我们对引用的操作,实际上就相当于操作内存中的对象。
请先看以下这行代码,然后我们解释一下真正的实现过程:
var kingda:Array = new Array(1,2,3;)
那么实际上在幕后是怎么实现的呢?又发生了哪些事呢?
首先,我们用var这个关键字告诉FlashPlayer要建立一个名字叫“kingda”的遥控器。“:”后面跟着“Array(数组)”,意思是说这个遥控器的类型是专门用来操作数组的。注意:这个时候,遥控器还不能工作,因为Flash Player还没有告诉它能遥控哪个数组对象。
然后,用new这个关键字,告诉Flash Player要建立一个对象。用“Array()”告诉Flash Player,对象的类型是数组:用“(1,2,3)”告诉Flash Player这个数组包含3个数:1,2,3。
最后,我们用“=”号告诉Flash Player,嗨,让这个名叫kingda的遥控器来遥控这个新的数组对象吧。OK,变量初始化完成了。
变量、引用、对象,这些是在所有OOP语言中都非常重要的概念。但是市面上清楚介绍这三者区别和本质的书并不多见。希望上述比喻能够让初学者对3个重要概念有进一步的领会。
进阶知识
引用(Reference)的真正实现机理:
每个对象在生成时,都会在内存中请求一块空间来保存它的数据。根据对象的大小和类型,它可以需要占用的空间大小也不等。访问对象的时候,我们不是访问对象在内存中的数据,而是通过引用去访问。引用也是一种数据类型,可以吧它想象为C语言中指针的东西,它指示了对象在内存中的地址,在ActionScript3中这些对用户都是不可见的,我们不能够观察到这个地址究竟是什么。我们也不需要知道它,有引用就可以操作对象了。
2.2.4 *重要:值类型和引用类型的区别
本节的重要性怎么强调也不过分。在实际运用时,初学者所犯的错误中,有相当大的一部分来自于对值类型和引用类型的混淆。即使是编程老手,偶尔也会在这个地方马失前蹄。
相比把数据类型分为基元数据类型和复杂数据类型,笔者认为,使用值类型和引用类型的分类,更加直观。在C#中,数据类型就分为值类型和引用类型。值类型直接存储值:而引用类型存储引用,指向要操作的对象。C#中的值类型正好对应于ActionScript3中的基元数据类型,C#中的引用类型正好对应于ActionScript3中的复杂数据类型。
那么在ActionScript3中怎样区分数据到底是值类型和引用类型呢?
ActionScript3中基本类型只有Boolean、int、Number、String和uint。那么,很简单,ActionScript3中值类型只有这几种,其余的数据类型就全是引用类型。
除此之外,还有一个典型的特征:值类型的数据不用new关键字来创建7;必须使用new关键字创建的一定不是值类型。
最关键的问题来了:值类型和引用类型在使用上有什么区别呢?
以值类型int和引用类型Array来做说明。先看值类型int的例子代码:
//值类型的例子
//声明变量a,赋值为3
var a:int = 3;
//声明变量b,并将a的值赋值给b
var b:int = a;
b = 9; //改变变量b的值为9
trace (“a现在的值是:”+a);
trace(“b现在的值是:”+b);
//输出结果
//a现在的值是:3
//b现在的值是:9
根据结果可以发现,把a的赋值给b后,改变了b的值,a的值并没有因为b值的改变而变化。这就是值类型的特点:直接存值。每个变量的值不因为其他变量值的改变而改变。
再看引用类型的情况,以Array为例:
//引用类型的例子
//声明变量a,新建一个数组[1,2,3]赋值给它
var a:Array = new Array(1,2,3);
//声明变量b,把变量a引用赋值给a
var b:Array = a;
//改变b数组第一个元素的值为4
b[0] = 4;
trace (“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;
//新建一个数组[4,5,6],将引用赋值给b
b [0] = 100;
trace (“a现在的值是:” + a);
trace (“a现在的值是:” + a);
//输出结果;
//a 现在的值是:1,2,3
//b 现在的值是:100,5,6
我们看到a、b的值居然互不干扰了。原因是什么呢?请看第三句“b=new Array(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倍左右9 。在一些特色的对性能要求高的场合,基元数据类型的优先使用可以时效率大大提高。
与其他语言对比
在Java中,基础数据类型(primitive data type)不是对象,相对应的包装类(wrapper)才是。如,int的包装类是Integer,double的包装类是Double。
在C#中,基础数据类型都是System命名空间的对象。每个基础数据类型名字,都是各自对应的类名的缩写或者别称。比如,int就是类System.Int32的缩写,double是System.Double的缩写。
而在ActionScript3中,基础类型就是对象,和其包装类在应用中没有什么分别连名字也一样。Int就对应着顶层类int,Number就对应着顶层Number。
既然在ActionScript3中基础类型是Object,我们可以直接调用它的方法(method)。例:
var i:int = 10000;
//调用int对象的to Exponential方法,输出i的指数形式
trace (i.toExponential(2));
//输出:1.00e+4
2.3使用const声明变量
在ActionScript3中新增了const关键字,用于声明常量。所谓常量,就是恒定不变的数值。我们可以把它看成一种特殊的变量,这种变量只能在声明时赋值,而且一经赋值,就不能再更改。如果试图使用赋值语句修改这个“变量”,那么编译器会报错。
使用const声明常量的语法,与使用var来声明变量的语法完全一样。只不过将var替换成const即可。
const foo:int = 100;
如果试图改变常量的值,编译器会报错。见下例:
const foo:int = 100;
foo = 99; //报错:1049
这样通过一个const,你可以告诉编译器和其他程序员,这个数值需要保持恒定不变。不管何时,只要确认一个变量的值不应被改变,就应该将它设为常量。这样,便可以让编译器来协助我们确保这一约定不被破坏。
const的运用是非常广泛的。使用ActionScript3编程,提倡能用常量,就尽量常量。只要程序意图中,某个变量不应发生变化,就可以把它设为常量。
对于值类型来说,常量持有的就是值;对于引用类型来说,常量持有的是引用。注意,对引用类型而言,常量只能保证有的引用不变,并不能保证引用对象自身的状态不发生改变。
const foo:Array = [1,2];
var b:Array = foo;
b[1] = 100;
trace (foo); //输出:1,100
//说明foo指向的数组内容被改变了
foo = [2,3];//这儿则会报错:常量不能被改变
2.4 基础(非基本)数据类型
注意,本节标题中基础数据类型并不是前文的基元(基本)数据类型(值类型)。所谓基础的数据类型,是指我们在ActionScript3编程中经常要用到的数据类型。那么,一般来说,基础数据类型包括:
所有的基元数据类型:Boolean、int、uint、Number、String。
两种复杂数据类型:Array、Object。
2.4.1 布尔值:Boolean
布尔值是用来表示真假的数据类型。它只有两个值:true(真)、false(假)。如果我们声明了一个布尔值变量,但却忘了赋值给它,也就是说忘了初始化它。那么此时这个变量的值是什么呢?答案是false。
在ActionScript2中,如果忘了初始化Boolean型变量,那么默认值是undefined,并不等同于false。下面来比较一下ActionScript2和ActionScript3中布尔值的不同。
ActionScript2中的情况:
//在ActionScript2中运行
var b:Boolean;
trace (b); //输出:undefined
trace (b == false); //判断b和false是否相等,结果输出:false
ActionScript3中的情况:
//在ActionScript3中运行
Var b:Boolean;
trace (b); //输出:false
trace (b == fales); //判断b和false是否相等,结果输出:true
2.4.2 数字:int、uint、Number
在ActionScript3中表示数字值的数据类型很少,只有3种:int、uint、Number。其中int和uint是整型数值,专门来处理整数。Number用来处理浮点数。
int 是有符合32位的整型数,数值范围是从-2147483648(负的2的31次方)到2147483647(正的2的32次方,减1)。说白了,就是-231~+(231-1)。为什么是31次方,而不是32次方呢?因为要留一位来标注正负号。
uint是没有符合的32位整形数,数值范围是0~4294967295。也就是0~232-1,刚好比int最大值大2倍再减1。
Number类型是64位浮动值。它的数值范围是1.797693133486231e+308到4.940656458412467e-324。“e+308”是科学记数法的表示方式,意为“乘以10的正的308次方”。
何时用整型值?何时用浮点值?一般来说,整数的运算时,使用整型值。涉及到小数点时。请使用浮点值。应当指出的是,如果你的整数数值运算涉及到除法,那么建议改用浮点值。还有一些特别需要注意的细节,请参见2.4.3节。
与其他语言对比
ActionScript3中int类型和C#、Java中的完全一样。
Uint类型和C#中的unit类型完全一样,但在Java中没有对应的类型。对于大整数型,Java中使用的是长整型long。Long是有符合的64位整形数。未来的ActionScript3后续版本中可能会加入long型的支持。
Number类型和C#、Java中的双精度浮点值double类型相同。未来ActionScript3后续版本中Number类型有可能会和其他语言统一、改成double类型。单精度浮点值float类型也可以加入。
关于未来ActionScript3可能加入的其他新类型,请参见2.6节:ActionScript3的保留字。
2.4.3 重要:使用int、uint、Number应当注意的事项
在ActionScript2中,不管数值是整型还是浮点型,我们只有一种类型:Number。我们把它即当成浮点型来使用,也当成整型用。看起来非常方便,但这却是ActionScript2代码执行效率不高的原因之一。因为,改用整型时却别无选择要用Number,就导致了浮点值的滥用。浮点值需要比整型值更多的硬件资源,存储整型值int只需要4字节,而存储双精度浮点值Number需要8字节。
所以,区分数值是整型还是浮点型是很有必要的。而且这也是标准编程语言的一个特征。以后ActionScript还会加入更多的数值类型来满足不同的需要。这样对我们来说,编程时选择数值类型就显得更加重要了。
使用数值类型的原则,用两句话概括就是:
能用整数值时优先使用int和uint。整数值有正负之分时,请使用int。只处理正整数,优先使用uint。处理和颜色相关的数值时,使用uint。
碰到或可能碰到小数点时用Number。
下面我们来逐句解释。
能使用整型int时,应当尽量使用int。比如。在for循环中,再也不要习惯性使用Number了。比如,在计算人数时,计算书本数目时,只可能碰到整数,那么我们就应当使用整型。这不仅仅是效率的问题(int的执行效率比Number高),根据Martin Fowler的观点,这更是涉及到程序易读性和重构方面的问题。
处理0xFFFFFF这样的十六进制颜色数值信息时,我们推荐使用哪个uint。0x000000到0xFFFFFF的范围,换算成十进制数就是0~16777215,虽然并没有超出int型的范围,但是考虑到颜色值一般都是正整数,使用uint型更加对口。另外,当我们处理含alpha通道值的像素值时,也必须使用uint整型值。因为这种像素值的范围是0x00000000到0xFFFFFFFF,换算成十进制则刚好是0~4294967295,正是uint的数值范围。在ActionScript3中,从某种意义上说,uint几乎就是为了这样的像素值类型准备的。
当我们处理数值时,遇到或可能遇到小数点时,那么我们就要使用浮点值。一般来说,涉及到除法时,我们就要慎重考虑是否要用浮点型Number。除法使用整型值很容易产生预想外的错误,因为整型值一般直接剔除小数点后面的值。如下例:
var a:int = 9;
var b:int = a/2;
trace (b);
//输出:4
var c:Number = a/2;
trace (c);
//输出:4.5
下面我们要说的是,要特别当心整型数值的边界。
使用uint时,要特别注意不一样在它前面使用负号。也不要让uint型数值在计算机和处理中小于零,也不要让它大于极限值4294967295。下面的代码展示了违反这条规则时,所出现的不合逻辑的现象。
var i:unit = 0;
i--;
trace (i);
//我们可能期望trace结果是-1,但实际上输出的是:4294967295
var j:uint = 4294967295;
j++;
trace (j);
//我们可能期望是4294967296,但实际上输出的是:0
同样,整型值int也有它的上下限值,我们也要注意。上限值是2147483647,下限是-2147483648。下例演示超过上下限值后,会出现不合逻辑的现象。
var iMin:int = -2147483648; //将下限值赋给iMin变量
iMin--;
trace (iMin);
//此时输出的是:2147483647
//iMin自减1后,没有变成-2147483649,反而变成了最大值2147483647。
iMin++; //我们让已经成为最大值的iMin再自增1
trace(iMin);
//此时输出的是-2147483648,变成了最小值。而不是我们期望的2147483648
有过其他编程语言经验的读者对以上几点可能比较熟悉。但是,ActionScript2及以前的开发者可能对以上新数据类型的特性有所陌生,因此稍作介绍。
那么使用Number类型有没有我们需要注意的呢?有,而且很可能大家一直都没有注意到这些细节。而这些细节在大一点的项目中,很可能产生一些让我们烦恼不已而又极难发现的Bug。
小数相加不一定能得到整数。
比如,我们声明一个变量floatB,初始值为零,让它连加10次0.1。我们期望得到数值1,但实际上我们得不到,只能得到一个莫名其妙的0.9999999999999999。示例如下:
var floatA:Number = 1.0;
var floatB:Number = 0;
for (var i:int = 0; i<10; i++){
floatB+=0.1;
c++;
}
trace (floatB);
//输出:0.9999999999999999
trace (floatA == floatB);
//输出:false表示不相等
trace (floatB -1);
//floatB比数值1小多少呢?输出的结果是“莫名其妙”的:-1.110223024625156e-16
因此,应当在编程时避免用这样的算法。如果实在需要浮点数相加得到整数,那么要记得使用Math.round()来修正。
不要让数值差距过大的浮点数相加减,结果可能有偏差。
比如,让100 000 000和0.000 000 001相加。我们期望结果是100 000 000.000 000 001,但实际上结果是100 000 000。无论我们让它们相加多少遍,结果都是100 000 000。示例如下:
var floatC:Number = 100000000;
//让floatC和0.000000001相加100000000次
for (var i:int = 0; i<100000000;i++){
floatC = floatC + 0.000000001;
}
trace (floatC);
//输出:100000000
//和没加一样
//我们将floatC赋值为0.000000001,让它们同样小的值相加
floatC = 0.000000001;
trace (floatC + 0.000000001);
//输出:2e-9
//同样小的值相加成功了
一般来说,只要数值差距不超过十几位,不会有问题。在实际应用中,比较少碰到这种情况。但如果将Flash用于某些精确的数值计算时,就一定要注意这种情况了。
2.4.4 字符串:String
String(字符串)可以说是开发人员打交道最多的数据类型了。顾名思义,字符串,就是一串字符。这里的字符串指的是Unicode字符,比如,每个汉字就是一个Unicode字符。
字符串的地位相当重要。有人说,程序员一辈子至少有60%的时间耗在字符串和相关处理上。这句话虽有些许夸张,但也说明了String的重要性。对普通文本的处理,对XML里面文本数据的处理,对外部文本的处理,等等,无一不和字符串相关。
本部分先说一下字符串的一些最基本操作。关于字符串的详细应用和高级应用请参见第13章“字符串:String”。
如何声明一个字符串?见以下代码:
var stringSample1:String; //声明一个String型变量。此时未定义,默认为null
var stringSample2:String = “”; //声明一个空的字符串,已经定义
var stringSample3:String = new String(); //用包装类来声明,与上行效果相同
var stringSample4:String = “abc”; //声明一个内容为abc的字符串
var stringSample5:String = new String(“abc”); //同上行效果相同
var stringSample6:String = ‘abc’; //也可以使用单引号来声明新字符串
如何知道字符串的长度?字符串是由字符构成的,那么字符串长度其实就是指字符串的个数。所谓字符,指的其实就是char,但ActionScript3中没有char类型,对于Java和C#,以及其他语言开发者,可能有些不适应。但是,笔者认为,char类型迟早都会加入。详见2.6节。
var stringSample:String = “this is a sample”;
var stringLength:Number = stringSample.length; //使用.length就可以得到长度
trace (stringLength); //得到长度16
为什么会得到16?因为一个空格也算一个字符。其实除了空格,还有一些看不到的字符比如说换行符、制表符,等等。怎么表示呢?在字符串中使用“/n”表示换行,“/r”表示回车,“/t”表示制表。“/”是转义符。那么如果想在字符串中表示“/”怎么办?使用“//”就可以了。同理,如果要在字符串中表示双引号(或单引号),在前面加斜杠即可。
2.4.5 数组:Array
在编程中,会经常碰到一些数据需要放在一起使用,比如说编制一个清单,包含一个班级所有学生的名字。那么这个清单就是一个数组。数组Array是极为常用的一种数据结构。
几乎所有的编程语言都支持它,只不过实现和使用的方式各有差别。先用标准术语介绍一下数组,后面还有详细浅显的解释和示例。
在ActionScript3中,数组是以非零整数为索引的稀疏数组(sparse array),不支持类型化数组(typed array)。以非负整数为索引值来访问数组元素。数组最多容纳232-1个元素,即4294967295.
再打一个比方,在ActionScript3中,Array好比一组空盒子,盒子编号从整数0开始。每次Array初始化时,都默认给了每一个空盒子一个空值,那就是null。
所谓非类型化数组,意思就是我们并不限制所有盒子都要放同一种类型的数据。所谓稀疏数组,意思就是不要求每个盒子都要装东西,比如我们可以让第1个和第9个装东西,其他留空。
最后强调一下,每个盒子中装的都不是数据对象本身,而是指向数据对象的引用,也就是我们之前所说的遥控器。操作数组时一定要记住这一点,并记住操作引用。如果盒子中装的是值类型,那么操作这个盒子中的元素时要按照值类型来:如果盒子中装的是引用数据类型,那么要按照引用数据类型的方式。参考2.2.4节“*重要:值类型和引用类型的区别”。
所谓多维数组,意思就是每个数组元素中放的是数组对象。一般来说,在这种情况下,所有元素中所放的数组应该长度相等,类型相同。当数组元素指向的数组长度不统一时,本数组就称为交错数组。
数组的声明方式有以下几种:
var a:Array; //声明一个数组变量a,但还没有告诉a这个引用指向谁。Trace得到null
var b:Array = [];//直接声明一个空数组b。trace得到空白的显示,但不再是null了
var c:Array = new Array(); //效果同声明b的方法
var d:Array = [1,2,3,4]; //直接使用[]操作符,建立一个含有整数1、2、3、4的数组
var e:Array = new Array(1,2,3,4) // 使用Array类来进行和d同样的操作
var f:Array = new Array(5); //声明一个长度为5的空数组,此时每个数组元素都为空
与其他语言对比
在Java和C#中,数组都是类型化数组,即使用类型声明。因此:
所有元素都应当是同一类型的。ActionScript3则没有这个限制。
在初始化时,数组元素可以初始化为不同的值,比如C#中,int[]a = new int[5]时,所有数组元素初始为零。在ActionScript3中则不一样,由于没有类型区分,所有元素初始化后,默认值都为null。
如何访问数组中的元素呢?我们只需要知道元素的位置,就可以使用数组运算符([])来访问它。比如:
d [0]
d[1]
索引为0代表第一个元素。第二个元素的索引值为1,其余依次推类。
数组的基本概念介绍到此为止,数组的操作及详细的应用在“Array”一章有详细讲述。
2.4.6 Object及关联数组
Object(对象)实在是大有来头。它是ActionScript3所有数据结构的基石,是面向对象编程思想的载体。所有的数据类型,包括Array、String等基本类型,甚至Function、Class都是它的子类。不过、本章属于基础章节,不对Object的其他方面进行深究,而只把它作为数据集合来介绍。
Array(数组)也是数据集合的一种。通过索引值来访问Array(数组)的各个成员。Object(对象)则是通过字符串来访问成员。说得更简单点,Array用数字来访问成员,Object通过名字来访问成员。
Object的成员有两种:一种是属性(Property),用来存放各种数据:一种是方法(Method),存放函数对象。
成员的名字,有时也被称为键(Key):成员被称为与这个键对应的值(Value)。这个定义读者要记住,不少技术文档和书籍中喜欢这样的称呼。
声明一个新的Object(对象)很简单,有两种方法:
//1.使用构造函数
Var foo:Object = new Object();
//2.使用空的大括号对作为new Object()函数的语法快捷方式
Var bar:Object = ();
这两种方法的效果完全相同,都是新建出一个空的Object。
使用空的大括号构造Object时,可以将属性写进去。不仅如此,还可以将方法直接写进去。见示例2-1.
示例2-1 使用大括号来新建对象
//写入多个属性,属性名和属性值以“:”号隔开,属性之间以“,”号隔开
var foo:Object = {name:”kingda”,web:”www.kingda.org”};
trace (foo.name);
//输出:kingda
//直接写入多个方法。这种形式很少用到,在这里只是告诉大家,可以这样运用。格式同上
var bar:Object = {hello:function(){trace (“Hello,guys!”);},
ok:function():String{return”that’s ok”;}};
bar.hello();
//输出:Hello,guys!
trace (bar.ok());
//输出:that’s ok
Object 可以动态添加属性。看下例:
//先初始化,即新建一个空对象,将其引用赋值给变量kingda
Var kingda:Object = {};
//新增一个属性name,将字符串“黑羽”赋值给它
kingda.gender= 1;
trace (kingda.name);
//输出:黑羽
也可以动态添加方法,接上例:
kingda.hello = function(){
trace(“Hi,ActionScript 3”)
}
kingda.hello();
//输出:Hi,ActionScript 3
在以上例子中,由于我们确切地知道成员的名字,所以可以直接使用点号运算符(.)来添加或访问它们。但如果是在程序运行时需要访问一个成员,而这个成员的名字到了运行时才会知道,那么就需要用到数组运算符([])了。
看下面这个例子,我们先取系统时间的毫秒数,如果数字是偶数,我们就增加一个中午属性“名字”:如果是奇数,那么就增加一个英文属性“name”。毫秒数只有等到程序运行时才会知道。见示例2-2。
示例2-2运行时使用数组运算符[]访问对象
var kingda:Object = {};
var now:Date = new Date();
var property:String;
trace (now.getTime());
//输出当前时间距离1970年1月1日的毫秒数:1172715590484
If ( (now.getTime()%2 == 0) {
Property = “名字”;
} else {
property = “name”;
}
//注意:使用字符串变量property作为kingda的属性名。使用了数组运算符
//property的值由当前时间决定,只有程序运行时才能决定property到底是“名字”还是“name”kingda[property] = “黑羽”;
//对kingda对象进行属性遍历,显示出所有的属性名和属性值
for (var I in kingda) {
trace (“当前属性名:” +I “ 当前属性值:” + kingda [i]);
}
//输出:当前属性名:名字 当前属性值:黑羽
当然,你也可以直接用数组运算符加字符来访问或添加属性。一般是为了绕过编译时检查访问对象属性时,才会使用数组运算符。看下例。
var kingda:Object = {};
kingda[“web”] = www.kingda.org; //这儿是数组运算符
trace (kingda.web); //这儿是点号运算符。二者效果相同
与ActionScript2、ActionScript1对比
在ActionScript2中,只在编译时(complie time)检查和区分一个对象的private(私有)成员和publice(公用)成员,在运行是(Runtime)是并不检查的。所以,有一个常用技巧:使用数组运算符绕过编译时的检查,来访问私有成员。有些特殊运用中需要用到这个技巧。
但是在ActionScript3中不论编译时还是运行时都会执行检查,因此这个技巧在ActionScript3中不再适用。
2.4.7 多维数组
在上两节中,我们讲到了数组和用Object实现的关联数组,举的例子中,数组的成员有String、int、Function,等等。实际上,数组的成员还可以是Array。所谓多维数组,很简单,其实就是它的成员也是Array(数组)。所以多维数组往往又被称为嵌套数组。
示例2-3展示新建多维数组的3种常用方法:中括号嵌套创建,构造函数创建,先父后子顺序创建。
示例2-3多维数组创建和访问
//直接使用中括号嵌套来创建多维数组
var sample:Array = [[1,2,3],[4,5,6],[7,8,9]];
//注意数组索引是从零开始的,所以输出的是第三个数组:7,8,9
trace (sample1[2][1]);
//输出第三个数组第二个元素:8
//使用构造函数来创建多维数组
var sample2:Array = new Array(new Array(1,2,3), new Array(4,5,6), new Array(7,8,9));
trace (sample2[2][1]);
//输出:8
//先定义数组的长度,再一一添加子数组
var sample3:Array = new Array(3);
sample3[0] = [1,2,3];
sample3[1] = [4,5,6];
sample3[2] = [7,8,9];
trace (sample3[2][1]);
//输出:8
在以上的例子中,只嵌套了一次,所以是二维数组。二维数组往往用来表示矩阵或网络。
如果嵌套二次,那么就是三位数组。如此类推。一般来说,超过二维的多维数组的运用并不多见。
还要指出的是,ActionScript3支持不规则的多维数组。所谓不规则,是指每个维度的子元素数目没必要一致。以上例中,sample3[2]可以写成[7,8]或[7,8,9,10,11],不一定是3个。
2.4.8 Null、NaN、undefined及各自应用对象:变量的默认值
我们声明变量时,如果没有初始化变量,即没有赋值给它,那么Flash Player会给予它一个值。这个值就是变量的默认值。不同类型的变量拥有不同的默认值。光说不如动手,我们执行如下代码,就可以清楚地知道不同类型的变量会有怎样不同的默认值。
与ActionScript2、ActionScript1对比
这和ActionScript2已经完全不一样了。ActionScript2中只要是未声明的变量,统统默认值为undefined。记倒是好记,却给调试和使用带来了不便。ActionScript3的改进是很值得称道的。
与其他语言对比
和C#不同,ActionScript3中变量没有初始化,不会报错,可以正常使用。其值就是默认值。
看上文代码可以注意到,int、uint默认值为0,但Number的默认值没有采用像C# double型的0.0D,而是使用了IEEE754标准中规定的一个特殊值:NaN。NaN是Not a Number(不是一个数)的缩写。因此,如果忘了对一个Number型变量初始化,那么这个变量参与的任何数学运算的结果都是NaN。见下例:
技巧点
当String类型转换成数值类型时,判断String是否能够正确转换成数值,请不要用:
Number("string")==NaN
这样返回的永远都是false。应当使用:
1(Number("string"))
进行一次Boolean值转换。
Boolean型的默认值是false了。String型的默认值成了null。Array、Object和其他一切Flash Player内置类或者用户创建的类默认值都是null。未定义类型的变量默认值才会是undefined。
如果我们将默认值赋值给不相同的类型,那么会自动转换成被赋值类型的默认值。如,var b:Boolean = NaN,那么trace(b)得到的结果将是Boolean型的默认值false。
2.5 运算符、表达式及运用
编程语言必须要清楚描绘如何进行数据运算。因此,我们必须通过某种表达式告诉电脑进行什么样的数据运算。那么最简便的方法莫过于我们设计一个符号系统来表示它们。这就是运算符最初的设计用意。
再一想,为了学习方便,这个符号系统最好能与我们已熟悉的符号系统相似。所以,语言设计大师们引入的符号系统,大多都是基于我们最为熟悉的算术符号系统设计的。
2.5.1 什么是运算符、表达式?以及运算符的设计用意
运算符(Operator)都必须有运算对象(Operand)才可以进行运算。这个道理很简单,没有数据,运算符对什么运算呢?运算对象和运算符的组合,称为表达式(Expression)。每个表达式都产生一个值,这个值是表达式的值。例如,4+3就是一个表达式,其中加号是运算符,4和3都是加号的运算对象,而这个表达式的值就是7:4==3是一个表达式,其值为false(假):x=4这个赋值运算也是一个表达式,其值就是4。
有多少种运算符操作呢?除了我们熟悉的加、减、乘、除、大于、小于、不等于,这些常见的算术运算外,还有很多编程语言特有的运算,如位运算。本书将从最常用的操作讲起,详细地说明这些运算如何在编程中实际运用。
2.5.2 在ActionScript3中使用操作符
大部分运算符不改变运算对象本身的值。但赋值运算符(=)、递增预算符(++)、递减运算符(--)、算法赋值运算符(+=,-=,*=等)这些预算符就会改变运算符对象的值。
运算符的运算对象基本上都是基元数据类型,而不是复杂类型。但赋值运算符(=)、4种关系运算符(!=,==,===,!==)可以作用于所有数据类型。对于复杂数据类型而言,运算符的操作对象是引用,而不是数据对象本身。基元数据类型和复杂数据类型的区别如果忘了,请参加2.1.2节"ActionScript3中数据类型概述"。
另外要指出的是,加号运算符(+)、加法赋值运算符(+=)、关系运算符(<,<=,>,>=)经过重载后,也可以对字符串(String)对象进行操作。详细参加13.5节"+,+=及<、<=、>、>=在字符串操作中的重载"。
String对象虽然是基元数据类型,但和数值是有区别的。
与其他语言对比
C#、C++、C语言使用者注意,ActionScript不支持用户自定义运算符重载。
2.5.3 *运算符的本质
运算符本身是一种特殊的函数,运算对象就是它的参数,运算结果值就是它的返回值。而每一个表达式都是单个运算符函数的组合,表达式可以看成一个组合成的特殊函数,表达式的值就是该函数的返回值。
2.5.4 最常用的运算符:赋值运算符(=)
赋值运算符等于号(=)将是我们用得最多的一个运算符。它的意思是将等号右边的值(右值rvalue)复制给等号左边的变量。右值(ravlue)可能是基元数据类型,也可以是一个表达式、函数返回值或者对象的引用。而等号左边必须是一个变量,不能是基元数据类型,也不能是没有声明的对象的引用。
它的常见合法形式有:
var a:int = 3; //声明新变量时,赋值 var b:String; b = "new"; //对已声明的变量赋值 a = 3+4-5; //右边是一个表达式,将表达式的值附给a var c:Object = new Object(); //新创建一个对象,将其引用赋值给新变量c var d:Object = c; //将c持有对象的引用赋值给d,这样c、d指向同一个对象
|
非法形式有(注意,以下例子是非法例子,无法编译通过):
4 = 5; //左边是基元数据类型4,非法 var a; "I am a string" = a; //左边是基元数据类型,字符串,非法 new Object() = 5; //左边是未声明的对象,不是唯一变量名,非法 |
上一节说过,大部分运算符只呢对基元数据类型进行运算,但赋值运算符可以操作所有对象。那么要强调的就是,对于赋值运算符,一定要注意值的赋值和引用的赋值之间的区别。对初学者提醒,引用和值的区别很重要,忘了,请参见2.2.4节"*重要:值类型和引用类型的区别"。
2.5.5 算术运算符:加、减、乘、除、模运算、求反运算
var a:int = 4; var b:int = 5; var c:int = a+b; var d:int = a-b; var e:int = a*b; var f:int = a/b; |
对于模运算我们要注意,如果模运算中运算对象不是整数,很可能会出现一些意外的小数。如下例:
var a:int = 13%5; trace (a); //输出:3,a得到值3,正常 trace (13.1%5) //输出:3.099999999999996,而不是我们期待的3.1 trace (12.2%6.1) //输出:0,恰好为整数倍时,即使都有小数,依然结果正确 trace(12.5%6.1) //输出:0.3000000000000007 |
其实使用加、减、乘、除时,如果模运算对象之间小数点差别较大,也很容易出现类似情况。初学者可以暂时忽略这种情况。详细2.4.3节"重要:使用int、uint、Number应当注意的事项"。
减法运算符作为一元运算符来使用的话,是求反运算的意思,相当于将运算对象乘以(-1)。使用如下:
var a:int = -2; var b:int = -a; trace (b); //输出:2 |
另外,加法运算符还可以对字符串进行拼接操作,详见13.5.1节"在ActionScript3中运算符的重载:+和+=的重载"。
2.5.6 算术赋值运算符:+=、-=、*=、/=、%=
可以看出,算术赋值运算符和算术运算符是一一对应的关系。它们和赋值运算符一样,运算符左边只能是变量。
拿加法赋值运算符(+=)来说,它实际上是赋值运算符和加法运算符组合表达式的缩写,举例如下:
var a:int = 0; a += 4; //a此时值为4 var b:int = 0; b = b +4; //b此时值以为4,效果与"+="相同 |
等价关系见表2-1。
表2-1运算赋值运算符等价表达式表
运算符 | 运算符例子 | 等价表达式 |
+= | a +=b | a=a+b |
-= | a -=b | a=a-b |
*= | a *=b | a=a*b |
/= | a /=b | a=a/b |
%= | a%=b | a=a%b |
注意,算术加法赋值运算符(+=)同加法运算符(+)一样通过重载,可以对字符串进行操作。详见:"在ActionScript3中运算符的重载:+和+=的重载"。
2.5.7 关系运算符(1):==、!=、===、!===
关系运算符是二元运算符,也就是左右各有一个运算对象。两边的运算对象可以是变量,也可以是表达式。关系运算符的返回值是布尔值(Boolean),要么为真(true),要么为假(false)。
关系运算符可以分为两类。
- l 判断相等关系的:等于运算符(==),也就是左右各有一个运算对象。两边的运算对象可是变量,也可以是表达式。关系运算符的返回值是布尔值(Boolean),要么为真(true),要么为假(false)。
- l 判断大小关系的:大于运算符(>),小于运算符(<),大于等于运算符(>=),小于等于运算符(<=)。
我们最常用的将是判断相等关系的4个运算符。判断大小关系的运算符在下一节中讲解。
与其他运算符的不同点在于,判断相等关系的4个运算符对左右运算对象完全没有限制
可以是任何数值和对象。
相等的概念,我们非常熟悉,一般指两个对象完全一样,我们就说两个对象相等。在ActionScript3中,对于基础数据类型和复杂数据类型,其相等的意思不同。
对于基础数据类型,如果等式两边值相同,即可判断为相等。但如果是复杂数据类型,那么相等的并不是判断等式两边的对象内容是否完全一样,而是看等式两边的对象引用是否相同。如果相同,即等式两边的运算对象持有的引用完全一样,那么就相等,不一样则不相等。值和引用的区别很重要,如果忘记了或不太清楚,请立刻查看2.2.4节"*重要:值类型和应用类型的区别"。基础数据类型和复杂数据类型的区别间2.1.2节"ActionScript3中数据类型概述"。
如果等于运算符(或不等于运算符)两边的基础数据类型不相同,那么会执行类型转换后,再做比较。而全等运算符(===)和等于运算符(==)的不同在于,全等运算符不执行类型转换。这就意味着,不同类型变量之间的比较用全等运算符一定会返回false。唯一的例外,就是全等运算符对于数值类型一视同仁,所以,如果int、uint、Number类型数值相同,那么全等运算符也会认为相同。
看例子:
var a:int = 5; var b:uint = 5; trace (a==b); //输出:true trace (a===b); //输出:true。Int、uint、Number都是类型,值相同,全等就成了 var c:String = "5"; trace (a==c); //输出:true。执行了数据转换,c被转换成了数值5 trace (a===c); //输出:false。没有数据转换,不是同种数据类型,全等不成立 var d:int = 1; var e:Boolean = true; trace (d==e); //输出:true。执行了数据转换,e被转成了数值1 trace (d===e); //输出:fales。没有数据转换,不是同种数据类型,全等不成立 |
要指出的是,等于运算符和全等运算符对于变量默认值的比较是不同的。
当两边运算对象的值都是Number类型,且值为NaN时,全等和等于运算符都判断为false。
var a:Number; var b:Number; trace (a); //输出:NaN trace (b); //输出:NaN trace (a==b); //输出:false trace (a===b); //输出:false |
但如果两边运算对象的值是undefined和null时,等于运算符判断为相等,返回true,二全等运算符判断不等,返回false。
trace (undefined == null); //输出:true trace (undefined === null); //输出:false |
下面要讲一下较为深入一点的话题,在Java中,使用基础类型包装类创建的同值的基础类型对象直接用等于运算符来判断,结果会为false。
//Java代码 Integer a = new Integer(8); Integer b = new Integer(8); System.out.println(a == b); //输出为false |
这一点和ActionScript2中是一样的。看ActionScript2代码:
//ActionScript 2代码 var a = new Number(5); var b = new Number(5); trace (a==b); //输出为false |
这是因为实际上,在ActionScript2和Java中还是采用引用相同来判断运算对象是否相等的。虽然变量a和变量b引用指向的对象值相同,但它们却不是指向同一个对象,所以用等于运算符来判断只能得到false。但是ActionScript3中,会自动调用它们的值来进行判断,而不是引用,所以再也不会出现这种不合逻辑的情况了,输出结果将会是true。
2.5.8 关系运算符(2):>=,<=,>,<,
关系运算符左边可以是表达式,不要求一定是变量。关系运算符的结果是Boolean值,要么是true,要么是false。
当运算符两边的运算对象都是数值时:
大于运算符(>)、小于运算符(<)、大于等于运算符(>=)、小于等于运算符(<=)和数学中不等式的意思一样,不再赘述。
当运算符一边的运算对象是数值,另一边运算对象是非数值是:
非数值的运算对象会尽量转换成数值,然后再进行数值比较。
看例子:
var a:int = 3; var b:String = "4"; trace (a>b); //输出:false。字符串4此时被转换成数值4了 var c:int = 0; var d:Boolean = true; trace (c>d); //输出:false。布尔值d此时被转换成了数值1 |
但,注意了,如果非数值的运算对象无法转换成数值,那么这个表达式的值始终为false。
var a:int = 0; var b = "kingda.org"; //注意,此时不要声明类型,不然编译不会通过 trace (a>b); //输出:false trace (a<b); //输出:false var c = new Object(); trace (a>c); //输出:false trace (a<c); //输出:false |
如果运算符两边的运算对象都是字符串的话,那么将按照从左到右按字母顺序来挨个进行比较。
2.5.9 逻辑运算符(&&、||、!)
逻辑运算符非常好理解,只包括3个运算符:逻辑AND运算符(&&)、逻辑OR运算符(||)和逻辑NOT(!)。逻辑运算符是二元运算符,意思就是左右两边都必须有运算对象。这个运算对象可以是变量、函数返回值,也可以是表达式。
当两边表达式的值都为true时,逻辑AND运算符(&&)才会返回true。
而两边表达式只要有一个值为true,逻辑OR运算符(||)就会返回true。
用法看下例:
var age:uint = 15; //使用&&规定age>12且age<40才能返回true,满足if条件 If((age > 12) && (age <40)){ trace ("你是青年人!"); } else { trace ("你不是青年人!"); } //输出:你是青年人! age = 2000;
//使用||规定只要age<0或者age>1000,那么就满足if条件 if ((age <0) || (age >1000)){ trace ("不是人啊!"); } //输出:不是人啊! |
逻辑NOT运算符(!)是一元运算符,只有一个运算对象,在右边。作用非常简单,把这个运算对象(比如说,是一个变量或表达式)的布尔值取反。见下例:
var failed:Boolean = false; //failed的值是false,但由于在前面加了一个!运算符,那么!failed的返回值就是true了 //从而满足了if的条件 if (!failed){ trace ("没有失败!") } |
很多优秀的程序员喜欢使用AND(&&)和逻辑OR(||)来简写代码。他们利用的是如下原理:
当使用"&&"时,如果第一个表达式就返回false,那么是不会执行第二个表达式的。只有第一个表达式返回true,才会执行第二个表达式。
对于初学者来说,了解这种代码简写形式,有助于阅读高手编写的程序代码。
2.5.10 三元if-else运算符(?:)
这个运算符又叫做条件运算符,它非常特殊,它是唯一一个三元运算符。也就是说,它一共有3个运算对象。一边的表达形式是:
(条件表达式)?(表达式2):(表达式3) |
当条件表达式返回值为true时,返回表达式2的值:当条件表达式返回值为false时,返回表达式3的值。这样,其实这个三元运算符和下面简单if else语句效用相同,相当于简单if else语句的简写形式。
if (条件表达式) { return 表达式2; } else { return 表达式3; } |
使用这个条件运算符(?:)可以达到简化代码的目的。很多优秀的程序员都有一个共同的特点,喜欢代码简介,所以他们经常用这个运算符。对于初学者来说,即使不想使用这个怪怪的运算符,也应当明白它的意思,这样才可以读懂和学习很多优秀的代码。
2.5.11 typeof、is、as
typeof是用字符串形式返回对象的类型。使用方法如下:
trace (typeof 10); //输出:number |
注意,返回的字符串不一定是类名的小写,对照表格见表2-2。
表2-2 typeof对象类型与返回结果对照表
对象类型 | 返回结果 |
Array | object |
Boolean | boolean |
Function | function |
int | number |
Number | number |
Object | object |
String | string |
uint | number |
XML | xml |
XMLList | xml |
is和as运算符使用很广泛。is用来判断一个对象是否属于一种类型,返回布尔值,true代表属于,false表示不属于。使用格式如下:
trace (9 is Number); //输出:true |
as与is格式一致,内容不同:如果一个对象属于一种类型,那么as返回这个对象;否则返回null。例子:
trace (9 as Number); //输出:9 trace (9 as Array); //输出:null |
2.5.12 *in
in关键字用来判断一个对象是否作为另一个对象的键(Key)或索引,存在返回true,不存在返回false。见示例2-4。
示例2-4 运算符in的使用
var a:Array = ["q","w","e"]; trace (2 in a); trace (3 in a);
var b:Object = {ary:a, name:"ok"}; trace ("ary" in b); trace ("name" in b);
import flash.utils.Dictionary; var c:Dictionary = new Dictionary(); c [a] = "a value"; c[b] = "b value"; trace (a in c); trace (b in c); |
2.5.13 delete:ActionScript3中的作用变了
在ActionScript2中可以使用delete关键字删除对象任意一个的实例属性。但在ActionScript3中,delete关键字的作用已经大大减少,它只可以来用删除对象的动态实例属性,非动态属性不能删除。
var b:Object = {ary:"one",name:"ok"}; //deleted b; deleted b.ary; trace (b.ary);
b = null; trace (b); |
2.5.14优先级顺序
至于优先级顺序,确实很难记,读者可以自行查阅帮助。但更多的程序员选择的是使用括号来代替记忆,至少我自动Bruce Eckel就是这样做的。除了括号多一点,没有什么坏处,还增强了可读性。
2.6 ActionScript3的保留字
所谓保留字,意思就是只留给ActionScript3语言用的英文单词。用户不能用这些保留字来做标识符。不能用做标识符,意味着不能用这些词命名变量、实例、自定义类。如果违反这个原则,将导致编译出错。
ActionScript3中的保留字分为3类:词汇关键字(lexical keywords)、语法关键字(syntactic keywords)、为将来预留的词(future reserved words)。见用图表2-1。
其中为将来预留的词是根据ECMAScript(ECMA-262)edition4语言规范草案所保留的供将来使用的关键字。在代码中也应该避免使用这些关键字。
图表2-1ActionScript3保留字
词汇关键字,共45个。都是经常在ActionScript3自身语言中用到的词汇 | ||||||
as | break | case | catch | class | const | continue |
defaulf | delete | do | else | extend | false | finally |
for | function | if | implements | import | in | instanceof |
interface | internal | is | native | new | null | package |
private | protected | public | return | super | switch | this |
throw | to | true | try | typeof | use | var |
void | while | with |
|
|
|
|
语法关键字,共10个。这些语法关键字不能单个使用,都是配合其他关键字一起使用的 | ||||||
each | get | set | namespace | include | dynamic | Final |
native | override | static |
|
|
|
|
为将来预留的词,共22个。可以看到未来的ActionScript3版本中可能会有哪些新的发展 | ||||||
abstract | boolean | byte | cast | char | debugger | double |
enum | export | float | goto | intrinsic | long | prototype |
short | synchronised | throws | to | transient | type | virtual |
volatile |
|
|
|
|
|
|
2.7本章小结
本章介绍了ActionScript3语言的一些基础知识:基元数据类型和复杂数据类型、运算符、表达式和一些简单的编程规范。
基元数据类型中内容并不多,布尔值、字符串、数组都比较好掌握。ActionScript3中新增了int和uint数据类型,使用数值是有了更多的选择。请ActionScript用户在使用int、uint和Number时,一定要注意2.4.3节提到的细节。
基元数据类型和复杂数据类型的区别是非常重要的。本书中借用了值类型和引用类型来表示二者的区别。并在多个章节中反复强调值类型和引用类型在使用上的不同。请读者务必加以注意。
运算符和表达式的使用是比较基础的内容。本书中解释了一下运算符的本质和一些不常用的运算符在代码老手中运用的例子。在这儿要强调的是,注意delete在ActionScript3中发生的变化。现在的delete只能用来删除动态属性了。这一点要记住。至于运算符优先级顺序。确实比较难记。也不用一定记清,勤用括号来分隔,效果是一样的。
保留字稍微看看即可。在好一点的IDE中,一旦使用保留字,会变色提示,因此一般不会犯用保留字命名变量的错误。
在了解了数据类型、运算符、表达式之后,下一章开始讲述ActionScript3中的流程控制。有了流程控制,才能让程序具有判断的能力,从而实现我们所需要的逻辑。