位运算符

之前接触到了一次为运算符,说是能提高效率,但是一直不是很理解。今天看到这篇文章,受益匪浅。决定保留。
原文
翻译后 [Flash/Flex] 位运算符的理解

也许在此之前,你觉得位运算符很难理解,但阅读完本文及附带的几个实例将更好帮助您的理解其用途及用法。

介绍
 位运算符是指二进制级别上作用于int和uint数据类型的运算符(如+,*,&&等等)。这意味着他们直接作用于一整数的二进制数位(binary digits or bits),听上去很深奥,而实际上位运算符是易学易用的。

 首先,要对二进制数和十六进制数有个理解(如不清楚请查看此文)。以下是一个小应用,用以帮助了解不同的位运算符。


 若还是不太清楚这到底怎么回事,也不必惊慌,我们马上就来讲解了,认真听哦孩纸们~

认识按位运算符
 让我们来看看AS3所支持的位运算符,与其他语言也类似(如JavaScript和Java):
&(位与)
|(位或)
~(位非)
^(位异或)
<<(位左移)
>>(位右移)
>>>(位无符号右移)
&=(位与并重新赋值)
| =(位或并重新赋值)
^ =(位异或并重新赋值)
<< =(位左移并重新赋值)
>> =(位右移并重新赋值)
>>>=(位无符号右移并重新赋值)

 在这要注意几点:
 1,某些位运算符长的跟一些常用的运算符很像,(& vs. &&, | vs. ||)。
 2,许多位运算符是复合赋值形式,与+和+=,-和-=等用法相同。

&运算符
 首先介绍的是位与运算符&。通常,int和uint占用4个字节(32位)的空间,即每一个int或uint是以32位形式存储的。在本教程中,我们假设int和uint只占用1个字节(只有8位)。

 &运算符依次比较两整数的每位二进制数,然后返回一个新的整数。两个整数同位均为1时返回1,反之返回0。一图解胜过千言万语,在此附上一说明:37&23=5。
 
 注意37和23的二进制位是如何比较的。以true或false来理解二进制数是一常见做法,即1相当于true,0相当于false,这样&运算符更通俗易懂。

 我们比较两个布尔值时,通常写作boolean1&& boolean2。只有当boolean1和boolean2均为true时表达式才是true。同理,只有当integer1与integer2同位上数字均为1时,返回值才为1。

 以下为一表格,用于解释:
 
 &运算符用途之一是检查某数是奇是偶。当此数为整数时,简单地检查最右边的位就能确定。由于二进制数基于10,最右边的位只可能为0或1。当最右边的位为1时,此数就是奇数;当最右边的位是0,此数就是偶数。
 以下为一例子:
var randInt:int = int(Math.random()*1000);
if(randInt & 1)
{
    trace("Odd number.");
}
else
{
    trace("Even number.");
}
复制代码

 我的计算机上,此方法比用randInt%2来检查奇偶效率高约66%,这提升了相当的性能呐!

|运算符
 接下来讲讲位或运算符|。你可能已经猜到了,|之于||正如&之于&&。 |运算符比较两整数的二进制位,若某一位为1就返回1。同样跟||布尔值用法类似。
 
 呈同一例,现在使用的是|运算符,即37 |23=55:
 
Flags:&和|运算符的用法

 我们可以利用&和|运算符来实现通过简单的int给函数传递多选项。

 假设我们创建了个弹出的窗口类,在其底部有YES, No, Okay,或Cancel按钮或其他任意组合,如何进行处理?以下为一硬性方式:
public class PopupWindow extends Sprite
{
    // Variables, Constructor, etc...

    public static void showPopup(yesButton:Boolean, noButton:Boolean, okayButton:Boolean, cancelButton:Boolean)
    {
        if(yesButton)
        {
            // add YES button
        }

        if(noButton)
        {
            // add NO Button
        }
        // and so on for the rest of the buttons
    }
}
复制代码

 这虽然也能实现,但其实是不太好的。作为一程序员,每次调用函数时都得检查其参数的顺序,非常烦人。-举个例子来说吧,假如你只想要显示“Cancel”按钮,那就必须将其他所有布尔值设为false。

 那么接下来就用我们刚学到的&和|来提出个更好的解决方案:
public class PopupWindow extends Sprite
{
    public static const YES:int = 1;
    public static const NO:int = 2;
    public static const OKAY:int = 4;
    public static const CANCEL:int = 8;

    public static void showPopup(buttons:int)
    {
        if(buttons & YES)
        {
            // add YES button
        }

        if(buttons & NO)
        {
            // add NO button
        }
    }
}
复制代码

 调用该函数来显示Yes按钮,No按钮,Cancel按钮:
PopupWindow.show(PopupWindow.YES | PopupWindow.NO | PopupWindow.CANCEL);
复制代码

 这到底发生了啥?在第二个例子中,咱的常量是二的幂。如果我们查下其二进制形式,就会发现其有某一位上数字为1,其余为0。事实上,他们这个数字为1的位均不同,也就是说不管咱怎么将其与|比较,返回值均是惟一且不与其他等同的。只要选项中含有1,我们返回的结果就会为含有一个1的二进制数。

 就咱前面那个例子来说吧,PopupWindow.YES|PopupWindow.NO|PopupWindow.CANCEL就相当于1|2|8,用二进制表示就是00000001|00000010|00001000,返回的结果就是00001011。

showPopup()函数里,我们使用&来检查传入的是什么选项。当我们检查 buttons & YES,所有YES中除却最右边位以外所有的位均等于0。因而咱只用做基础的检查,判断按钮最右位是否为1。若是,buttons & YES将不等于0,并且相应的if语句将被执行;若否,buttons & YES将等于0,并且相应的if语句将被跳过。

~运算符
 位异运算符于上面两个略有不同,使用的时候不是把两个数分别置于前后,而是只需拿一数字置于其后。就像!运算符那样,意料之中其功能也跟!差不多。正如!逆转布尔值true为false,~运算符能逆转二进制位0为1,1为0:
 
 再来个简例。咱有一整数37,二进制表示00100101。 ~37就为11011010。

补码,uint vs. int等等
 好戏开场!让我们更深入了解下计算机上的二进制数吧。首先从uint开始。之前提过,uint长度通常是4个字节或32位,即它有32个二进制位。这是很容易理解的:要获得基于10的值只需简单转换就行(所有值均为正数不必考虑符号问题)。

 那int呢?它同样是32位的,而如何储存负数?让我们来看看用于存储二进制数的补码体系。虽然此部分我们不会涉及太深入,但使用补码体系能使咱二进制算术更容易说清楚。

 为了得到某二进制数的补码,我们只需逆转所有位(即同~运算符操作),并给结果加1。我们来做个小例:
 
 然后此值就为-37了。为什么要通过此般复杂的过程来获得-37值而不是简单逆转第一个位?

 关于这个问题嘛,看看一简单的表达式37+-37就知道了。众所周知,这应该等于0,于是将37与其补码相加后咱就得到:
 
 请注意,由于咱们的整数只能容纳八个二进制数字,所以最高位上的那个1将被丢弃,即最终结果为0。

 总之要得到一个数的负值,我们只要取补。通过逆转所有的位并加一就可得到。

 想自己试一下么?给一个AS3文件加上语句 trace(~37+1); 然后编译运行,你将看到结果为-37,正解。

 笔算的话是有捷径的:从右至左找到第一个1,然后逆转其左侧的所有位。
 

 当我们查看一个带符号的二进制数(换句话说,此数可以为负,int而非uint),我们可以查看其最左边的数字获知其是正是负。若为0,则此数为正,我们可以简单转换为10形式;如果最左边的位是1,那么此数为负,我们得先求出此数绝对值的补码,然后在加上负号。

 举例说明,11110010是一个负数,我们从右至左反转首个1左侧的所有位,得到00001110(等于13),于是就有11110010等于-13。

^运算符
 咱继续回来讨论位运算符,接下来是位异或运算符。布尔运算符里并没有与此等效的符号。

 ^运算符也与&和|类似,其前后均要有int或uint值。其计算结果的时候,同样要比较数字的二进制位上的值,若同位上的值相同,就返回0;反之则返回1。这就是“异或”这个名字的由来。
 

 同样看个小例
 
 ^运算非常有用-特别适用于切换二进制数-但在此文我们将不涉及实际应用。

<<运算符
 现在轮到位移运算符了,在这就拿位左移运算符解说吧。

 不像之前&,|和^比较的是两个整数,此类运算符用于整数的移位。运算符左侧为需要移位的整数,右侧则为需要移的位数。举个例子,37<< 3将37左移了3位。当然,这是作用在二进制表示的37的。

 看个小例(注意,咱假设整数只有8位以便于说明,而实际上它是有32的)。以下咱将数字37分配到8位的内存块内了。
 
 咱将数字左移三位,即37<< 3所作用的:
 
 但,现在出现了个小问题 - 被释放的3位内存要做什么?
 
 废废,所有空出来的用0填充就好了。于是我们得到结果00101000。这就是位左移的内容了。孩纸们记住,Flash将位左移的结果视为int类型而非uint。因而,若你出于某种原因需要的是一个uint,你必须要进行类型转换,如uint(37 << 3)。事实上,这类型转换并不改变二进制任何的信息,只是跟Flash的解析有关罢了(关于补码的内容)。

 位左移的一有趣的功能:它等同于与一个二的幂相乘。因此,37 << 3 == 37 *Math.pow(2,3)== 37 * 8。若你使用位左移来替代Math.pow,你会得到巨大的性能提升。

 您可能已经注意到,我们计算得到的二进制数不等于37 * 8。这是由于咱假设内存使用的是8位的,若您在ActionScript中尝试,将会得到正确的结果。不然在页面顶部的demo测试也行!

>>运算符
 至此,咱了解了位左移,接下来呢,理解位右移就会很容易。同理,位右移实现向右移动我们指定的位数,轻微的区别就是最后需要填充的空位在左边。

 若我们用的是一个负数(二进制数的最左位为1),空位需要用1填充;而如果我们用的是一个正数(最左位为0),那么所有的空位就用0填充。再次,一切追溯到补码层面。

 虽然听起来有点复杂,但其保存了数值的符号。所以-8 >> 2 == -2 而8 >> 2 == 2。强烈建议你自己在纸算算。

 >>与<<相反,所以意料之中位右移某个数等同于除以2的幂。同样,可以用它来代替调用Math.pow来获得显着的性能提升。

>>>运算符
 最后咱来说说位无符号右移运算符。其与位右移非常相似,不同的只是左侧的所有空位均用0填充。这意味着该运算符结果始终是一个正整数,并且其始终将整数转换为无符号整数。本节我们并不运行相关例子,但会简要看看它的使用方法。

使用位运算符处理颜色
 在ActionScript 3中关于位运算符最实用的用途之一就是颜色处理,颜色通常存储为uint类型。

 颜色的标准格式用十六进制写的:0xAARRGGBB - 每个字母代表一个十六进制数字。其中,前两个十六进制数字(即前8个二进制数字)代表的是alpha值(透明)。接下来的8位代表红色的量(0到255的整数),接下来的8位为绿色的量,最后八位即蓝色的量。

 若不用位运算符,处理此格式的颜色是相当困难的-有了位运算符,一切变得如此简单!

挑战1:获取某颜色包含的蓝色的量:使用&运算符,找出任意颜色包含的蓝色的量。
public function findBlueComponent(color:uint):uint
{
    // Your code here!
}
复制代码

 我们需先剔除此颜色其他不相干的数据,让蓝色部分留下。要实现很简单!写成color & 0x000000FF –或更简单的color & 0xFF –就能得到蓝色部分。
 
 正如之前&运算符部分学到的那样,任何二进制数字&0将始终等于0,而任何二进制数字&1将保持原值。因此,我们用0xFF来进行位剔除,最终得到的只有蓝色分量。

挑战2:获取某颜色包含的红色的量:使用两个位运算符,找出任意颜色包含的红色的量。
public function findRedComponent(color:uint):uint
{
    // Your code here!
}
复制代码

 其实解决这一问题有两个方法。一个是return (color & 0xFF0000) >> 16;另一个为return (color >> 16) & 0xFF;
 这个问题其实与挑战1非常相似,不同的是我们需要做位移操作。

挑战3:获取某颜色的透明度:只使用一个位运算符找出某颜色的alpha(0到255的整数)。
public function findAlphaComponent(color:uint):uint
{
    // Your code here!
}
复制代码

 这题有点小棘手,选择位右移运算符的时候要注意到最左位数字为uint,因而要使用>>>运算符。因此,这答案就是return color >>> 24;

最后挑战:将以上单色分支合并一个新颜色:使用<<和|运算符,将分支合并到1个uint里。
public function createColor(a:uint, r:uint, g:uint, b:uint):uint
{
    // Your code here!
}
复制代码

 在此我们已将所有分支移到其正确位置并合并。我们希望Flash将其看做一个无符号整型,因而我们将它转换为uint类型: return uint((a << 24) | (r << 16) | (g << 8) | b);

复合运算符
 假设咱有一整数x,x= x&0xFF等同于x&=0xFF,x = x | 256 等同于x |= 256,同理其他符合运算符用法也类似。

小结

 感谢您阅读这篇文章!希望现在您已经了解位运算符,并能够在你的AS3代码(或在许多其他语言!)里使用它们了。一如既往地说一句,若您有任何意见或建议,请在以下留言。



--------------------------------------------------------------------------------------------------------------------------------

转自:http://www.douban.com/note/218122019/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值