二进制的世界

正如你所知的那样,电脑的资料都是以二进制存储的,当然我们编程语言中的变量也都是用二进制存储,在Cocoa和iOS编程中大量使用了位运算。通常我们接触的有这几个: <<(左移),>>(右移),&(与),|(或), ^(异或),~(非)。通过这些符号,我们可以对变量进行位元运算。

<<和>>

左移和右移的功能是移动变量中所有位元,位元向左/向右移动之后,最高位/最低为的位元会被移除并补0:
5 << 1 = 10  //5的二进制为00101,全部位元向左移动一位数后便会变成 01010
5 >> 1 = 2    //5的二进制为00101,全部位元向右移动一位数后便会变成 00010

  • 位元:即我们常说的Bit,指二进制中的一位,也称二进制位,是二进制最小信息单位。

十进制中,所有位数向左移动一位变为原来的十倍,向右移动一位变为原来的十分之一,二进制中也是如此,向左移动一位会变为原来的两倍,移动两位则变为四倍。对于电脑来说,进行位元运算要比乘除法快不少(在现代架构中, 情况并非如此:位运算的运算速度通常与加法运算相同,仍然快于乘法运算),所以对于性能要求较高的应用中,可以多考虑使用位元运算取代乘除法,但缺点是代码可读性就没那么高了。

&

学过C语言的应该都接触过&符号,它能将两个变量对应的位元进行’与’逻辑运算并产生新的变量。最基本的如:
0 & 0 = 0
1 & 0 = 0
0 & 1 = 0
1 & 1 = 1

两个相应的二进位都为1, 该位的结果值才为1,否则为0,假设我们现在计算85&109,根据运算规则:
109 -> 1101101
&85 -> 1010101
69      = 1000101

弄懂这个运算后我们来看一个Cocoa中的一个例子:
UIColor类中,我们不能直接通过颜色的Hex值得到一个UIColor的对象,而PS,Web中都大量的使用Hex值,所以我们需要给UIColor扩展一个Category,这样便可快速根据Hex值生成UIColor对象:

+(UIColor *)colorWithRGBHex:(UInt32)hex alpha:(CGFloat)alpha
{
    int r = (hex >> 16) & 0xFF;
    int g = (hex >> 8) & 0xFF;
    int b = (hex) & 0xFF;
    return [UIColor colorWithRed:r / 255.0f green:g / 255.0f blue:b / 255.0f alpha:alpha];
}

相信很多人都用过这样的方法,但估计很少人去深究这样做的道理,下面我就详细解释一下这其中的原理:

1)这个类方法接受两个参数,一个是UInt32类型的hex值(即16进制的颜色值,eg:0xf5c018),CGFloat类型的alpha(即浮点类型的透明度)。
2)根据所给的hex值计算得到r,g,b的值。
3)调用UIColor的类方法colorWithRed:green:blue:alpha:返回一个color对象

这个方法的主要都集中在第二步,我们可以看到计算r值时先将hex右移16位,再和0xFF进行&运算。

  • 我们先了解一下hex值,比如0xf5c018代表了红色为f5,绿色为c0,蓝色为18的颜色(均为16进制),每个颜色组件占一个字节即8位(在iOS和OS X上最常见的格式就是大家所熟知的32bits-per-pixel(每个像素占32位),8bits-per-componet(每个颜色组件占8位)),所以这三个颜色总共占了24位(还有8位用来存放透明度)。

我们可以将0xffc018转换为二进制:11110101 11000000 00011000,此时我们再看右移16位的操作,你便会发现这个操作执行完后变为:00000000 00000000 11110101,其实是移除了绿色和蓝色的位,最终只剩下红色位上的值,接下来我们用得到的值和0xFF进行&运算:
00000000 00000000 11111111
& 00000000 00000000 11110101
->00000000 00000000 11110101
最终我们把00000000 00000000 11110101赋值给整型r,到此我们便得到红色的值:r=245。

同理将hex值右移8位便得到:00000000 11110101 11000000,然后再和0xFF进行&运算:
00000000 00000000 11111111
&  00000000 11110101 11000000
->00000000 00000000 11000000
最终我们把00000000 00000000 11000000赋值给整型g,到此我们便得到绿色的值:g=192。

同样的方法得到蓝色:b=24,得到r,g,b后我们便很容易得到UIColor对象了,其实RGB和hex之间转换的原理就是利用了进制的相互转换。

另外我们可以用&很容易的判断一个数的奇偶性,因为奇数的二进制最后一位必定为1,所以再和1进行&运算后仍为1,而偶数的二进制最后一位必定为0,所以与1进行&运算为0:
(n & 1)?奇数:偶数

|

|的功能是将两个变量对应的位元进行’或’逻辑运算并产生新的变量。最基本的如:
0 | 0 = 0
0 | 1 = 1
1 | 0 = 1
1 | 1 = 1

两个相应的二进位中只要有一个为1, 该位的结果值为1,假设我们现在计算85|109,根据运算规则:
109  -> 1101101
| 85 -> 1010101
125 =    1111101

同样的,我们看Cocoa中的一个例子(Lancy的这篇文章已经讲的比较详细,就直接引用了):

typedef NS_OPTIONS(NSUInteger, UIViewAnimationOptions) {
    UIViewAnimationOptionLayoutSubviews            = 1 <<  0,
    UIViewAnimationOptionAllowUserInteraction      = 1 <<  1, // turn on user interaction while animating
    UIViewAnimationOptionBeginFromCurrentState     = 1 <<  2, // start all views from current value, not initial value
    UIViewAnimationOptionRepeat                    = 1 <<  3, // repeat animation indefinitely
    UIViewAnimationOptionAutoreverse               = 1 <<  4, // if repeat, run animation back and forth
    UIViewAnimationOptionOverrideInheritedDuration = 1 <<  5, // ignore nested duration
    UIViewAnimationOptionOverrideInheritedCurve    = 1 <<  6, // ignore nested curve
    UIViewAnimationOptionAllowAnimatedContent      = 1 <<  7, // animate contents (applies to transitions only)
    UIViewAnimationOptionShowHideTransitionViews   = 1 <<  8, // flip to/from hidden state instead of adding/removing
    UIViewAnimationOptionOverrideInheritedOptions  = 1 <<  9, // do not inherit any options or animation type

    UIViewAnimationOptionCurveEaseInOut            = 0 << 16, // default
    UIViewAnimationOptionCurveEaseIn               = 1 << 16,
    UIViewAnimationOptionCurveEaseOut              = 2 << 16,
    UIViewAnimationOptionCurveLinear               = 3 << 16,

    UIViewAnimationOptionTransitionNone            = 0 << 20, // default
    UIViewAnimationOptionTransitionFlipFromLeft    = 1 << 20,
    UIViewAnimationOptionTransitionFlipFromRight   = 2 << 20,
    UIViewAnimationOptionTransitionCurlUp          = 3 << 20,
    UIViewAnimationOptionTransitionCurlDown        = 4 << 20,
    UIViewAnimationOptionTransitionCrossDissolve   = 5 << 20,
    UIViewAnimationOptionTransitionFlipFromTop     = 6 << 20,
    UIViewAnimationOptionTransitionFlipFromBottom  = 7 << 20,
} NS_ENUM_AVAILABLE_IOS(4_0);

我们观察Apple在UIViewAnimationOptions的枚举变量,使用了一个NSUInteger就表示了UIViewAnimation所需的所有Option。其中0~9十个是互不影响的可同时存在option。16~19,20~24使用了4位来表示互斥的option。

如此定义了之后,对UIViewAnimationOptions的赋值变得尤为简单,使用 | 操作符既可以获得一个给对应的option位赋值后的结果。例如:

[UIView animateWithDuration:1.0
                      delay:0
                    options:UIViewAnimationOptionAllowUserInteraction
                         | UIViewAnimationOptionBeginFromCurrentState
                         | UIViewAnimationOptionCurveEaseIn
                 animations:{...}
                 completion:{...}];

提取也比较简单,使用 & 操作符 和 >> 操作符,就可以轻松判定某个位有没有被设置,以及提取某些状态位,例如:

UIViewAnimationOptions option = UIViewAnimationOptionAllowUserInteraction
                                | UIViewAnimationOptionBeginFromCurrentState
                                | UIViewAnimationOptionCurveEaseIn
                                | UIViewAnimationOptionTransitionCrossDissolve;

if (option & UIViewAnimationOptionAllowUserInteraction) {
    NSLog(@"UIViewAnimationOptionAllowUserInteraction has been set");
}
if (option & UIViewAnimationOptionBeginFromCurrentState) {
    NSLog(@"UIViewAnimationOptionBeginFromCurrentState has been set");
}
UInt8 optionCurve = option >> 16 & 0xf;
if (optionCurve == 1) {
    NSLog(@"UIViewAnimationOptionCurveEaseIn has been set");
}
UInt8 optionTransition = option >> 20 & 0xf;
if (optionTransition == 5) {
    NSLog(@"UIViewAnimationOptionTransitionCrossDissolve has been set");
}

这里最需要注意的地方就是,对互斥的状态的设置必须尤为小心,如果你这么写:

UIViewAnimationOptions badOption = UIViewAnimationOptionCurveEaseIn | UIViewAnimationOptionCurveEaseOut;
UInt8 oops = badOption >> 16 & 0xf;
NSLog(@"Sorry, it's not UIViewAnimationOptionCurveEaseInOut");
NSLog(@"oops = %d, you got UIViewAnimationOptionCurveLinear", oops);

^

^的功能是将两个变量对应的位元进行’异或’逻辑运算并产生新的变量。最基本的如:
0 ^ 0 = 0
0 ^ 1 = 1
1 ^ 0 = 1
1 ^ 1 = 0
简言之就是两个比较的位不同时才为1,相同则为0。Cocoa中^有特定的用意(block中),所以别的场合中很少用到,这里我想到一个比较经典但不常见的例子:
写程序时有时要交换两个变量的值,通常的做法是定义一个中间变量,从而将两个变量的值进行交换,而我们其实可以使用^运算符直接将两个变量进行交换:
a = a ^ b;
b = a ^ b;
a = a ^ b;
乍眼看去可能看不出什么,我们不妨假设sum=a^b。
第二步中的a替换成sum就得到b=sum^b=a^b^b=a。
第三步中的a和b同时替换掉前两步中的值就得到a=a^b^a=b。
最终顺利交换两个变量的值。

  • 对于a^b^b,我们可以先计算b^b,因为所有位元都相同,所以b^b=0,然后再计算a^0,因为1^0=1,0^0=0,所以a^0=a。

~

~ 0 = 1
~ 1 = 0
~的作用是将变量的每一个位元都颠倒过来,比如:
~00000000 00000000 00000000 00000101 -> 5
=11111111 11111111 11111111 11111010 ->-6