美思 [C 語言] 程式設計教學:如何使用運算子 (Operators)

C 語言運算子
Facebook Twitter LinkedIn LINE Skype EverNote GMail Yahoo Email

前言

在程式語言中,運算子多以符號表示,通常都無法再化約成更小的單位,所以運算子可視為該語言的基礎指令。本文介紹 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;
}

要注意 truefalse 都是巨集宣告,這個範例不代表 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 是運算子,需注意。

運算子優先順序

如果同一行敘述內用到多個運算子時,要如何確認那個運算子會先運算呢?正統的方法是透過查詢運算子優先順序來確認。

實際上程式設計者甚少背誦運算子優先順序。因為:

  • 運算子的優先順序和數學的概念相同
  • 藉由簡化敘述來簡化運算子的使用
  • 適度使用括號來改變運算子優先順序

即使自己很熟運算子的優先順序,也不保證團隊成員都很熟。我們應該要撰寫簡單明暸的敘述,減少大家對程式碼的猜測。

關於作者

身為資訊領域碩士,美思認為開發應用程式的目的是為社會帶來價值。如果在這個過程中該軟體能成為永續經營的項目,那就是開發者和使用者雙贏的局面。

美思喜歡用開源技術來解決各式各樣的問題,但必要時對專有技術也不排斥。閒暇之餘,美思將所學寫成文章,放在這個網站上和大家分享。