逻辑运算、位运算


在逻辑上有所谓的「且」、「或」与「反」运算,在 C 中也提供这几个基本逻辑运算所需的逻辑运算符(Logical operator),分别为「且」(&&)、「或」(||)及「反相」(!)三个运算符。

来看看下面这个程序会输出什么?

#include <stdio.h>

int main(void) {
    int num = 75;
    printf("%d\n", num > 70 && num < 80);
    printf("%d\n", num > 80 || num < 75);
    printf("%d\n", !(num > 80 || num < 75));

    return 0;
}

三段程序分别会输出 1、0 与 1,也就是分别表示真、假与真三种状况。

&&运算中,如果左边的式子已被评断为假,则可立即判断整个式子为假,因而右边的式子就不会再评断;||运算中如果左边的式子已经被评断为真,则可以判断整个式子为真,因而右边的式子就不会再评断。

接下来看看位运算符(Bitwise operator),数位设计上有 AND、OR、NOT、XOR 与补数等运算,在 C 中提供这些运算的就是位运算符,它们的对应分别是 AND (&)、OR(|)、NOT(!)、XOR(^)与补数(~)。

如果不会基本的位运算,这边可以提供一个程序来显示各个运算的结果:

#include <stdio.h>

int main(void) {
    puts("AND运算:");
    printf("0 AND 0\t\t%d\n", 0 & 0);
    printf("0 AND 1\t\t%d\n", 0 & 1);
    printf("1 AND 0\t\t%d\n", 1 & 0);
    printf("1 AND 1\t\t%d\n\n", 1 & 1);

    puts("OR运算:");
    printf("0 OR 0\t\t%d\n", 0 | 0);
    printf("0 OR 1\t\t%d\n", 0 | 1);
    printf("1 OR 0\t\t%d\n", 1 | 0);
    printf("1 OR 1\t\t%d\n\n", 1 | 1);

    puts("XOR运算:");
    printf("0 XOR 0\t\t%d\n", 0 ^ 0);
    printf("0 XOR 1\t\t%d\n", 0 ^ 1);
    printf("1 XOR 0\t\t%d\n", 1 ^ 0);
    printf("1 XOR 1\t\t%d\n\n", 1 ^ 1);

    puts("NOT运算:");
    printf("NOT 0\t\t%d\n", !0);
    printf("NOT 1\t\t%d\n\n", !1);

    return 0;
}

执行结果如下:

AND运算:
0 AND 0         0
0 AND 1         0
1 AND 0         0
1 AND 1         1

OR运算:
0 OR 0          0
0 OR 1          1
1 OR 0          1
1 OR 1          1

XOR运算:
0 XOR 0         0
0 XOR 1         1
1 XOR 0         1
1 XOR 1         0

NOT运算:
NOT 0           1
NOT 1           0

C 中的位运算是逐位运算,例如 10010001 与 01000001 作 AND 运算,是一个一个位对应运算,答案就是 00000001;而补数运算是将所有的位 0 变 1,1 变 0,例如 00000001 经补数运算就会变为 11111110,例如下面这个程序所示:

char num = 255;
printf("%d\n", ~num);

这段程序会在控制台显示 0,char使用一个字节,若用于存储正整数最大可存储 255 的值,255 的二进制表示法为11111111,经补数运算就是00000000,也就是0

要注意的是,逻辑运算符与位运算符也是很常被混淆的,像是&&为逻辑运算,而&为位运算,||为逻辑运算,而|为位运算, 初学时可得多注意。

位运算对初学者来说的确较不常用,但如果用的洽当的话,可以增进不少程序效率,例如下面这个程序可以判断使用者的输入是否为奇数:

#include <stdio.h>

int main(void) {
    int input = 0;

    printf("输入正整数:");
    scanf("%d", &input);

    printf("输入为奇数?%c\n", input & 1 ? 'Y' : 'N');

    return 0;
}

执行结果如下:

输入正整数:5
输入为奇数?Y

这个程序得以运算的原理是,奇数的数值若以二进制来表示,其最右边的位必为 1,而偶数最右边的位必为 0,所以使用 1 来与输入的值作 AND 运算,由于 1 除了最右边的位为 1 之外,其他位都会是0,与输入数值 AND 运算的结果,只会留下最右边位为 0 或为 1 的结果,其他部份都被 0 AND 运算遮掉了,这就是所谓「位遮罩」,例如:

00000100    4
00000001    1
00000000    判 断为偶数

00000011    3
00000001    1
00000001    判 断为奇数

XOR 的运算较不常见,这边举个简单的 XOR 字符加密例子,先看看程序:

#include <stdio.h>

int main(void) {
    char ch = 'A';

    printf("before encoding:%c\n", ch);

    ch = ch ^ 0x7;
    printf("after encoding:%c\n", ch);

    ch = ch ^ 0x7;
    printf("decoding:%c\n", ch);

    return 0;
}

执行结果如下:

before encoding:A
after encoding:F
decoding:A

0x7是 C 中整数的 16 进制写法,其实就是 10 进制的 7,将位与 1 作 XOR 的作用其实就是位反转,0x7 的最右边三个位为 1,所以其实就是反转ch的最后两个字符,如下所示:

01000001    65 (对应 ASCII的'A')
00000111    0x7
01000110    70 (对应 ASCII中的'F')

同样地,这个简单的 XOR 字符加密,要解密也只要再进行相同的位反转就可以了。

要注意的是,虽然在说明时都只取8个位来说明,但实际的位在运算时,需依数据类型所占的内存长度而定,例如在使用int类型的 0 作运算时,要考虑的 是 32 个位,而不是只有 8 个位,因为int占有4个字节。

在位运算上,C 还有左移(<<)与右移(>>)两个运算符,左移运算符会将所有的位往左移指定的位数,左边被挤出去的位会被丢弃,而右边会补上 0;右移运算则是相反,会将所有 的位往右移指定的位数,右边被挤出去的位会被丢弃,至于左边位补0或补1则不一定,视系统而定。

可以使用左移运算来作简单的 2 次方运算示范,如下所示:

#include <stdio.h>

int main(void) {
    int num = 1; 

    printf("2 的 0 次:%d\n", num);

    num = num << 1; 
    printf("2 的 1 次:%d\n", num);

    num = num << 1; 
    printf("2 的 2 次:%d\n", num);

    num = num << 1; 
    printf("2 的 3 次:%d\n", num);

    return 0;
}

执行结果如下:

2 的 0 次:1
2 的 1 次:2
2 的 2 次:4
2 的 3 次:8

实际来左移看看就知道为何可以如此运算了:

00000001    1
00000010    2
00000100    4
00001000    8




展开阅读全文