很完整的2、8、10、16进制转换方法
最近在研究C语言,因为要用到各进制间转换,所以收集了一些资料…
这是一节“前不着村后不着店”的课。不同进制之间的转换纯粹是数学上的计算。不过,你不必担心会有么复杂,无非是乘或除的计算。
生活中其实很多地方的计数方法都多少有点不同进制的影子。
比如我们最常用的10进制,其实起源于人有10个指头。如果我们的祖先始终没有摆脱手脚不分的境况,我想我们现在一定是在使用20进制。
至于二进制……没有袜子称为0只袜子,有一只袜子称为1只袜子,但若有两袜子,则我们常说的是:1双袜子。
生活中还有:七进制,比如星期。十六进制,比如小时或“一打”,六十进制,比如分钟或角度……
我们找到问号字符(?)的ASCII值是63,那么我们可以把它转换为八进值:77,然后用 ‘\77′来表示’?'。由于是八进制,所以本应写成 ‘\077′,但因为C,C++规定不允许使用斜杠加10进制数来表示字符,所以这里的0可以不写。
事实上我们很少在实际编程中非要用转义符加八进制数来表示一个字符,所以,6.2.4小节的内容,大家仅仅了解就行。
6.2.5 十六进制数转换成十进制数
2进制,用两个阿拉伯数字:0、1;
8进制,用八个阿拉伯数字:0、1、2、3、4、5、6、7;
10进制,用十个阿拉伯数字:0到9;
16进制,用十六个阿拉伯数字……等等,阿拉伯人或说是印度人,只发明了10个数字啊?
16进制就是逢16进1,但我们只有0~9这十个数字,所以我们用A,B,C,D,E,F这五个字母来分别表示10,11,12,13,14,15。字母不区分大小写。
十六进制数的第0位的权值为16的0次方,第1位的权值为16的1次方,第2位的权值为16的2次方……
所以,在第N(N从0开始)位上,如果是是数 X (X 大于等于0,并且X小于等于 15,即:F)表示的大小为 X * 16的N次方。
假设有一个十六进数 2AF5, 那么如何换算成10进制呢?
用竖式计算:
2AF5换算成10进制:
第0位: 5 * 16^0 = 5
第1位: F * 16^1 = 240
第2位: A * 16^2 = 2560
第3位: 2 * 16^3 = 8192 +
————————————-
10997
直接计算就是:
5 * 16^0 + F * 16^1 + A * 16^2 + 2 * 16^3 = 10997
(别忘了,在上面的计算中,A表示10,而F表示15)
现在可以看出,所有进制换算成10进制,关键在于各自的权值不同。
假设有人问你,十进数 1234 为什么是 一千二百三十四?你尽可以给他这么一个算式:
1234 = 1 * 10^3 + 2 * 10^2 + 3 * 10^1 + 4 * 10^0
6.2.6 十六进制数的表达方法
如果不使用特殊的书写形式,16进制数也会和10进制相混。随便一个数:9876,
就看不出它是16进制或10进制。
C,C++规定,16进制数必须以 0x开头。比如 0×1表示一个16进制数。而1则表示一个十进制。另外如:0xff,0xFF,0X102A,等等。其中的x也也不区分大小写。(注意:0x中的0是数字0,而不是字母O)
以下是一些用法示例:
int a = 0×100F;
int b = 0×70 + a;
至此,我们学完了所有进制:10进制,8进制,16进制数的表达方式。最后一点很重要,C/C++中,10进制数有正负之分,比如12表示正12,而-12表示负12,;但8进制和16进制只能用达无符号的正整数,如果你在代码中里:-078,或者写:-0xF2,C,C++并不把它当成一个负数。
6.2.7 十六进制数在转义符中的使用
转义符也可以接一个16进制数来表示一个字符。如在6.2.4小节中说的 ‘?’ 字符,可以有以下表达方式:
‘?’ //直接输入字符
‘\77′ //用八进制,此时可以省略开头的0
‘\0×3F’ //用十六进制
同样,这一小节只用于了解。除了空字符用八进制数 ‘\0′ 表示以外,我们很少用后两种方法表示一个字符。
6.3 十进制数转换到二、八、十六进制数
6.3.1 10进制数转换为2进制数
给你一个十进制,比如:6,如果将它转换成二进制数呢?
10进制数转换成二进制数,这是一个连续除2的过程:
把要转换的数,除以2,得到商和余数,
将商继续除以2,直到商为0。最后将所有余数倒序排列,得到数就是转换结果。
听起来有些糊涂?我们结合例子来说明。比如要转换6为二进制数。
“把要转换的数,除以2,得到商和余数”。
那么:
要转换的数是6, 6 ÷ 2,得到商是3,余数是0。 (不要告诉我你不会计算6÷3!)
“将商继续除以2,直到商为0……”
现在商是3,还不是0,所以继续除以2。
那就: 3 ÷ 2, 得到商是1,余数是1。
“将商继续除以2,直到商为0……”
现在商是1,还不是0,所以继续除以2。
那就: 1 ÷ 2, 得到商是0,余数是1 (拿笔纸算一下,1÷2是不是商0余1!)
“将商继续除以2,直到商为0……最后将所有余数倒序排列”
好极!现在商已经是0。
我们三次计算依次得到余数分别是:0、1、1,将所有余数倒序排列,那就是:110了!
6转换成二进制,结果是110。
把上面的一段改成用表格来表示,则为:
被除数
计算过程
商
余数
6
6/2
3
0
3
3/2
1
1
1
1/2
0
1
(在计算机中,÷用 / 来表示)
如果是在考试时,我们要画这样表还是有点费时间,所更常见的换算过程是使用下图的连除:
(图:1)
请大家对照图,表,及文字说明,并且自已拿笔计算一遍如何将6转换为二进制数。
说了半天,我们的转换结果对吗?
二进制数110是6吗?你已经学会如何将二进制数转换成10进制数了,所以请现在就计算一下110换成10进制是否就是6。
6.3.2 10进制数转换为8、16进制数
非常开心,10进制数转换成8进制的方法,和转换为2进制的方法类似,惟一变化:除数由2变成8。
来看一个例子,如何将十进制数120转换成八进制数。
用表格表示:
被除数
计算过程
商
余数
120
120/8
15
0
15
15/8
1
7
1
1/8
0
1
120转换为8进制,结果为:170。
非常非常开心,10进制数转换成16进制的方法,和转换为2进制的方法类似,惟一变化:除数由2变成16。
同样是120,转换成16进制则为:
被除数
计算过程
商
余数
120
120/16
7
8
7
7/16
0
7
120转换为16进制,结果为:78。
请拿笔纸,采用(图:1)的形式,演算上面两个表的过程。
6.4 二、十六进制数互相转换
二进制和十六进制的互相转换比较重要。不过这二者的转换却不用计算,每个C,C++程序员都能做到看见二进制数,直接就能转换为十六进制数,反之亦然。
我们也一样,只要学完这一小节,就能做到。
首先我们来看一个二进制数:1111,它是多少呢?
你可能还要这样计算:1 * 2^0 + 1 * 2^1 + 1 * 2^2 + 1 * 2^3 = 1 * 1 + 1 * 2 + 1 * 4 + 1 * 8 = 15。
然而,由于1111才4位,所以我们必须直接记住它每一位的权值,并且是从高位往低位记,:8、4、2、1。即,最高位的权值为23 = 8,然后依次是 22 = 4,21=2, 20 = 1。
记住8421,对于任意一个4位的二进制数,我们都可以很快算出它对应的10进制值。
下面列出四位二进制数 xxxx 所有可能的值(中间略过部分)
仅4位的2进制数 快速计算方法 十进制值 十六进值
1111 = 8 + 4 + 2 + 1 = 15 F
1110 = 8 + 4 + 2 + 0 = 14 E
1101 = 8 + 4 + 0 + 1 = 13 D
1100 = 8 + 4 + 0 + 0 = 12 C
1011 = 8 + 4 + 0 + 1 = 11 B
1010 = 8 + 0 + 2 + 0 = 10 A
1001 = 8 + 0 + 0 + 1 = 10 9
….
0001 = 0 + 0 + 0 + 1 = 1 1
0000 = 0 + 0 + 0 + 0 = 0 0
二进制数要转换为十六进制,就是以4位一段,分别转换为十六进制。
如(上行为二制数,下面为对应的十六进制):
1111 1101 , 1010 0101 , 1001 1011
F D , A 5 , 9 B
反过来,当我们看到 FD时,如何迅速将它转换为二进制数呢?
先转换F:
看到F,我们需知道它是15(可能你还不熟悉A~F这五个数),然后15如何用8421凑呢?应该是8 + 4 + 2 + 1,所以四位全为1 :1111。
接着转换 D:
看到D,知道它是13,13如何用8421凑呢?应该是:8 + 2 + 1,即
:1011。
所以,FD转换为二进制数,为: 1111 1011
由于十六进制转换成二进制相当直接,所以,我们需要将一个十进制数转换成2进制数时,也可以先转换成16进制,然后再转换成2进制。
比如,十进制数 1234转换成二制数,如果要一直除以2,直接得到2进制数,需要计算较多次数。所以我们可以先除以16,得到16进制数:
被除数
计算过程
商
余数
1234
1234/16
77
2
77
77/16
4
13 (D)
4
4/16
0
4
结果16进制为: 0×4D2
然后我们可直接写出0×4D2的二进制形式: 0100 1011 0010。
其中对映关系为:
0100 — 4
1011 — D
0010 — 2
同样,如果一个二进制数很长,我们需要将它转换成10进制数时,除了前面学过的方法是,我们还可以先将这个二进制转换成16进制,然后再转换为10进制。
下面举例一个int类型的二进制数:
01101101 11100101 10101111 00011011
我们按四位一组转换为16进制: 6D E5 AF 1B
6.5 原码、反码、补码
结束了各种进制的转换,我们来谈谈另一个话题:原码、反码、补码。
我们已经知道计算机中,所有数据最终都是使用二进制数表达。
我们也已经学会如何将一个10进制数如何转换为二进制数。
不过,我们仍然没有学习一个负数如何用二进制表达。
比如,假设有一 int 类型的数,值为5,那么,我们知道它在计算机中表示为:
00000000 00000000 00000000 00000101
5转换成二制是101,不过int类型的数占用4字节(32位),所以前面填了一堆0。
现在想知道,-5在计算机中如何表示?
在计算机中,负数以其正值的补码形式表达。
什么叫补码呢?这得从原码,反码说起。
原码:一个整数,按照绝对值大小转换成的二进制数,称为原码。
比如 00000000 00000000 00000000 00000101 是 5的 原码。
反码:将二进制数按位取反,所得的新二进制数称为原二进制数的反码。
取反操作指:原为1,得0;原为0,得1。(1变0; 0变1)
比如:将00000000 00000000 00000000 00000101每一位取反,得11111111 11111111 11111111 11111010。
称:11111111 11111111 11111111 11111010 是 00000000 00000000 00000000 00000101 的反码。
反码是相互的,所以也可称:
11111111 11111111 11111111 11111010 和 00000000 00000000 00000000 00000101 互为反码。
补码:反码加1称为补码。
也就是说,要得到一个数的补码,先得到反码,然后将反码加上1,所得数称为补码。
比如:00000000 00000000 00000000 00000101 的反码是:11111111 11111111 11111111 11111010。
那么,补码为:
11111111 11111111 11111111 11111010 + 1 = 11111111 11111111 11111111 11111011
所以,-5 在计算机中表达为:11111111 11111111 11111111 11111011。转换为十六进制
:0xFFFFFFFB。
再举一例,我们来看整数-1在计算机中如何表示。
假设这也是一个int类型,那么:
1、先取1的原码:00000000 00000000 00000000 00000001
2、得反码: 11111111 11111111 11111111 11111110
3、得补码: 11111111 11111111 11111111 11111111
可见,-1在计算机里用二进制表达就是全1。16进制为:0xFFFFFF。
一切都是纸上说的……说-1在计算机里表达为0xFFFFFF,我能不能亲眼看一看呢?当然可以。利用C++ Builder的调试功能,我们可以看到每个变量的16进制值。
alert:弹出警示窗
write:书写
document:文档的意思
prompt:显示可提示用户进行输入的对话框
document.write:在html文档页面中输出内容
console.log在控制台打印括号内容
var用于定义变量
折叠 产生
面向对象语言借鉴了20世纪50年代的人工智能语言LISP,引入了动态绑定的概念和交互式开发环境的思想;始于20世纪60 年代的离散事件模拟语言SIMULA67,引入了类的要领和继承,成形于20世纪70年代的Smalltalk。
折叠发展方向
面向对象语言的发展有两个方向:一种是纯面向对象语言,如Smalltalk、EIFFEL等;另一种是混合型面向对象语言,即在过程式语言及其它语言中加入类、继承等成分,如C++、Objective-C等。
折叠 主要特点
面向对象语言刻画客观系统较为自然,便于软件扩充与复用,有四个主要特点:
(1)识认性,系统中的基本构件可识认为一组可识别的离散对象;
(2)类别性,系统具有相同数据结构与行为的所有对象可组成一类;
(3)多态性,对象具有惟一的静态类型和多个可能的动态类型;
(4)继承性,在基本层次关系的不同类中共享数据和操作。
其中,前三者为基础,继承是特色。四者(有时再加上动态绑定)结合使用,体现出面向对象语言的表达能力。
折叠 典型语言
一般认为,较典型的面向对象语言有:
imula 67,支持单继承和一定含义的多态和部分动态绑定;
Smalltalk支持单继承、多态和动态绑定;
EIFFEL,支持多继承、多态和动态绑定;
C++,支持多继承、多态和部分动态绑定。
Java,支持单继承、多态和部分动态绑定。
五种语言涉及概念的含义虽然基本相同,但所用术语有别。
C#,也支持单继承,与Java和C++等有很多类似之处……
折叠 面向对象语言
基于类的面向对象语言是面向对象世界里的主流。它包括:
Simula,第一个面向对象语言
Smalltalk,第一个支持动态类型的语言
C++,它的大部分基于类的特性继承自Simula.等等等等。
与基于类的语言相对应的是基于对象的面向对象语言。这里“基于对象”的概念和把Visual Basic叫做基于对象的概念是不同的。这里的“基于对象”是指一个只以对象为中心,没有类的概念的语言,类似Python之类的语言。
折叠 类和对象
先看一个类的定义:
classcell is
var contents: Integer :=0;
method get(): Integer is
return self.contents;
end;
method set(n:Integer) is
elf.contents := n;
end;
end;
一个类是用来描述所有属于这个类的对象的共同结构的。这个cell类表示的对象拥有一个叫做contents的整数属性(attribute),这个属性被初始化成0。它还描述了两个操作contents的方法。Get和set. 这两个方法的内容都是很直观的。Self变表示这个对象自己。
对象的动态语义可以这样理解:
一个对象在内部被表示为一个指向一组属性的指针。任何对这个对象的操作都会经过这个指针操作对象的属性和方法。而当对象被赋值或被当作参数传递的时候,所传递的只是指针,这样一来,同一组属性就可以被共享。
(注, 有些语言如C++,明确区分指向属性组的指针和属性组本身,而一些其它的语言则隐藏了这种区别)
对象可以用new从一个类中实例化。准确地说,new C分配了一组属性,并返回指向这组属性的指针。这组属性被赋予了初始值,并包括了类C所定义的方法的代码。
下面来考虑类型。对一个new C所生成的对象,把它的类型记为InstanceTypeOf(c). 一个例子是:
var myCell: InstanceTypeOf(cell) := new cell;
这里,通过引入InstanceTypeOf(cell),开始把class和type区分开来了。也可以把cell本身当作是类型,但接下来,就会发现,那样做会导致混淆的。
折叠 方法解析
方法解析(Method Lookup)给出一个方法的调用o.m(……),一个由各个语言自己实现的叫做方法解析的过程负责找到正确的方法的代码。(者按:是不是想起了vtable了?)。
直观地看,方法的代码可以被嵌入各个单个对象中,而且,对于许多面向对象语言,对属性和方法的相似的语法,也确实给人这种印象。
不过,考虑到节省空间,很少有语言这样实现。比较普遍的方法是,语言会生成许多method suite,而这些method suite可以被同一个类的对象们所共享。方法解析过程会延着对象内指向method suite的指针找到方法。
在考虑到继承的情况,方法解析会更加复杂化。Method suite也许会被组成一个树,而对一个方法的解析也许要查找一系列method suite. 而如果有多继承的话,method suite甚至可能组成有向图,或者是环。
方法解析可能发生在编译时,也可能发生在运行时。
在一些语言中,方法到底是嵌入对象中的,还是存在于method suite中这种细节,对程序员是无关紧要的。因为,所有能区分这两种模式的语言特性一般在基于类的面向对象语言中都不被支持。
比如说,方法并不能象属性一样从对象中取出来当作函数使用。方法也不能象属性一样在对象中被更新。(也就是说,更新了一个对象的方法,而同一个类的其它对象的该方法保持不变。)
折叠 子类和继承
子类和继承(Subclassing and Inheritance)子类和一般的类一样,也是用来描述对象的结构的。但是,它是通过继承其它类的结构来渐进式地实现这个目的。
父类的属性会被隐式地复制到子类,子类也可以添加新的属性。在一些语言中,子类甚至可以override父类的属性(通过更改属性的类型来实现)
父类中的方法可以被复制到子类,也可以被子类override.
一个子类的代码的示例如下:
ubclass reCell of cell is
var backup: Integer := 0;
override set(n: Integer) is
elf.backup := self.contents;
uper.set(n);
end;
method restore() is
elf.contents := self.backup;
end;
end;
对有subclass的方法解析,根据语言是静态类型还是动态类型而有所不同。
在静态类型的语言(如C++,Java)里,父类,子类的method suite的拓扑结构在编译时就已经确定,所以可以把父类的method suite里的方法合并到子类的method suite中去,方法解析时就不用再搜索这个method suite的树或图了。(按:C++的vtable就是这种方法)
而对于动态类型的语言,(也就是说,父子类的关系是在运行时决定的),method suite就无法合并了。所以,方法解析时,就要沿着这个动态生成的树或有向图搜索直到找到合适的方法。而如果语言支持多继承,这个搜索就更复杂了。
折叠 父类和子类
从上述的几个例子来看,似乎子类只是用来从父类借用一些定义,以避免重复。但是,当考虑到subsumption,事情就有些不同了。什么是Subsumption呢?请看下面这个例子:
var myCell: InstanceTypeOf(cell) := new cell;
var myReCell: InstanceTypeOf(reCell) := new reCell;
rocedure f(x: InstanceTypeOf(cell)) is … end;
再看下面这段代码:
myCell := myReCell;
f(myReCell);
在这两行代码中,头一行把一个InstanceTypeOf(reCell)类型的变量赋值给一个InstanceTypeOf(cell)的变量。而第二行则用InstanceTypeOf(reCell)类型的变量作为参数传递给一个参数类型为InstanceTypeOf(cell)的函数。
这种用法在类似Pascal的语言中是不合法的。而在面向对象的语言中,依据以下的规则,它则是完全正确的用法。该规则通常被叫做subtype polimorphism,即子类型多态(按:其实subtyping应该是OO语言最区别于其它语言的地方了)
如果c’是c的子类,并且o’是c’的一个实例,那么o’也是c的一个实例。
更严格地说:
如果c’是c的子类,并且o’: InstanceTypeOf(c’),那么o’: InstanceTypeOf( c ).
仔细分析上面这条规则,可以在InstanceTypeOf的类型之间引入一个满足自反和传递性的子类型关系, 用<;:符号来表示。(按:自反就是说, 对任何a,a 关系 a都成立,比如说,数学里的相等关系就是自反的。而传递性是说,如果a 关系 b,b 关系c,就能推出a 关系c。大于,小于等关系都是具备传递性的)
那么上面这条规则可以被拆成两条规则:
1. 对任何a: A,如果 A <: B,那么 a: B.
2. InstanceTypeOf(c’) <: InstanceTypeOf(c) 当且仅当 c’是c的子类
第一条规则被叫做Subsumption. 它是判断子类型(注意,是subtype,不是subclass)的唯一标准。
第二条规则可以叫做subclassing-is-subtyping (子类就是子类型,绕嘴吧?)
一般来说,继承都是和subclassing相关的,所以这条规则也可以叫做:inheritance-is-subtyping (继承就是子类型)
所有的面向对象语言都支持subsumption (可以说,没有subsumption,就不成为面向对象)。
大部分的基于类的面向对象语言也并不区分subclassing和subtyping. 但是,一些最新的面向对象语言则采取了把subtyping和subclassing分开的方法。也就是说,A是B的子类,但A类的对象却不可以当作B类的对象来使用。(按:有点象C++里的私有继承,但内容比它丰富)
好吧,关于区分subclassing和subtyping,后面会讲到。
下面,重新回头来看看这个procedure f. 在subsumption的情况下,下面这个代码的动态语义是什么呢?
Procedure f(x: InstanceTypeOf(cell)) is
x.set(3);
end;
f(myReCell);
当myReCell被当作InstanceTypeOf(cell)的对象传入f的时候,x.set(3)究竟是调用哪一个版本的set方法呢?是定义在cell中的那个set还是定义在reCell中的那个呢?
这时,有两种选择,
1. Static dispatch (按照编译时的类型来决定)
2. Dynamic dispatch (按照对象运行时真正类型来决定)
(按,熟悉C++的朋友们一定微笑了,这再简单不过了。)
tatic dispatch没什么可说的。
dynamic dispatch却有一个有趣的属性。那就是,subsumption一定不能影响对象的状态。如果在subsumption的时候,改变了这个对象的状态,比如象C++中的对象切片,那么动态解析的方法就可能会失败。
好在,这个属性无论对语义,还是对效率,都是很有好处的。
(按,C++中的object slicing会把新的对象的vptr初始化成它自己类型的vtable指针,所以不存在动态解析的问题。但实际上,对象切片根本不能叫做subsumption。
具体语言实现中,如C++,虽然subsumption不会改变对象内部的状态,但指针的值却是可能会变化的。这也是一个让人讨厌的东西,但 C++ vtable的方案却只能这样。有一种变种的vtable方法,可以避免指针的变化,也更高效。会在另外的文章中阐述这种方法。)
折叠 关于类型信息
虽然subsumption并不改变对象的状态,在一些语言里(如Java),它甚至没有任何运行时开销。但是,它却使丢掉了一些静态的类型信息。
比如说,有一个类型InstanceTypeOf(Object),而Object类里没有定义任何属性和方法。又有一个类MyObject,它继承自Object。那么当把MyObject的对象当作InstanceTypeOf(Object)类型来处理的时候,就得到了一个什么东西也没有的没用的空对象。
当然,如果考虑一个不那么极端的情况,比如说,Object类里面定义了一个方法f,而MyObject对方法f做了重载,那么,通过dynamic dispatch,还是可以间接地操作MyObject中的属性和方法的。这也是面向对象设计和编程的典型方法。
从一个purist的角度看, dynamic dispatch是唯一应该用来操作已经被subsumption忘掉的属性和方法的东西。它优雅,安全,所有的荣耀都归于dynamic dispatch!!!
不过,让purist们失望的是,大部分语言还是提供了一些在运行时检查对象类型,并从而操作被subsumption遗忘的属性和方法。这种方法一般被叫做RTTI(Run Time Type Identification)。如C++中的dynamic_cast,或Java中的instanceof.
实事求是地说,RTTI是有用的。但因为一些理论上以及方法论上的原因,它被认为是破坏了面向对象的纯洁性。
首先,它破坏了抽象,使一些本来不应该被使用的方法和属性被不正确地使用。
其次,因为运行时类型的不确定性,它有效地把程序变得更脆弱。
第三点,也许是最重要的一点,它使程序缺乏扩展性。当加入了一个新的类型时,也许需要仔细阅读dynamic_cast或instanceof的代码,必要时改动它们,以保证这个新的类型的加入不会导致问题。
很多人一提到RTTI,总是侧重于它的运行时的开销。但是,相比于方法论上的缺点,这点运行时的开销真是无足轻重的。
而在purist的框架中(按,吸一口气,目视远方,做深沉状),新的子类的加入并不需要改动已有的代码。
这是一个非常好的优点,尤其是当并不拥有全部源代码时。
总的来说,虽然RTTI (也叫type case)似乎是不可避免的一种特性,但因为它的方法论上的一些缺点,它必须被非常谨慎的使用。今天面向对象语言的类型系统中的很多东西就是产生于避免RTTI的各种努力。
比如有些复杂的类型系统中可以在参数和返回值上使用Self类型来避免RTTI. 这点后面会介绍到。
折叠 协变,反协变和不变
协变,反协变和压根儿不变 (Covarance,Contravariance and Invariance)
在下面的几个小节里,来介绍一种避免RTTI的类型技术。在此之前,先来介绍“协变”,“反协变”和“压根儿不变”的概念。
协变
首先,来看一个Pair类型:A*B
这个类型支持一个getA()的操作以返回这个Pair中的A元素。
给定一个A’ <: A,那么,可以说A’*B <: A*B。
为什么呢?可以用Subsumption的属性加以证明:
假设有一个A’*B类型的对象a’*b,这里,a’:A’,b:B,a’*b <: A’*B
那么,因为,A’ <: A, 从subsumption,可以知道a’:A,getA():A 所以, a’*b<: A*B
这样,就定义A*B这个类型对于A是协变的。
同理,也可以证明A*B对于B也是协变的。
正规一点说,Covariance是这样定义的:
给定L(T),这里,类型L是通过类型T组合成的。那么,
如果 T1 <: T2 能够推出 L(T1) <: L(T2),那么就说L是对T协变的。
反协变
请看一个函数:A f(B b); (用functional language 的定义也许更简洁, 即f: B->A)
那么,给定一个B’ <: B,在B->A 和 B’->A之间有什么样的subtype关系呢?
可以证明,B->A <: B’->A。
基于篇幅,不再做推导。
所以,函数的参数类型是反协变的。
Contravariance的正规点的定义是这样的:
给定L(T),这里,类型L是通过类型T组合成的。那么,
如果 T1 <: T2 能够推出 L(T2) <: L(T1),那么就说L是对T反协变的。
同样,可以证明,函数的返回类型是协变的。
压根儿不变
那么再考虑函数g: A->A
这里,A既出现在参数的位置,又出现在返回的位置,可以证明,它既不是协变的,也不是反协变的。
对于这种既不是协变的,也不是反协变的情况,称之为Invariance
值得注意的是,对于第一个例子中的Pair类型,如果支持setA(A),那么,Pair就变成Invariance了。
折叠 方法特化
方法特化 (Method Specialization)
在前面对subclass的讨论中,采取了一种最简单的override的规则,那就是,overriding的方法必须和overriden的方法有相同的signature.
但是,从类型安全的角度来说,这并不是必须的。
这样,只要A <: A’,B’ <: B,下面的代码就是合法的:
class c is
method m(x:A):B is … end;
method m1(x1:A1):B1 is … end;
end;
ubclass c’ of c is.
折叠 区分方法
传统的基于类的面向对象语言的一个主要特点就是inheritance,subclassing和subtyping之间的密不可分的联系。很多的面向对象语言的语法,概念,就是从这三者而来的。比如说,通过subclassing,可以继承父类的一些方法,而同时又可以在子类中改写父类的方法。这个改写过的方法,通过subtyping、subsumption,又可以从一个类型是父类的对象去调用。
但是,inheritance,subclassing,subtyping这三者并不是永远和睦相处的。在一些场合,这三者之间的纠缠不清会妨碍到通过继承或泛型得到的代码重用。因此,人们开始注意到把这三者分离开来的可能性。区分subclassing和subtyping已经很常见了。而其它的一些方法还处于研究的阶段。
折叠 对象类型
在早期的面向对象语言中(如Simula),类型的定义是和方法的实现是混合在一起的。这种方式违反了今天已经被广泛认识到的把实现和规范(Specification) 分离的原则。这种分离得原则在开发是团队进行的时候尤其显得重要。
更近期一些的语言,通过引入不依赖于实现的对象类型来区分实现和规范。Modula-3以及其它如Java等的支持class和interface的语言都是采用的这种技术。
开始引入InstanceTypeOf(cell)时,它代表的概念相当有限。看上去,它似乎只表示用new cell生成的对象的类型,于是,并不能用它来表示从其它类new出来的对象。但后来,当引入了subclassing,method overriding,subsumption和dynamic dispatch之后,事情变得不那么简单了。的InstanceTypeOf(cell)已经可以用来表示从cell的子类new出来的对象,这些对象可以包括不是cell类定义的属性和方法。
如此看来,让InstanceTypeOf(cell)依赖于一个具体的类似乎是不合理的。实际上,一个InstanceTypeOf(cell)类型的对象不一定会跟class cell扯上任何关系。
它和cell类的唯一共同之处只是它具有了所有cell类定义的方法的签名(signature).
基于这种考虑,可以引入对象类型的语法:
针对cell类和reCell类的定义:
class cell is
var contents: Integer :=0;
method get(): Integer is
return self.contents;
end;
method set(n:Integer) is
elf.contents := n;
end;
end;
ubclass reCell of cell is
var backup: Integer := 0;
override set(n: Integer) is
elf.backup := self.contents;
uper.set(n);
end;
method restore() is
elf.contents := self.backup;
end;
end;
可以给出这样的对象类型定义:
ObjectType Cell is
var contents: Integer;
method get(): Integer;
method set(n:Integer);
end;
ObjectType ReCell is
var contents: Integer;
var backup: Integer;
method get(): Integer
method set(n: Integer);
method restore();
end;
这两个类型的定义包括了所有cell类和reCell类定义的属性和方法的类型,但却并不包括实现。这样,它们就可以被当作与实现细节无关的的接口以实现规范和实现的分离。两个完全无关的类c和c’,可以具有相同的类型Cell,而Cell类型的使用者不必关心它使用的是c类还是c’类。
注意,还可以加入额外的类似继承的语法来避免在ReCell里重写Cell里的方法签名。但那只是小节罢了。
折叠 分离
在上面的讨论中,subtype的关系是建立在subclass关系的基础上的。但如果想要让type独立于class,那么也需要定义独立于subclass的subtype.
在定义subtype时,又面临着几种选择:subtype是由类型的组成结构决定的呢?还是由名字决定呢?
由类型的组成结构决定的subtype是这样的:如果类型一具有了类型二的所有需要具备的属性和方法,就说类型一是类型二的subtype.
由类型名字决定的subtype是这样的:只有当类型一具有了类型二的所有需要具备的属性和方法, 并且类型一被明确声明为类型二的subtype时,才认可这种关系。
而如果的选择是一,那么那些属性和方法是subtype所必需具备的呢?哪些是可有可无的呢?
由组成结构决定的subtype能够在分布式环境和object persistence系统下进行类型匹配。缺点是,如果两个类型碰巧具有了相同的结构,但实际上却风马牛不相及,那就会造成错误。不过,这种错误是可以用一些技术来避免的。
相比之下,基于名字的subtype不容易精确定义,而且也不支持基于结构的subtype.
目前,可以先定义一个简单的基于结构的subtype关系:
对两个类型O和O’,
O’ <: O 当 O’ 具有所有O类型的成员。O’可以有多于O的成员。
例如:ReCell <: Cell.
为了简明,这个定义没有考虑到方法的特化。
另外,当类型定义有递归存在的时候(类似于链表的定义),对subtype的定义需要额外地加小心。
因为不关心成员的顺序,这种subtype的定义自动地就支持多重的subtype.
比如说:
ObjectType ReInteger is
var contents: Integer;
var backup: Integer;
method restore();
end;
那么,就有如下的subtype的关系:
ReCell <: Cell
ReCell <: ReInteger
(按,例子中没有考虑到象interface不能包含数据域这样的细节。实际上,如果支持对数据域的override,而不支持shadowing -- 作者的基于结构的subtype语义确实隐含着这样的逻辑― 那么,interface里包含不包含数据域就无关紧要了,因为令人头疼的名字冲突问题已经不存在了)
从这个定义,可以得出:
如果c’是c的子类, 那么ObjectTypeOf(c’) <: ObjectTypeOf(c)
注意,这个定义的逆命题并不成立,也就是说:
即使c’和c之间没有subclass的关系,只要它们所定义的成员符合了subtype的定义,ObjectTypeOf(c’) <: ObjectTypeOf(c)仍然成立。
回过头再看看在前面的subclass-is-subtyping:
InstanceTypeOf(c’) <: InstanceTypeOf(c) 当且仅当 c’是c的子类在那个定义中,只有当c’是c的子类时,ObjectTypeOf(c’) <: ObjectTypeOf(c)才能成立。
相比之下,已经部分地把subclassing和subtyping分离开了。Subclassing仍然是subtyping,但subtyping不再一定要求是subclassing了。
把这种性质叫做“subclassing-implies-subtyping”而不是“subclass-is-subtyping”了。
折叠 泛型
泛型 (Type Parameters)
一般意义上来说,泛型是一种把相同的代码重用在不同的类型上的技术。它作为一个相对独立于其它面向对象特性的技术,在面向对象语言里已经变得越来越普遍了。这里之所以讨论泛型,一是因为泛型这种技术本身就很让人感兴趣,另外,也是因为泛型是一个被用来对付二元方法问题(binary method problem) 的主要工具。
和subtyping共同使用,泛型可以用来解决一些在方法特化等场合由反协变带来的类型系统的困难。考虑这样一个例子:
有Person和Vegitarian两种类型,同时,有Vegitable和Food两种类型。而且,Vegitable <: Food
ObjectType Person is
…
method eat(food: Food);
end;
ObjectType Vegetarian is
…
method eat(food: Vegitable);
end;
这里,从常识,知道一个Vegitarian是一个人。所以,希望可以有Vegetarian <: Person.
不幸的是,因为参数是反协变的,如果错误地认为Vegetarian <: Person,根据subtype的subsumption原则,一个Vegetarian的对象就可以被当作Person来用。于是一个Vegetarian就可以错误地吃起肉来。
使用泛型技术,引入Type Operator (也就是,从一个类型导出另一个类型,概念上类似于对类型的函数)。
ObjectOperator PersonEating[F<:Food] is
…
method eat(food: F);
end;
ObjectOperator VegetarianEating[F<: Vegetable] is
…
method eat(food: F);
end;
这里使用的技术被称作Bounded Type Parameterization. (Trelli/Owl,Sather,Eiffel,PolyTOIL,Raptide以及Generic Java都支持Bounded Type Parameterization. 其它的语言,如C++,只支持简单的没有类型约束的泛型)
F是一个类型参数,它可以被实例化成一个具体的类型。类似于变量的类型定义,一个bound如F<:Vegitable限制了F只能被Vegitable及其子类型所实例化。所以,VegitarianEating[Vegitable],VegitarianEating[Carrot]都是合法的类型。而VegitarianEating[Beef]就不是一个合法的类型。类型VegitarianEating[Vegitable]是VegitarianEating的一个实例,同时它等价于类型Vegitarian. (用的是基于结构的subtype)
于是,有:
对任意F<:Vegitable,VegitarianEating[F] <: PersonEating[F]
对于原来的Vegitarian类型,有:
Vegetarian = VegetarianEating[Vegetable] <: PersonEating[Vegitable]
这种关系,正确地表达了“一个素食者是一个吃蔬菜的人”的概念。
除了Bounded Type Parameterization之外,还有一种类似的方法也可以解决这个素食者的问题。这种方法被叫做:Bounded Abstract Type请看这个定义:
ObjectType Person is
Type F<: Food;
…
var lunch: F;
method eat(food: F);
end;
ObjectType Vegetarian is
Type F<: Vegitable;
…
var lunch: F;
method eat(food: F);
end;
这里,F<:Food的意思是,给定一个Person,知道他能吃某种Food,但不知道具体是哪一种。这个lunch的属性提供这个Person所吃的Food.
在创建Person对象时,可以先选定一个Food的subtype,比如说,F=Dessert. 然后,用一个Dessert类型的变量赋给属性lunch. 最后再实现一个eat(food:Dessert)的方法。
这样,Vegetarian <: Person是安全的了。当把一个Vegetarian当作一个Person处理时,这个Vegitarian可以安全地吃他自带的午餐,即使不知道他吃的是肉还是菜。
这种方法的局限在于,Person,Vegitarian只能吃他们自带的午餐。不能让他们吃买来的午餐
所有编程语言都具有内部(或内置的)对象来创建 语言的基本功能。内部对象是 您编写自定义代码所用语言的基础, 该代码基于您的想象实现自定义功能。JavaScript 有许多 将其定义为语言的内部对象。本文介绍了一些 最常用的对象,并简要介绍了它们 有哪些功能以及如何使用这些功能。
Number
JavaScript Number
对象是 一个数值包装器。您可以将其与 new
关键词结合使用,并将其设置为一个稍后要在 JavaScript 代码中使用的变量:
var myNumber = new Number(numeric value);
或者,您可以通过将一个变量设置为一个数值来创建一个 Number
对象。然后,该变量将 能够访问该对象可用的属性和方法。
除了存储数值, Number
对象包含各种属性和 方法,用于操作或检索关于数字的信息。 Number
对象可用的所有属性 都是只读常量,这意味着它们的值始终保持 不变,不能更改。有 4 个属性包含在 Number
对象里:
MAX_VALUE
MIN_VALUE
NEGATIVE_INFINITY
POSITIVE_INFINITY
MAX_VALUE
属性返回 1.7976931348623157e+308
值,它是 JavaScript 能够处理的最大数字:
document.write(Number.MAX_VALUE);
// Result is: 1.7976931348623157e+308
另外,使用 MIN_VALUE
返回 5e-324
值,这是 JavaScript 中最小的数字:
document.write(Number.MIN_VALUE);
// Result is: 5e-324
NEGATIVE_INFINITY
是 JavaScript 能够处理的最大负数,表示为 -Infinity
:
document.write(Number.NEGATIVE_INFINITY);
// Result is: -Infinity
POSITIVE_INFINITY
属性是大于 MAX_VALUE
的任意数,表示为 Infinity
:
document.write(Number.POSITIVE_INFINITY);
// Result is: Infinity
Number
对象还有一些方法,您可以 用这些方法对数值进行格式化或进行转换。这些方法包括:
toExponential
toFixed
toPrecision
toString
valueOf
每种方法基本上执行如其名称所暗示的操作。例如, toExponential
方法以指数形式返回 数字的字符串表示。每种 方法的独特之处在于它接受的参数。 toExponential
方法有一个可选参数, 可用于设置要使用多少有效数字, toFixed
方法基于所传递的参数确定小数 精度, toPrecision
方法基于所传递的参数确定 要显示的有效数字。
JavaScript 中的每个对象都包含一个 toString
和 valueOf
方法,因此这些方法 在前面的章节中不介绍。 toString
方法返回 数字的字符串表示(在本例中),但是在其他对象中,它返回 相应对象类型的字符串表示。valueOf
方法返回调用它的对象类型的原始值,在本例中为 Number
对象。
仅 Number
对象似乎并不十分 强大,但它是任何编程语言的一个重要组成部分, JavaScript 也不例外。JavaScript Number
对象为任何 数学程序提供基础,这基本上是所有 编程语言的基础。
Boolean
Boolean
在尝试 用 JavaScript 创建任何逻辑时是必要的。Boolean 是一个 代表 true 或 false 值的对象。 Boolean
对象有多个值,这些值 相当于 false 值(0
、 -0
、null
或 ""
[一个空字串]),未定义的 (NaN
),当然还有 false。所有其他布尔 值相当于 true 值。该对象可以 通过 new
关键词进行实例化,但通常是 一个被设为 true 或 false 值的变量:
var myBoolean = true;
Boolean
对象包括 toString
和 valueOf
方法,尽管您不太可能需要使用这些方法。 Boolean
最常用于在 条件语句中 true 或 false 值的简单判断。 布尔值和条件语句的组合提供了一种使用 JavaScript 创建逻辑的方式。此类条件语句的示例包括 if
、if...else
、 if...else...if
以及 switch
语句。当与 条件语句结合使用时,您可以基于 您编写的条件使用布尔值确定结果。清单 1 显示了 条件语句与布尔值相结合的一个简单示例。
清单 1. 与布尔值相结合的条件语句
var myBoolean = true;
if(myBoolean == true) {
// If the condition evaluates to true
}
else {
// If the condition evaluates to false
}
不言而喻,Boolean
对象 是 JavaScript 一个极其重要的组成部分。如果没有 Boolean 对象, 在条件语句内便无法进行判断。
String
JavaScript String
对象是 文本值的包装器。除了存储文本, String
对象包含一个属性和各种 方法来操作或收集有关文本的信息。与 Boolean
对象类似, String
对象不需要进行实例化 便能够使用。例如,您可以将一个变量设置为一个字符串, 然后 String
对象的所有属性或 方法都可用于该变量:
var myString = "My string";
String
对象只有一个 属性,即 length
,它是 只读的。length
属性可用于只返回 字符串的长度:您不能在外部修改它。随后的代码 提供了使用 length
属性确定一个字符串中的字符数的示例:
var myString = "My string";
document.write(myString.length);
// Results in a numeric value of 9
该代码的结果是 9
,因为 两个词之间的空格也作为一个字符计算。
在 String
对象中有相当多的方法可用于操作和收集有关文本的信息。 以下是可用的方法列表:
charAt
charCodeAt
concat
fromCharCode
indexOf
lastIndexOf
match
replace
search
slice
split
substr
substring
toLowerCase
toUpperCase
chartAt
方法可用于基于您作为参数传递的索引检索 特定字符。 下面的代码说明了如何返回 字符串的第一个字符:
var myString = "My string";
document.write(myString.chartAt(0);
// Results in M
如果您需要相反的结果,有几个方法 可返回字符串中的指定字符或字符集,而不 使用索引返回字符。这些方法包括 indexOf
和 lastIndexOf
,这两个方法都包含两个 参数:searchString
和 start
。 searchString
参数是起始索引, start
参数告诉方法 从哪里开始搜索。这两个方法之间的区别在于, indexOf
返回第一个索引, lastIndexOf
返回最后一个索引。
charCodeAt
方法类似于 charAt
:惟一的区别在于它返回 Unicode 字符。另一种与 Unicode 相关的方法(包括在 String
对象中)是 fromCharCode
,它将 Unicode 转换为 字符。
如果您想要组合字符串,可以使用加号 (+
) 将这些字符串加起来,或者您可以 更适当地使用 concat
方法。该 方法接受无限数量的字符串参数,连接它们,并 将综合结果作为新字符串返回。
var myString1 = "My";
var myString2 = " ";
var myString3 = "string";
document.write(myString.concat(myString1, myString2, myString3);
// Results in "My String"
还有一组 String
方法 接受正则表达式作为一个参数,以查找或修改一个字符串。 这些包括 match
、 replace
和 search
方法。match
方法使用正则 表达式搜索特定字符串并返回所有的匹配的字符串。 replace
方法实际上接受子字符串或 正则表达式和替换字符串作为其第二个参数, 用替换字符串更换所有匹配项,并返回更新的 字符串。这些方法的最后一个是 search
方法,它搜索正则表达式的匹配结果并返回其 位置。
如果需要修改字符串,有多个方法派得上用场。 第一个方法是 slice
方法,它基于索引或 索引的开始和结尾的组合提取 并返回一个字符串的一部分。另一个方法是 split
方法。 split
方法每当找到分隔符参数时就将一个字符串分割成一系列 子字符串。例如,如果将逗号 (,
) 作为一个参数传递,那么字符串 将在每个逗号处分割成一个新的子字符串。能够修改字符串的方法还包括 substr
方法,它 基于指定为参数的起始位置和长度,从字符串提取字符, 还有 substring
方法,该方法基于指定为参数的两个索引从一个字符串提取字符。能够改变字符串的最后的方法分别是 toLowerCase
和 toUpperCase
,它们将字符串中的字符分别转换为 小写和大写字母。这些方法在 比较字符串值时非常有用,因为字符串有时可能 大小写不一致。这些方法确保您在比较 值,而不是大小写。
Date
JavaScript Date
对象提供了一种方式 来处理日期和时间。您可以用许多不同的 方式对其进行实例化,具体取决于想要的结果。例如,您可以在没有参数的情况下对其进行实例化:
var myDate = new Date();
或传递 milliseconds
作为一个参数:
var myDate = new Date(milliseconds);
您可以将一个日期字符串作为一个参数传递:
var myDate = new Date(dateString);
或者您可以传递多个参数来创建一个完整的日期:
var myDate = new Date(year, month, day, hours, minutes, seconds, milliseconds);
此外,有几种方法可用于 Date
对象,一旦该对象 得到实例化,您便可以使用这些方法。大多数可用的方法围绕 获取当前时间的特定部分。以下方法是 可用于 Date
对象的 getter 方法:
getDate
getDay
getFullYear
getHours
getMilliseconds
getMinutes
getMonth
getSeconds
getTime
getTimezoneOffset
如您所见,每个方法所 返回的值都相当简单。区别在于所返回的值范围。例如, getDate
方法返回 一个月份的天数,范围从 1 到 31;getDay
方法返回每周的天数,范围从 0 到 6; getHours
方法返回小时数值, 范围从 0 到 23;getMilliseconds
函数返回毫秒数值,范围从 0 到 999。 getMinutes
和 getSeconds
方法返回一个范围从 0 到 59 的值,getMonth
方法返回一个 从 0 到 11 之间的月份数值。本列表中惟一独特的方法 是 getTime
和 getTimezoneOffset
。 getTime
方法返回 自 1/1/1970 中午 12 点的毫秒数,而 getTimezoneOffset
方法返回 格林尼治标准时间和本地时间之间的时间差,以分钟为单位。
对于大多数 getter 方法,还有一个 setter 方法,接受 相应的值范围内的数值参数。setter 方法 如下所示:
setDate
setFullYear
setHours
setMilliseconds
setMinutes
setMonth
setSeconds
setTime
对于上述所有 getter 方法,有一些匹配的方法 返回相同的值范围,只是这些值以 国际标准时间设置。这些方法包括:
getUTCDate
getUTCDay
getUTCFullYear
getUTCHours
getUTCMilliseconds
getUTCMinutes
getUTCMonth
getUTCSeconds
当然,由于对于所有原始 getter 方法都有 setter 方法, 对于国际标准时间也一样。这些方法包括:
setUTCDate
setUTCFullYear
setUTCHours
setUTCMilliseconds
setUTCMinutes
setUTCMonth
setUTCSeconds
正如在本文开头提到的,我不提供许多 关于 toString
方法的信息,但是 在 Date
对象中有一些方法可将日期转换为一个字符串,值得一提。在某些 情况下,需要将日期或日期的一部分转换为一个 字符串。例如,如果您将其追加到一个字符串或在 比较语句中使用它。有几个方法可用于 Date
对象,提供略微不同的 方法将其转换成字符串,包括:
toDateString
toLocaleDateString
toLocaleTimeString
toLocaleString
toTimeString
toUTCString
toDateString
方法将日期转换为 字符串:
var myDate = new Date();
document.write(myDate.toDateString());
toDateString
返回当前日期, 格式为 Tue Jul 19 2011。
toTimeString
方法将时间从 Date
对象转换为字符串:
var myDate = new Date();
document.write(myDate.toTimeString());
toTimeString
将时间作为字符串返回, 格式为 23:00:00 GMT-0700 (MST)。
最后一种将日期转换为字符串的方法是 toUTCString
,它将日期转换为 国际标准时间的字符串。
有几种方法使用区域设置将日期转换成字符串,但是在撰写本文之时 Google Chrome 还不支持这几种方法。不支持的方法 包括 toLocaleDateString
、 toLocaleTimeString
和 toLocaleString
。
JavaScript Date
对象乍看起来似乎很简单, 但是它不仅仅是一种显示 当前日期的有用方式。它取决于您要创建的功能。 例如,Date
对象是 创建倒计时钟表或其他与时间相关的功能的基础。
Array
JavaScript Array
对象是一个存储变量的变量:您可以用它一次在一个变量中存储多个值, 它有许多方法允许您操作或收集 有关它所存储的值的信息。尽管 Array
对象不差别对待值类型,但是 在一个单一数组中使用同类值是很好的做法。因此, 在同一数组中使用数字和字符串不是好的做法。所有 可用于 Array
对象的属性 都是只读的,这意味着它们的值不能从外部予以更改。
可用于 Array
对象的惟一属性 是 length
。该属性返回 一个数组中的元素数目,通常在使用 循环迭代数组中的值时用到:
var myArray = new Array(1, 2, 3);
for(var i=0; i<myArray.length; i++) {
document.write(myArray[i]);
}
有多种方法可用于 Array
对象,您可以使用各种方法来向数组添加元素,或从数组删除元素。 这些方法包括 pop
、 push
、shift
和 unshift
。pop
和 shift
方法都从 数组中删除元素。pop
方法删除并返回 一个数组中的最后一个元素,而 shift
方法删除并返回一个数组中的第一个元素。相反的 功能可以通过 push
和 unshift
方法实现,它们将元素添加到 数组中。push
方法将元素作为新元素添加到 数组的结尾,并返回新长度,而 unshift
方法将元素添加到 数组的前面,并返回新长度。
在 JavaScript 中对数组进行排序可以通过两个方法实现,其中之一 实际上称为 sort
。另一个方法是 reverse
。sort
方法的复杂之处在于,它基于可选的 sort
函数排列数组。 sort
函数可以是 您编写的任何自定义函数。reverse
方法不像 sort
那样复杂,尽管它的确通过颠倒元素更改 数组中元素的顺序。
在处理数组时,索引非常重要,因为它们定义 数组中每个元素的位置。有两个方法可基于索引更改 字符串:slice
和 splice
。slice
方法接受索引或 索引开始和结尾的组合作为参数,然后提取数组的一部分并基于参数将其作为 新数组返回。splice
方法包括 index
、 length
和 unlimited element
参数。该方法基于指定的索引将 元素添加到数组,并基于指定的索引将元素从 数组中删除,或基于指定的索引将元素添加到数组或从 数组删除元素。还有一种方法 可以基于匹配值返回一个索引: indexOf
。然后您可以使用该索引截取 或拼接数组。
用任何编程语言编写好代码的关键是编写 有条理的代码。正如其各种方法所示, JavaScript Array
对象是一种 组织数据并创建复杂功能的强大方式。
Math
JavaScript Math
对象用于执行 数学函数。它不能加以实例化:您只能依据 Math
对象的原样使用它,在没有任何实例的情况下从该对象调用属性和 方法:
var pi = Math.PI;
Math
对象有许多属性和方法 向 JavaScript 提供数学功能。所有的 Math
属性都是只读常量, 包括以下各项:
E
LN2
LN10
LOG2E
LOG10E
PI
SQRT1_2
SQRT2
E
属性返回 自然对数的底数的值,或欧拉指数。该值是惟一的 实数,以 Leonhard Euler 命名。调用 E
属性会产生数字 2.718281828459045。其他两个属性也用于返回自然 对数:LN2
和 LN10
。LN2
属性返回值为 2 的自然对数,而 LN10
属性返回值为 10 的自然 对数。LOG2E
和 LOG10E
属性可用于返回 E
以 2 或 10 为底的对数。 LOG2E
的结果是 1.4426950408889633,而 LOG10E
的结果是 0.4342944819032518。通常您不需要 大部分这些属性,除非您正在构建 计算器或其他数学密集型项目。然而, PI
和平方根比较常见。 PI
方法返回圆周与直径的比率。两个属性返回平方根值: SQRT1_2
和 SQRT2
。 第一个属性返回 0.5 的平方根,而 SQRT2
返回 2 的平方根。
除了这些属性,还有几种方法可用来 返回一个数的不同值。其中每种方法都接受 数值,并根据方法名称返回一个值。 遗憾的是,方法名称不总是显而易见的:
abs
。一个数的 绝对值acos
。反余弦asin
。反正弦atan
。反正切atan2
。多个数的 反正切cos
。余弦exp
。幂log
。一个数的自然 对数pow
。x 的 y 次方值sin
。正弦sqrt
。平方根tan
。一个角的 正切
有三种方法可用于在 JavaScript 中取整数: ceil
、floor
和 round
。ceil
方法返回一个数的向上舍入值。该方法在 您需要将数字向上舍入到最接近的整数时非常有用。 floor
方法提供 与 ceil
相反的功能:它返回 一个数字的向下舍入值。该方法在需要 将数字向下舍入到最近的整数时非常有用。 round
方法提供了普通的四舍五入 功能,基于现有的 小数将数字向上或向下舍入。
Math
对象中包括的最后三个方法分别是 max
、min
和 random
。max
方法接受多个数字参数并返回最高值, 而 min
方法接受多个数字 参数并返回最低值。这些方法在 比较拥有数值的变量时非常有用,特别是当您事先不 知道是什么数值时。您使用 random
方法返回 0 与 1 之间的一个随机数。您可以将该方法用作多种目的,比如在 网站主页上显示一个随机图像,或返回一个随机数, 该随机数可用作包含图像的文件路径的数组的一个索引。 从该数组选择的随机图像文件路径然后可 用于将该图像写到 HTML <img>
标记