【程序设计】4.4 [排成队] 理解对象

 

4.4 理解对象

我们先看一段例子代码:


A

B

1

=4

=A1

2

=[3,4,12,4,6,9,3,5]

=A2

3

>B1=3

=A1

4

>B2(2)=0

=A2(2)

A1 的值被赋给了 B1,然后在 A3 中把 B1 改掉,再看 A1,会发现它没有变,还是开始的值,也就是说 B1 格中的 =A1,是将 A1 复制了一份,和原来的 A1 无关了。

同样,把 A2 的值赋给了 B2,然后在 A4 中的把 B2 的一个成员改掉了,现在我们再看原来 A2 的这个成员,竟然也被改掉了!这说明序列的赋值和整数不一样,在 B2 中的赋值并没有把 A2 复制一份,A2 和 B2 实际上是同一个东西。

和整数、浮点数这些单个的值不同,序列是由一批值构成的复杂数据类型,这种数据在计算机中会占用较大的空间来存储,不适合直接存储在变量自己占据的存储空间中,需要专门的存储空间并被统一管理。

计算机会给每一片存储空间编个号,以后拿着这个号就能找到这片空间,这个号也就叫做空间的地址,和我们日常中用到的地址很像。对于序列这种复杂数据类型,其变量中存储的并不是数据本身,而只是它的地址。

存储空间的地址,在很多计算机资料中也称为指针

回到上面的代码,B2 的赋值语句中仅仅是复制了序列 A2 的地址,而没有复制值。执行完之后,A2 和 B2 存储了同一个序列的地址。这时候改变 B2 的成员,也就是改变了那个序列的成员,那么 A2 也会看到这个改变。同样地,如果改变 A2 的成员,那么从 B2 也能看到变化。

而整数这些简单值,在赋值时被完全复制了,再改变其中某个就和另一个没有关系了。

计算机界还习惯于把序列这类复杂的数据类型称为对象。我们在后面还会碰到更多类型的对象。

一种对象通常有许多与之相关的函数。对于以对象 x 为主要参数的函数 f,当然仍可以把它写成传统的 f(x,…) 形式,但为了表明 f 是与 x 相关性很强,我们经常会把它写成 x.f(…) 的形式,这时候也称 f 是对象 x 的函数方法。比如能返回序列 x 的长度的函数 x.len()。

SPL 中有大量的函数会写成这种对象的形式。

在前面的代码中,我们要使用一个序列时,需要知道这个序列的最大长度,先造一个确定长度的序列放着,在程序执行过程再填入成员。这其实有点不方便,我们并不是任何时候都能知道在程序中用到的序列有多长(比如前面例子中水仙花数的个数,也许 100 个还不够),如果造长了就浪费空间,造短了又不够会导致程序出现溢出错误。

其实,对于序列这种对象,SPL 提供了一些方法用来改变它的长度,确切地说,就是可以插入和删除成员。


A

1

=[1,2,3,4,5,6,7,8,9]

2

>A1.insert(1,0)

3

>A1.delete(5)

4

>A1.insert(0,10)

5

>A1.insert(8,78)

执行这段程序,观察 A1 的变化。

A.insert(i,x) 将在序列 A 的第 i 个位置前插入一个成员 x,原来第 i,i+1,…个成员会依次向后移;如果 i 是 0,则将插入到最后去,也就是追加一个成员,A.insert(…) 会使序列长度增加。A.delete(i) 则将删除第 i 个成员,序列长度减少。

我们用 insert 函数改写水仙花数的程序:


A

B

C

D

E

F

1

=0

=[]





2

for 9

=100*A2

=A2*A2*A2




3


for 0,9

=B2+10*B3

=C2+B3*B3*B3

if C3<D3

break

4



for 0,9

=C3+C4

=D3+C4*C4*C4


5




if D4==E4

>B1.insert(0,D4)


6




else if D4<E4

break


在 B1 造一个空序列 [],然后每发现一个水仙花数则在 E5 追加一个成员,最后 B1 就是所有水仙花数构成的序列,有几个水仙花数,其长度就是几,不再有浪费的空位置了。

帕斯卡三角也可以类似实现:


A

B

C

1

5

=[[1],[1,1]]


2

for 3,A1+1

=[1,1]

>B1.insert(0,[B2])

3


for 2,A2-1

>B2.insert(B3,B1(A2-1)(B3-1)+B1(A2-1)(B3))

B1 先初始化前两行。在每一行的循环中,在 B2 中生成一个只有头尾的序列,追加到 B1 最后,然后再用循环在 B2 插入中间值完成 B2 的计算。

注意 C2 格中向 B1 追加 B2 的动作,当增加一个序列成员进入一个二层序列时,要在参数上多加一层 [],即这里的[B2]。这是因为,insert 函数其实允许一次性将一个序列插入到原序列中去,A.insert(i,x) 在 x 也是序列时,将会把 x 的成员依次插入到 A 的第 i 个成员之前(如果 i 是 0 则追加到最后)。要把序列 x 当成一个整体插入时(本题的情况),就需要多加一层[],作为一个长度为 1 的序列插入进去,否则 insert 函数将把 x 拆开依次插入进去。

再说点题外话。

实际上,计算机程序里的存储空间一旦创建出来,其大小是固定而不能改变的。insert 函数能改变序列的长度,背后的动作是这样的:重新创建一块更大的存储空间,把原先的序列成员依次抄进去,然后再把原来的空间丢弃(交还给操作系统)。

可想而知,insert 函数会是一个非常慢的动作,频繁地使用 insert 函数会导致不断地创建和搬动存储空间(当然实际上不会笨到每次都重建,会一次性创建得大一点,不够了再创建新的),导致运算性能低下,要习惯在代码避免。

这个原则对任何程序语言都是适用的。

【程序设计】 前言及目录
【程序设计】4.3 [排成队] 多层序列
【程序设计】5.1 [一把抓] 集合运算