在邏輯上有所謂的「且」、「或」與「反」運算,在 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