前面我们讨论了赋值语句,在赋值号(=)两边的类型不相同时,VBA 会自动将右边表达式求值的结果转换为左边期望的类型,这个“自动转换过程”称为隐式类型转换。如果转换成功,则将右边的值赋给左边的变量,如果不能转换为左边所期望的类型,VBA 报告“类型不匹配”的错误。今天我们接着昨天的内容,继续讨论赋值语句的类型转换。VBA 除了隐式类型转换之外,还有显式类型转换(explicitly conversion)。所谓显式类型转换,就是程序员主动进行的类型转换。VBA 中的隐式类型转换太宽泛,容易导致问题。譬如:Dim xL As LongxL = 300 * 300 ' Error: Overflow!
上面代码中的表达式 300 * 300 会导致“溢出”(Overflow)的运行时错误。VBA 在看到 300 这个数时,会自动将其转换为 Integer 类型,两个 Integer 类型的数相乘,结果也是 Integer。但因为 300 * 300 = 90000,超出了 Integer 所能表示的范围(Integer 所能表示的范围是:-32768 ~ 32767),因此导致溢出错误。你自然会问:赋值号左边的不是 Long 吗?右边相乘的结果应该转换为 Long 才对啊。为什么会转换为 Integer 呢?答案也许是:VBA 没那么聪明,因为左边是 Long,所以会将右边的表达式都转换为 Long。不,VBA 首先分析右边的表达式。它看到一个数 300,因为 Byte 无法表示 300,而 Integer 可以,所以把 300 的类型转换为 Integer,两个 Integer 类型的操作数相乘,乘积也是 Integer。溢出发生在 Integer * Integer => Integer 这一步。如果这一步没有发生溢出,譬如 30 * 30。VBA 会将乘积 900 的类型转换为 Long,然后赋给左边的变量。Dim xL As LongxL = CLng(300) * 300
CLng 是类型转换函数,CLng(300) 是将 300 这个 Integer 类型的数转换为 Long 类型(长整型)。长整型是 32 位整数,能表示 300 * 300 的结果。我们已经讲过,二元表达式运算符两边的操作数类型要一致,不一致的话,VBA 会自动进行类型转换。在表达式 CLng(300) * 300 中,乘号(*)左边的操作数类型是 Long,右边的类型是 Integer,VBA 会自动将右边操作数的类型转换为 Long。所以,在 300 * 300 这个表达式中,只需要将其中一个 300 的类型显式转换为 Long,VBA 会自动将另一个也转换为 Long。为什么 VBA 会自动将 Integer 转换为 Long,而不是将 Long 自动转换为 Integer?在 VBA 的隐式类型转换规则中,有一条是:自动将低阶类型转换为高阶类型。如:- Byte -> Integer -> Long -> LongLong
Byte 是 8 位整数类型,Integer 是 16 位整数类型,Integer 类型所能表示的范围比 Byte 要大。因此 Integer 相对 Byte 来说是高阶类型,而 Byte 则是低阶类型。所谓“低阶类型”、“高阶类型”,是为了易于理解才这样说的,准确的说法是窄域类型(narrow type)、宽域类型(wide type)。宽窄是相对的。8 位的 Byte 相对于 16 位的 Integer 来说自然是窄类型,而 Integer 相对于 Byte 来说肯定是宽类型,但相对于 32 位的 Long 来说又是窄类型。其他类似。隐式类型转换,也就是 VBA 自动完成的类型转换,都是将低阶类型转换为高阶类型。将类型从低阶转换为高阶不会损失精度,因为高阶类型所能表示的值域比低阶类型所能表示的值域要宽广(这就是为什么高阶类型被称为“宽域类型”的原因)。譬如,Byte 只能表示 0 ~ 255 之间的值,而 Integer 则能表示 -32768 ~ 32767 之间的值。所以,一般来讲,隐式类型转换是安全的。但也会造成问题,如我们上面的例子所表明的。因此,尽量少使用隐式类型转换,在需要类型转换的时候,尽量使用 Cxx 函数来表达显式类型转换,如 xL = CLng(300) * 300。类型转换函数,除了上面见过的 CLng 之外,还有 CByte,CInt,CLngLng,CSng,CDbl 等。下面举例说明这些函数的用法:Dim xB As ByteDim xI As IntegerDim xLL As LongLongDim xS As SingleDim xD As DoublexB = CByte(128)xI = CInt(xB)xLL = CLngLng(xI)xS = CSng(3.56789)xD = CDbl(xS)xB = CByte(xD)xI = CInt(xD)
在上面的例子中,128 是一个 0 ~ 255 之间的数,因此 CByte(128) 可以成功实现类型转换,CInt(xB) 和 CLngLng(xI) 都可以。3.56789 也是一个单精度浮点类型能够表示的值,因此 CSng(3.56789) 可以成功实现类型转换,而 CDbl(xS) 将单精度 xS 转换为值域更加宽广的双精度类型自然也能成功转换。最后两行的转换,不是从低阶向高阶的转换,而是从高阶向低阶的转换。从高阶向低阶转换时,如果高阶的值能够用低阶类型来表示,转换就能成功实现,如果不能用低阶类型来表示,则会发生“溢出”错误,说明高阶的值太大或太小,无法在低阶类型中表示。如下图所示:上图中,双精度 xD 就不能转换为单精度的值,因为 xD 的值超出了单精度类型所能表示的值域,从而产生“溢出”错误。在计算机工业草创期的 1960-1970 年代,世界上并没有统一的浮点数表示法,浮点数的表示往往随机器而不同,这给程序的开发带来了很大的困难。一直到 1985 年,美国电气与电子工程师学会(IEEE)才制定了第一个浮点数表示标准——IEEE 754-1985。从此,业界有了统一的浮点数表示法。简单地说,IEEE 754 将浮点数类型分为两大类:单精度浮点数,用 32 位即 4 个字节表示,以及双精度浮点数,用 64 位即 8 个字节表示。IEEE 754 的最新版本是 IEEE 754-2019,新标准又增加了其他的表示法。VBA 只支持单精度(Single)和双精度(Double),所以其他我们就略去不谈了。浮点数的一个重要问题是取整(rounding),我们从小学算术中学到了“四舍五入”,那只是取整的一种方法。IEEE 754 定义了 5 种取整方式,第一种与我们熟悉的“四舍五入”比较接近,叫“四舍五入,逢五取偶”,VBA 中的取整就采用了这种方式。如图3:CInt 在将浮点类型转换为整型时,要对浮点数进行取整操作。如图,在将 11.5 取整时,四舍五入,.5 进位,取整为 12。而在对 12.5 取整时,虽然要舍去的位满足“五入”,但,因为整数的最低位是偶数(2),所以此时并不进位,而是直接舍去,故取整后的值也是 12。对负数的处理也是一样。这种取整操作不仅在 CInt 转换函数中存在,在浮点数保留小数位的问题中也存在。但不同的操作会导致不同的结果,见下图:FormatNumber 和 Round 函数的基本功能都是将浮点数“取整”为所要求的小数位数。上图的例子是保留 2 位小数位。但 FormatNumber 将取整后的结果转换为字符串,Round 则维持原来的类型不变。由上图也可以看出,FormatNumber 和 Round 的“取整”策略是不同的:FormatNumber 严格按照“四舍五入”的规则行事,而 Round 则按照 IEEE 754 标准所规定的“四舍五入,逢五取偶”的规则行事。