位运算简介

供新手入门和老手参考的教程和相关资料,包括中文帮助
User avatar
amnesiac
Posts: 186
Joined: 22 Nov 2013, 03:08
Location: Egret Island, China
Contact:

位运算简介

23 Aug 2014, 03:57

注:本文译自《ahkbook》(虽然目前的半成品状态已持续很久且可能无限下去,不过仍是个伟大的创意,《AutoHotkey 之美》名称的由来即是向其致敬之意),稍有调整。
导言:这里位运算中的位是指二进制位,所以位运算都是二进制数值之间的运算。不过在脚本中也可以看到十进制数进行位运算,实际情况是脚本解释器会自动将其转换为二进制数并计算出结果。
理解二进制
在说明位运算之前,我们需要理解在计算机中是如何存储数字的。最简单的形式是使用开关状态表示,即关闭或打开,在程序中分别表示为 0 和 1(被称为位)。

十进制
二进制只是表示数字的另一种方式。在日常生活中我们使用十进制,其中有个位、十位、百位等。从技术上来说,每个位的值等于这个位的数字乘以 10^n,这里 n 表示到右边的距离,从 0 开始。因此,数字 647 等于 (6 * 100) + (4 * 10) + (7 * 1)(6 * 10^2) + (4 * 10^1) + (7 * 10^0)(是的,10^0 为 1)。
二进制
二进制使用相同的原理。每个位的值等于这个位的数字乘以 2^n,这里 n 表示到右边的距离,从 0 开始。因此,数字 1101001 等于 (1 * 64) + (1 * 32) + (0 * 16) + (1 * 8) + (0 * 4) + (0 * 2) + (1 * 1)。如果换算为十进制,则为 105。

位运算
那么在二进制中 1+1 等于多少呢?当然不会是 2,二进制中没有数字 2!结果是 10,它表示 (1 * 2^1) + (0 * 2^0)
所有的基本算术运算(加、减、乘、除)都与十进制工作方式相同。所以在这些运算中,我们没有理由使用二进制而不用十进制。然而,还有其他一些运算是二进制独有的:按位与、按位或、按位异或、按位非、按位左移和按位右移。

按位与
按位与(在 AutoHotkey 中使用单个 & 符号表示)需要两个数参与运算。运算时对每个同一位置的位进行如下操作:如果两个位都为 1,则结果为 1,否则为 0。请看示例:

Code: [Select all] [Download] GeSHi © Codebox Plus

 1100
&1010
_____
1000

注意,结果中唯一的一个“1”是在两个操作数所在的位都为 1 的位置。规则:1 & 1 = 1,1 & 0 = 0,0 & 1 = 0,0 & 0 = 0。

按位或
按位或(在 AutoHotkey 中使用单个 | 符号表示)也需要两个数参与运算。按位进行如下操作:只要其中有一个位为 1,则结果为 1,否则为 0。例如:

Code: [Select all] [Download] GeSHi © Codebox Plus

 1100
|1010
_____
1110

注意,结果中唯一的一个“0”是在两个操作数所在的位都为 0 的位置。
按位非
按位非(AutoHotkey 中表示为 ~)只需要一个操作数。它会反转操作数中的每个位,如果这个位为 0,则反转为 1,否则反转到 0。例如:

Code: [Select all] [Download] GeSHi © Codebox Plus

~1010
_____
0101

又如:

Code: [Select all] [Download] GeSHi © Codebox Plus

~0001
_____
1110

按位异或
按位异或(注:AutoHotkey 中异或的符号为 ^,而 ** 表示乘方)需要两个操作数。按位进行如下操作:如果两个位不同,则结果为 1,否则为 0。换种说法是,仅某个位为真,结果才为真,否则为假(这也是异或术语的由来)。例如:

Code: [Select all] [Download] GeSHi © Codebox Plus

 1010
^1100
_____
0110

按位左移和按位右移
按位左移(<<)和按位右移(>>)只需要一个操作数。操作时,把位向左或向右进行移动。例如:

Code: [Select all] [Download] GeSHi © Codebox Plus

00001010 << 3
_____________
01010000

又如:

Code: [Select all] [Download] GeSHi © Codebox Plus

0011100 >> 2
____________
0000111

注意移出外面的位将丢失。例如:

Code: [Select all] [Download] GeSHi © Codebox Plus

0101 >> 1
_________
0010

这两种运算和乘以或除以 2 的幂是一样的(应该注意到二进制的基数为 2)。因此 3 << 5 等于 3 * 2**5(记住:AutoHotkey 中 ** 表示乘方),即 96。还需要注意可能丢失位,例如 3 >> 1 结果不是 1.5(3/2)而是 1。请看示例:

Code: [Select all] [Download] GeSHi © Codebox Plus

0011      (二进制表示的 3
>> 1 (右移 1 位)
____
0001 (二进制表示的 1

位赋值
位赋值运算符看起来有点奇怪,例如 >>=^=|=。这些运算符的左边是变量,右边是参数,把它们连在一起进行计算(在等号前的运算符指定运算的类型),计算出结果后保存回变量中。例如:

Code: [Select all] [Download] (Script.ahk)GeSHi © Codebox Plus

MyVar := 5 ; 二进制的 101
MyVar |= 2 ; 101 | 010 = 111(二进制)= 7(十进制)
MsgBox % MyVar ; 7

MyVar := 5 ; 二进制的 101
MyVar <<= 2 ; 101<<2 = 10100(二进制)= 20(十进制)
MsgBox % MyVar ; 20

小结
现在您已经了解了二进制的基本知识和它能进行的运算,可以用在哪里呢?在普通的脚本中可能用的不多,不过在 WinAPI 和需要传递给它的结构之间就很常用了。
在消息中有时我们会遇到同时包含了多种信息的参数,此时需要分离出来。
例如在 WM_COMMAND 的回调函数中 wParam 参数的高字和低字各表示一部分信息,我们需要首先分离后才能判断:

Code: [Select all] [Download] (Script.ahk)GeSHi © Codebox Plus

LOWORD := wParam & 0xffff
HIWORD := wParam >> 16

必须注意,此时使用 Numget() 是错误的。
LOWORD := NumGet(wParam, 0, "short")
HIWORD := NumGet(wParam, 2, "short")
还有些意想不到的地方使用后也会方便许多,例如 MsgBox 命令。因为每个选项只有两种状态:打开或关闭,都是 2 的倍数,因此(除了选项为 0 时)每个选项的二进制数字表示形式中都只有一个 1。这样就可以使用按位或把它们加在一起(有些选项数值较大相加不便),例如:

Code: [Select all] [Download] (Script.ahk)GeSHi © Codebox Plus

MsgBox, % 4|16, title, text

反过来,要判断一个给出的数值中是否包含某个特定的选项,可以使用按位与来获取某个位的值。例如:

Code: [Select all] [Download] GeSHi © Codebox Plus

If (VarContaining35 & 32)
; 添加“?”图标
AutoHotkey 学习指南(Beauty of AutoHotkey)
I do not make codes, and only a porter of AutoHotkey: from official to Chinese, from other languages to AutoHotkey, and show AutoHotkey to ordinary users sometimes.
lcaird
Posts: 1
Joined: 26 Aug 2014, 08:58

Re: 位运算简介

29 Aug 2014, 08:04

帖子结尾部分,提取wParam 参数的高字和低字时,amnesiac你提到使用 Numget( ) 是错误的。
我执行了一下代码,发现Numget( )可以正确获取Numput( )存储的数据。

请问NumPut(0x1234, var, 0, "Short")和var := 0x1234的执行效果有什么不同吗?
User avatar
amnesiac
Posts: 186
Joined: 22 Nov 2013, 03:08
Location: Egret Island, China
Contact:

Re: 位运算简介

31 Aug 2014, 20:07

lcaird wrote:帖子结尾部分,提取wParam 参数的高字和低字时,amnesiac你提到使用 Numget( ) 是错误的。
我执行了一下代码,发现Numget( )可以正确获取Numput( )存储的数据。
请问NumPut(0x1234, var, 0, "Short")和var := 0x1234的执行效果有什么不同吗?

差异很大,后者适用于常规的命令和函数,前者使用时需慎重(你会调用 dll 时会明白它的本质)。

Return to “教程资料”

Who is online

Users browsing this forum: No registered users and 1 guest