标题: VBS中&H前缀十六进制数的陷阱
作者: Demon
链接: https://demon.tw/programming/vbs-hex-constant.html
版权: 本博客的所有文章,都遵守“署名-非商业性使用-相同方式共享 2.5 中国大陆”协议条款。
16进制在编程中是很普遍的,在VBScript中,以“&H”前缀来表示十六进制数。
例如:
'Author: Demon 'Website: https://demon.tw 'Date: 2012/5/7 Const cr = ChrW(&HD) 'Carriage return Const lf = ChrW(&HA) 'Line feed Const HKCR = &H80000000 'HKEY_CLASSES_ROOT Const HKCU = &H80000001 'HKEY_CURRENT_USER Const HKLM = &H80000002 'HKEY_LOCAL_MACHINE Const HKU = &H80000003 'HKEY_USERS Const HKCC = &H80000005 'HKEY_CURRENT_CONFIG Const Normal = &H000 'Normal file Const ReadOnly = &H001 'Read-only file Const Hidden = &H002 'Hidden file Const System = &H004 'System file Const Volume = &H008 'Disk drive volume label Const Directory = &H010 'Folder or directory Const Archive = &H020 'File has changed since last backup Const Alias = &H400 'Link or shortcut Const Compressed = &H800 'Compressed file
当这些以&H为前缀的十六进制值比较小或者仅用于位运算时,一切都很正常。但是当它们的值比较大时并且参与运算时,噩梦就开始了:
问题代码一:
'Author: Demon 'Website: https://demon.tw 'Date: 2012/5/7 For i = 0 To &HFFFF WScript.Echo ChrW(i) Next
这段代码的本意是输出0到65535之间的Unicode字符,但是运行后却没有任何输出。
问题代码二:
'Author: Demon 'Website: https://demon.tw 'Date: 2012/5/7 uni = 55401 ' &HD800 = 55296 &HDBFF = 56319 If (uni >= &HD800) And (uni <= &HDBFF) Then WScript.Echo uni 'do something End If
这是一个条件判断语句,看似符合条件,结果还是没有任何输出。
问题代码三:
'Author: Demon 'Website: https://demon.tw 'Date: 2012/5/7 WScript.Echo AscW("魔") WScript.Echo MyAscW("魔") Function MyAscW(s) Dim n n = AscW(s) If n < 0 Then n = n + &HFFFF End If MyAscW = n End Function
如果字符的代码点(code point)大于32,767,那么AscW函数会返回负数(详见《AscW函数返回负数的问题》)。自定义函数MyAscW的本意是AscW的改进版,但是依然返回了负值。
例子就举那么多,不知道你看出问题所在了吗?没看出来也不要紧,让我们一起来终结这个噩梦。
首先复习一下基础知识,VBS中的整数类型分为Integer和Long,Integer的范围是-32,768到32,767,Long的范围是-2,147,483,648到2,147,483,647。可以知道,Integer是16位的,Long是32位的,并且两者都是有符号数。
当&H前缀十六进制数比较小时,也就是&H0000到&H7FFF时,正好处于Integer正数的范围之内。但是&H7FFF的下一个数&H8000(十进制32768)超出了Integer所能表示的正数,这时候就会变成-32768,为什么呢?
十六进制的8000转成二进制是1000000000000000,16位中除了最高位是1以外,其他位都是0。最高位是1,你想到了什么?没错,因为Integer是有符号数,所以最高位是符号位,0表示正数,1表示负数,所以得到的值当然是负数。于是从&H8000到&HFFFF之间的十六进制数都是负数,依次为-32768到-1。
数字继续增大,&HFFFF的下一个数&H10000已经不能用16位表示了,此时就用32位的Long表示,原理与Integer一样。所以从&H10000到&H7FFFFFFF都是正数,对应32,768到2,147,483,647;&H80000000到&HFFFFFFFF都是负数,对应-2,147,483,648到-1。
现在32位都已经用完了,VBS没有64位的整数,所以&H100000000及其之后的数会报错:Microsoft VBScript compilation error: Syntax error。
OK,原理讲完了,现在看上面的代码应该知道错在哪里了。那么怎么解决呢,难道所有用&H十六进制数参与运算的地方都要改成十六进制不成?
这的确是解决方法之一,只不过如果数值比较大的话代码会很长很难看而已,而且十进制看起来没有十六进制那么直观。
这里有一个我无意中搜索到的undocumented的技巧,那就是在&H十六进制数后面再加上一个&,强制转换成Long类型,这应该是从VB那里继承下来的吧。
'Author: Demon 'Website: https://demon.tw 'Date: 2012/5/7 x = &HFFFF y = &hFFFF& WScript.Echo x, TypeName(x) WScript.Echo y, TypeName(y)
顺便提醒一句,这个技巧对于&H80000000到&HFFFFFFFF是没有用的,因为它们本身就已经是Long了。
最后的最后,说一个很奇怪的地方,在&H0000到&HFFFF范围内,除了&H8000(-32768)之外,其他数的类型(用TypeName函数)都是Integer,而&H8000的类型却是Long:
'Author: Demon 'Website: https://demon.tw 'Date: 2012/5/7 x = &H8000 WScript.Echo x, TypeName(x)
可如果它是Long的话,应该是32768才对啊,为什么还会是负值呢?百思不得其解,如果哪位高人知道,请留言指点一下。
参考链接:http://social.technet.microsoft.com/Forums/fi/ITCG/thread/673d7646-2fbb-45f2-be67-91717589a6f5
赞赏微信赞赏支付宝赞赏
随机文章:
很有钻研精神,不知道有没有找到实习单位?
问题代码三中的n = n + &HFFFF貌似少加了1
我居然奇迹般地看懂了这篇文章!你的问题我也在纳闷。。
另外,VBS不是解释执行的吗?怎么会有compilation error呢?
[…] 答案见《VBS中&H前缀十六进制数的陷阱》。 […]
‘Author: Demon
‘Website: http://demon.tw
‘Date: 2012/5/7
x = &H8000
WScript.Echo x, TypeName(x)
&H8000的integer值是-32768,转换成long时要符号扩展的,还是-32768,即&HFFFF8000
Set FSO = CreateObject(“Scripting.FileSystemObject”)
Set FileObj = FSO.CreateTextFile(“E:\123.EXE”, True)
FileObj.Write Chr(CLng(“&H90”))
显示的是问号3F,请问怎么写入是实实在在的16进制数据90呢
&H8000的integer值是-32768,确实应该是是符号扩展的原因,符号扩展,让符号高位全为1,仍然是负数。C++里面用“unsigned”去除符号位,不知道VBS里有没有类似语法?