今天我们继续讨论 CStudents 集合类,讨论 CStudents 的优化。我们的 CStudents 类是一个集合类,它的作用就是保持、存有 CStudent 的对象。所以我们在内部选择数组来保持、存有 CStudent 对象。虽然 CStudents 内部实现是数组,但呈现给外部的接口(属性和方法)则是集合类所通用的一些属性和方法,譬如 Add 和 Delete 方法用来添加和删除集合元素,Count 用来获知集合中元素个数,Item 用来获取集合元素等。集合类的最大特点是元素数量不定,可以随意增长和减少。我们在内部实现中充分利用了动态数组的 ReDim Preserve 功能,在保持数组原有元素不变的情况下增加或减少数组的存储空间。在计算机的所有操作中,分配内存(不管是增加还是减少)几乎是最昂贵的操作。所以,提高程序性能的手段之一就是尽可能减少分配内存的操作。在 CStudents 集合类中,内存分配的操作就是 Add 和 Delete 方法中的动态增减数组空间的 ReDim Preverse 操作。所以,要减少 CStudents 类中分配内存的操作,就要减少 ReDim Preserve 操作。仔细分析 CStudents 的实现,发现有个致命的缺陷:每添加或删除一个元素,都要进行一次 ReDim Preserve!假设要增减 1000、10000 个元素,就要进行 1000、10000 次的 ReDim Preserve 操作!毫无疑问,这样的实现成本太高,但这个实现为我们的优化提供了前提和基础。下面就来研究如何优化 CStudents 内存空间动态增减的问题。如果说“每添加或删除一个元素都要进行一次 ReDim Preserve 操作”性能太低的话,很自然就会想到:每添加或删除 n 个元素进行一次 ReDim Preserve 操作,不就可以减少内存分配次数了吗?譬如要添加 1000 个元素,如果我们一次性分配 1000 个元素的数组空间,就只进行一次内存分配,性能自然提升。问题是:集合中要包含多少个元素是不可预知的!如果我们只是一次性分配 1000 个元素,但如果集合只包含 10、20 个元素,就会造成巨大的空间浪费。所以,我们的优化就是确定上面提到的 n:每添加或删除 n 个元素再进行一次 ReDim Preserve 操作。在讨论如何优化之前,先介绍一下动态缩减数组空间的 ReDim 语句。我们在用 Dim 声明动态数组时,只是声明了数组变量的类型,并没有对数组进行空间分配,也就是说,对于下面的代码:只是告诉 VBA 编译器:myA 是一个动态数组,其元素类型是 Object。再没有其它信息。只有再经过 ReDim 之后,数组 myA 才有了真实的存在:ReDim myA(10) ' Allocate 11 elements
上面的 ReDim 语句为数组 myA 分配 11 个元素的内存空间。注:这里的 10 是指数组 myA 的上界,即 UBound 的值,因为我们假定数组的下界 LBound 是 0,即假定了 Option Base 0,所以上面的 ReDim 语句给 myA 数组分配了 11 个元素的空间。如果数组 myA 已经分配了空间,并且元素有了值,再次进行 ReDim 操作时,myA 中原有的值将会丢失。如果要保持原有的值,则使用 ReDim Preserve:ReDim Preserve myA(25) ' Enlarge myA space to include ' additional 15 elements
这行语句在保持 myA 原有 11 个元素的前提下,将 myA 的空间扩大到 26 个元素。假设经过一段时间后,myA 中装满了 26 个元素,现在要缩小 myA 的空间:执行这行语句后,myA 就缩小到只包含 21 个元素,最后的 5 个元素就被丢弃了。总结上面的例子,ReDim Preserve 在保持数组原有元素的条件下,对数组空间进行扩大或缩小。到底是扩大还是缩小数组空间,完全取决于当前 ReDim Preserve 语句中数组名称后面所带参数小于还是大于数组当前上界:若参数小于当前数组上界,则缩小数组空间;若大于当前数组上界,则扩大数组空间。其中,方括号 [] 表示其中的内容是可选的。上面我们已经说过有和没有 Preserve 的区别。如果没有 As type,则数组 arrname 元素的类型不变。一般来讲也不允许在 ReDim 语句中改变数组的类型,但如果用 Dim 声明的数组是 Variant 类型,则可以在 ReDim 语句中使用 As type 改变数组的类型。lower 表示数组的下界(LBound),upper 表示数组的上界(UBound)。如果动态数组是多维数组,则 ReDim 只能改变数组最后一维的大小。VBA 的数组最多可以到 60 维。从 subscripts 的一般格式可以看出,动态数组可以使用 ReDim 语句同时改变下界和上界。我们前面的例子只是更改数组的上界,这更符合一般的应用场景。有了以上的准备,下一篇就可以讨论 CStudents 动态数组内存分配的优化方案了。