前言
在程式語言中,運算子多以符號表示,通常都無法再化約成更小的單位,所以運算子可視為該語言的基礎指令。本文介紹 C 語言的運算子。
代數運算子
代數運算子用來進行日常的十進位代數運算。包括以下運算子:
+
:相加-
:相減*
:相乘/
:相除%
:取餘數
以下是簡短範例:
#include <assert.h>
int main(void)
{
assert(4 + 3 == 7);
assert(4 - 3 == 1);
assert(4 * 3 == 12);
assert(4 / 3 == 1);
assert(4 % 3 == 1);
return 0;
}
許多 C 語言教材會用 printf
將資料輸出終端機,但我們刻意用 assert
巨集檢查運算結果是否正確。因為透過 assert
巨集可自動檢查程式是否正確,但使用 printf
函式得手動檢查。而且 assert
敘述可以表明我們的意圖。
除了上述運算子,還有遞增和遞減運算子:
++
--
遞增、遞減運算子會帶著隱性的狀態改變,在使用時要注意。以下是範例:
#include <assert.h>
int main(void)
{
unsigned n = 3;
assert(++n == 4);
assert(n++ == 4);
assert(n == 5);
return 0;
}
在這個範例中,第一個 assert
敘述使用 ++n
,會先加 1
再比較值。但第二個 assert
敘述使用 n++
,會先比較值才加 1
。
由於遞增、遞減運算子會造成隱性的狀態改變,應儘量把敘述寫簡單一些,以免發生預期外的結果。
二元運算子
二元運算子用在二進位數運算。包括以下運算子:
&
:bitwise AND|
:bitwise OR^
:bitwise XOR~
:取補數<<
:左移 (left shift)>>
:右移 (right shift)
二元運算有其規則。以下是 &
(bitwise AND) 的運算規則:
p | q | p & q |
---|---|---|
1 | 1 | 1 |
1 | 0 | 0 |
0 | 1 | 0 |
0 | 0 | 0 |
歸納起來,就是「兩者皆為真時才為真」。
以下是 |
(bitwise OR) 的運算規則:
p | q | p | q |
---|---|---|
1 | 1 | 1 |
1 | 0 | 1 |
0 | 1 | 1 |
0 | 0 | 0 |
歸納起來,就是「兩者任一為真即為真」。
以下是 ^
(bitwise XOR) 的運算規則:
p | q | p ^ q |
---|---|---|
1 | 1 | 0 |
1 | 0 | 1 |
0 | 1 | 1 |
0 | 0 | 0 |
以下是 ~
(取補數) 的規則:
p | ~p |
---|---|
1 | 0 |
0 | 1 |
只看規則會覺得有點抽象,所以我們補上範例程式,並把運算的過程寫在註解裡:
#include <assert.h>
int main(void)
{
int a = 3; /* 0000 0011 */
int b = 5; /* 0000 0101 */
/* 0000 0011
&) 0000 0101
-------------
0000 0001 */
assert((a & b) == 1);
/* 0000 0011
|) 0000 0101
-------------
0000 0111 */
assert((a | b) == 7);
/* 0000 0011
^) 0000 0101
-------------
0000 0110 */
assert((a ^ b) == 6);
/* <<) 0000 0101
--------------
0000 1010 */
assert((b << 1) == 10);
/* >>) 0000 0101
--------------
0000 0010 */
assert((b >> 1) == 2);
return 0;
}
二進位運算比十進位運算不直觀,但速度較快。故二進位運算多用於注意速度的低階運算,平常用不太到。
比較運算子
比較運算子用來比較兩個值之間的相對關係。包含以下運算子:
==
:相等!=
:相異>
:大於>=
:大於或等於<
:小於<=
:小於或等於
以下是簡短的範例程式:
#include <assert.h>
#include <stdbool.h>
int main(void)
{
assert(4 == 4);
assert(4 != 5);
assert(5 > 4);
assert(5 >= 4);
assert(3 < 4);
assert(3 <= 4);
return 0;
}
要注意比較運算子只能用來比較基礎型別,而且要兩者間相容。對於其他型態的資料,要自行撰寫比較函式。像是標準函式庫中的 string.h 函式庫就有比較 C 字串的 strcmp() 函式。其範例如下:
#include <assert.h>
#include <string.h>
int main(void)
{
assert(strcmp("C", "C++") < 0);
assert(strcmp("C", "Ada") > 0);
return 0;
}
邏輯運算子
邏輯運算子用來進行布林運算。以下是 C 語言的邏輯運算子:
&&
:logic AND||
:logic OR!
:logic NOT
C 語言雖然沒有真正的布林數,仍然有布林語境,故仍可進行布林運算。
邏輯運算子和二元運算子規則類似,符號也有點類似,要注意不要寫錯。
以下是 &&
(logic AND) 的運算規則:
p | q | p && q |
---|---|---|
true | true | true |
true | false | false |
false | true | false |
false | false | false |
以下是 ||
(logic OR) 的運算規則:
p | q | p || q |
---|---|---|
true | true | true |
true | false | true |
false | true | true |
false | false | false |
以下是 !
(logic NOT) 的運算規則:
p | !p |
---|---|
true | false |
false | true |
我們使用 C99 引入的 stdbool.h 函式庫做一些基礎布林運算:
#include <assert.h>
#include <stdbool.h>
int main(void)
{
assert((true && true) == true);
assert((true && false) == false);
assert((false && true) == false);
assert((false && false) == false);
assert((true || true) == true);
assert((true || false) == true);
assert((false || true) == true);
assert((false || false) == false);
assert((!true) == false);
assert((!false) == true);
return 0;
}
要注意 true
和 false
都是巨集宣告,這個範例不代表 C 語言有布林數。
指派運算子
指派運算子分為一般指派運算和複合指派運算。一般指派運算就是單純的賦值。複合指派運算則是小小的語法糖。C 語言包含以下指派運算子:
=
:一般賦值+=
:相加後賦值-=
:相減後賦值*=
:相乘後賦值/=
:相除後賦值%=
:取餘數後賦值
以下是簡短的範例:
#include <assert.h>
int main(void)
{
unsigned short n = 4;
n += 3;
assert(n == 7);
return 0;
}
在這個例子中,n
原本是 4
,後來經 +=
(相加後賦值) 變成 7
。
三元運算子
三元運算子類似於小型的 if
敘述。其虛擬碼如下:
condition ? a : b
但三元運算子是表達式,和 if
敘述相異。所以,三元運算子會回傳值。
以下範例用三元運算子求得兩值中較大者:
#include <assert.h>
int main(void)
{
unsigned max = 5 > 3 ? 5 : 3;
assert(max == 5);
return 0;
}
其他運算子
以下是一些未歸類的運算子:
sizeof
:取得資料的寬度*
:宣告指標變數、取值&
:取址
以下實例用 sizeof
求得不同資料型態的寬度:
#include <stdbool.h>
#include <stdio.h>
int main(void)
{
printf("Size of bool: %u\n", sizeof(true));
printf("Size of char: %u\n", sizeof('c'));
printf("Size of short: %u\n", sizeof((short) 3));
printf("Size of int: %u\n", sizeof(3));
printf("Size of long: %u\n", sizeof(3l));
printf("Size of long long: %u\n", sizeof(3ll));
printf("Size of float: %u\n", sizeof(3.4f));
printf("Size of double: %u\n", sizeof(3.4));
printf("Size of double: %u\n", sizeof(3.4l));
return 0;
}
sizeof
長得很像函式,但是 sizeof
是運算子,需注意。
運算子優先順序
如果同一行敘述內用到多個運算子時,要如何確認那個運算子會先運算呢?正統的方法是透過查詢運算子優先順序來確認。
實際上程式設計者甚少背誦運算子優先順序。因為:
- 運算子的優先順序和數學的概念相同
- 藉由簡化敘述來簡化運算子的使用
- 適度使用括號來改變運算子優先順序
即使自己很熟運算子的優先順序,也不保證團隊成員都很熟。我們應該要撰寫簡單明暸的敘述,減少大家對程式碼的猜測。