邏輯運算、位元運算


在邏輯上有所謂的「且」、「或」與「反」運算,在 C++ 提供這幾個基本邏輯運算所需的邏輯運算子(Logical operator),分別為「且」(&&)、「或」(||)及「反相」(!)三個運算子。

來看看下面這個程式會輸出什麼?

int num = 75;
cout <<  (num > 70 && num < 80) << endl;
cout << (num > 80 || num < 75)  << endl;
cout << !(num > 80 || num < 75) << endl;

三段程式分別會輸出 1、0 與 1,也就是分別表示 truefalsetrue 三種狀況。

&& 運算中,如果左邊的式子已經被評斷為 false,可立即判斷整個式子為 false,因而右邊的式子就不會再評斷; || 運算中如果左邊的式子已經被評斷為 true,可以判斷整個式子為 true,因而右邊的式子就不會再評斷。

接下來看看「位元運算子」(Bitwise operator),數位設計上有 AND、OR、NOT、XOR 與補數等運算,C++ 提供這些運算的就是位元運算子,它們的對應分別是 AND (&)、OR(|)、NOT(!)、XOR(^)與補數(~)。

如果不會基本的位元運算,這邊可以提供一個程式來顯示各個運算的結果:

#include <iostream>
using namespace std;

int main() { 
    cout << "AND 運算:" << endl; 
    cout << "0 AND 0\t\t" << (0 & 0) << endl; 
    cout << "0 AND 1\t\t" << (0 & 1) << endl; 
    cout << "1 AND 0\t\t" << (1 & 0) << endl; 
    cout << "1 AND 1\t\t" << (1 & 1) << endl; 

    cout << "OR 運算:" << endl; 
    cout << "0 OR 0\t\t" << (0 | 0) << endl; 
    cout << "0 OR 1\t\t" << (0 | 1) << endl; 
    cout << "1 OR 0\t\t" << (1 | 0) << endl; 
    cout << "1 OR 1\t\t" << (1 | 1) << endl; 

    cout << "XOR 運算:" << endl; 
    cout << "0 XOR 0\t\t" << (0 ^ 0) << endl; 
    cout << "0 XOR 1\t\t" << (0 ^ 1) << endl; 
    cout << "1 XOR 0\t\t" << (1 ^ 0) << endl; 
    cout << "1 XOR 1\t\t" << (1 ^ 1) << endl; 

    cout << "NOT 運算:" << endl; 
    cout << "NOT 0\t\t" << (!0) << endl; 
    cout << "NOT 1\t\t" << (!1) << endl; 

    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

位元運算是逐位元運算的,例如 10010001 與 01000001 作 AND 運算,是一個一個位元對應運算,答案就是 00000001;而補數運算是將所有的位元 0 變 1,1 變 0,例如 00000001 經補數運算就會變為 11111110,例如下面這個程式所示:

signed char num = 255;
cout << ~num;

這段程式會在主控台顯示 0,signed char 使用一個位元組,若用於儲存正整數最大可儲存 255 的值,255 的二進位表示法為 11111111,經補數運算就是 00000000,也就是 0。

例如下面這個程式,運用位元運算來判斷使用者的輸入是否為奇數:

#include <iostream>
using namespace std;

int main() { 
    int input = 0; 

    cout << "輸入正整數:"; 
    cin >> input; 
    cout << "輸入為奇數?" 
         << (input & 1 ? 'Y' : 'N') 
         << endl; 

    return 0;
}

執行結果如下:

輸入正整數:5
輸入為奇數?Y

這個程式的原理是,奇數的數值若以二進位來表示,其最右邊的位元必為 1,而偶數最右邊的位元必為 0,所以使用 1 來與輸入的值作 AND 運算,由於 1 除了最右邊的位元為 1 之外,其它位元都會是 0,與輸入數值 AND 運算的結果,只會留下最右邊位元為0或為的結果,其它部份都被 0 AND 運算遮掉了。,例如:

00000100    4
00000001    1
00000000    判 斷為偶數

00000011    3
00000001    1
00000001    判 斷為奇數

底下是個簡單的 XOR 字元加密例子,先看看程式:

#include <iostream>
using namespace std;

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

    cout << "before encoding:" << ch 
         << endl; 

    ch = ch ^ 0x7; 
    cout << "after encoding:" << ch 
         << endl; 

    ch = ch ^ 0x7; 
    cout << "decoding:" << ch 
         << endl; 

    return 0;
}

執行結果如下:

before encoding:A
after encoding:F
decoding:A

0x7 是整數的 16 進位寫法,其實就是 10 進位的 7,將位元與 1 作 XOR 的作用後位元反轉:

01000001    65 (對應 ASCII 的'A')
00000111    0x7
01000110    70 (對應 ASCII 中的'F')

這個簡單的 XOR 字元加密,要解密也只要再進行相同的位元反轉就可以了。

雖然說明時都只取 8 個位元來說明,但實際位元在運算時,需依資料型態所佔的記憶體長度而定,例如在使用 int 型態的 0 運算時,要考慮的不會只有 8 個位元。

在位元運算上還有左移(<<)與右移(>>)兩個運算子(不是 coutcin 使用的 <<>>);左移運算子會將所有的位元往左移指定的位數,左邊被擠出去的位元會被丟棄,而右邊會補上 0;右移運算則是相反,會將所有的位元往右移指定的位數,右邊被擠出去的位元會被丟棄,至於左邊位元補 0 或補 1 則不一定,視系統而定。

可以使用左移運算來作簡單的 2 次方運算示範,如下所示:

#include <iostream>
using namespace std;

int main() { 
    int num = 1; 

    cout << "2 的 0 次:" << num
         << endl; 

    num = num << 1; 
    cout << "2 的 1 次:" << num
         << endl; 

    num = num << 1; 
    cout << "2 的 2 次:" << num
         << endl; 

    num = num << 1; 
    cout << "2 的 3 次:" << num 
         << endl; 

    return 0;
}

執行結果如下:

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

實際來左移看看就知道為何可以如此運算了:

00000001    1
00000010    2
00000100    4
00001000    8