【程序设计】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 函数会导致不断地创建和搬动存储空间(当然实际上不会笨到每次都重建,会一次性创建得大一点,不够了再创建新的),导致运算性能低下,要习惯在代码避免。
这个原则对任何程序语言都是适用的。