一把梭系列 ~ C語言範例 (0007) [運算: 算術運算&型態轉換、關係運算&條件運算、邏輯運算&位元運算、遞增&遞減&指定運算]

一把梭系列 ~ C語言範例 (0007) [運算: 算術運算&型態轉換、關係運算&條件運算、邏輯運算&位元運算、遞增&遞減&指定運算]

一把梭系列 ~ C語言範例 (0007) [運算: 算術運算&型態轉換、關係運算&條件運算、邏輯運算&位元運算、遞增&遞減&指定運算]


資料來源:

https://openhome.cc/Gossip/CGossip/ArithmeticOperator.html
https://openhome.cc/Gossip/CGossip/RelationalConditional.html
https://openhome.cc/Gossip/CGossip/LogicalBitwise.html
https://openhome.cc/Gossip/CGossip/IncrementDecrementAssignment.html


★前言:


★主題:

01.算術運算&型態轉換
    在 C 中提供與算術相關的 加(+)、減(-)、乘(*)、除(/)的運算子,另外還有一個也常用的餘除運算子(%)或稱模數(Modulus)運算子,這類以數學運算為主的運算子,稱之為算術運算子(Arithmetic operator)。    


    這類運算子的使用,基本上由左而右進行運算,遇到加減乘除的順序問題時,也是先乘除後加減,必要時加上括號表示運算的先後順序,例如這個程式碼會在主控台顯示7:

    printf("1 + 2 * 3=%d\n", 1 + 2 * 3);


    編譯器在讀取程式碼時,是由左往右讀取的,而初學者往往會犯一個錯誤,例如 (1 + 2 + 3) / 4,由於我們習慣將分子寫在上面,而分母寫在下面的方式,使得初學者往往將之寫成了:

    printf("1 + 2 + 3 / 4=%d\n", 1 + 2 + 3 / 4);


    這個程式事實上會是 1 + 2 + (3 / 4),為了避免這樣的錯誤,在必要的時候可為運算式加上括號。例如:

    printf("1 + 2 + 3) / 4=%d\n", (1 + 2 + 3) / 4);


    % 運算子是餘除運算子 它計算除法後的餘數,一個例子是,假設有個亂數產生函式為 rand(),可以產生正整數亂數,而你卻不知道它的最大範圍是多少,這時可以如下產生 0 到 99 的亂數:

    printf("%d\n", rand() % 100);


    也可以利用%來作循環計數之用,例如由 0 計數至 9 不斷循環:

    counter = (counter + 1) % 10;


    算術運算子使用不難,但要注意型態轉換的問題,請你先看看這段程式會印出什麼結果?

    int number = 10; 
    printf("%d\n", number / 3);


    答案不是 3.333333,而是 3,小數點之後的部份被自動消去了,這是因為 number 是 int 整數,而除數 3 也是 int 整數,運算出來的程式被自動轉換為 int 整數了,而為了正確的顯示運算的整數結果,還要使用 %d 格式指定字, 那下面這個程式呢?

    double number = 10.0;
    printf("%f\n", number / 3);


    這個程式的結果會是 3.3333,而為了正確的顯示運算的整數結果,使用了 %f 格式指定字,這是 C 的隱式型態轉換(Implicit type conversion),在一個型態混雜的算式中,長度較長的資料型態會成為目標型態,較小的型態會自動提升為目標型態,因而在上例中3會被提升為 3.0 再進行運算,結果就可以顯示無誤,這樣的轉換又稱算術轉換(Arithmetic conversion)。


    除了注意隱式型態轉換的問題,運算結果輸出時,還必須搭配格式指定字,才可以正確的顯示最後運算的結果。


    在一個指定的動作中,左邊的數值會成為目標型態,當右邊的數值型態比左邊的數值型態長度小時,右邊的數值會自動提升為目標型態,例如:

    int num = 10;
    double number = num;


    在上例中,number 的值最後會是 10.0,在指定的動作時,如果右邊的數值型態比左邊的數值型態型態長度大時,超出可儲存範圍的部份會被自動消去,例如將浮點數指定給整數變數,則小數的部份會被自動消去,例子如下,num 最後的結果會是 3 而不是 3.14,而為了顯示正確的整數結果,輸出時要指定格式指定字 %d:

    int num = 0;
    double number = 3.14;
    num = number; 
    printf("%d\n", num);


    在兩個整數型態相除時,也可以進行型態轉換,將其中一個型態轉換至 double 型態再進行運算,例如:

    int number = 10;
    printf("%f\n", (double) number / 3);//顯性型態轉換
    printf("%f\n", number / 3.0);//隱性型態轉換


    上例中結果會顯示 3.3333。

    


02.關係運算&條件運算

    數學上有比較的運算,像是大於、等於、小於等,C 中也提供了這些運算子,這些運算子稱為關係運算子(Relational operator)或比較運算子(Comparison operator),它們有大於(>)、不小於(>=)、小於(<)、不大於(<=)、等於 (==)以及不等於(!=)。


    請看看下面這幾行會顯示哪些數值:

    printf("10 > 5\t\t%d\n", 10 > 5);
    printf("10 >= 5\t\t%d\n", 10 >= 5);
    printf("10 < 5\t\t%d\n", 10 < 5);
    printf("10 <= 5\t\t%d\n", 10 <= 5);
    printf("10 == 5\t\t%d\n", 10 == 5);
    printf("10 != 5\t\t%d\n", 10 != 5);


    程式的執行會顯示 0 或 1,分別表示真(成立)或假(不成立),如下所示:

    10 > 5          1
    10 >= 5         1
    10 < 5          0
    10 <= 5         0
    10 == 5         0
    10 != 5         1


    在 C 中,所有非零的數值在作為條件式時都被視為真。


    關係運算在使用時有個即使是程式設計老手也可能犯的錯誤,且不容易發現,也就是等於運算子(==),注意它是兩個連續的等號(=)所組成,而不是一個等號,一個等號是指定運算子,這點必須相當注意,例如若有兩個變數 x 與 y 要比較是否相等,是寫成 x == y,而不是 x = y,後者的作用是將 y 的值指定給 x,而不是比較運算。


    既然談到了條件式的問題,我們來介紹 C 中的條件運算子(Conditional operator),它的使用方式如下:


    條件式 ? 成立傳回值 : 失敗傳回值


    條件運算子的傳回值依條件式的結果而定,如果條件式的結果為真,則傳回冒號前的值,若為假,則傳回冒號後的值,下面這個程式可以作個簡單的示範:

    int score = 0;

    printf("輸入學生分數:");
    scanf("%d", &score);

    printf("該生是否及格?%c\n", score >= 60 ? 'Y' : 'N');


    執行結果:

    輸入學生分數:59
    該生是否及格?N


    這個程式會依你所輸入的分數來判斷學生成績是否不小於 60 分,以決定其是否及格,如果是則傳回字元 ‘Y’,否則傳回字元 ‘N’,下面這個程式可以判斷使用者輸入是否為奇數:

    int input = 0;

    printf("輸入整數:");
    scanf("%d", &input);

    printf("該數為奇數?%c\n", input % 2 ? 'Y' : 'N');


    執行結果:

    輸入整數:3 
    該數為奇數?Y
    


    在 C 中非零數值都可以表示真,而 0 表示假,所以輸入的數為奇數時,就不能被 2 整除,所以餘數一定不是 0,在條件式中表示真, 因而傳回字元 ‘Y’,若數值為偶數,則被 2 整除,所以餘數為 0,在條件式中表示假,所以傳回字元 ‘N’。


03.邏輯運算&位元運算

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


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

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


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


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


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


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

    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);


    執行結果如下:

    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。


    要注意的是,邏輯運算子與位元運算子也是很常被混淆的,像是 && 為邏輯運算,而 & 為位元運算,|| 為邏輯運算,而 | 為位元運算, 初學時可得多注意。

    


    位元運算對初學者來說的確較不常用,但如果用的洽當的話,可以增進不少程式效率,例如下面這個程式可以判斷使用者的輸入是否為奇數:

    int input = 0;

    printf("輸入正整數:");
    scanf("%d", &input);

    printf("輸入為奇數?%c\n", input & 1 ? 'Y' : 'N');


    執行結果如下:

    輸入正整數: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 字元加密例子,先看看程式:

    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);


    執行結果如下:

    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 次方運算示範,如下所示:

    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);


    執行結果如下:

    2 的 0 次:1
    2 的 1 次:2

    2 的 2 次:4

    2 的 3 次:8


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

    00000001    1
    00000010    2
    00000100    4
    00001000    8


04.遞增&遞減&指定運算

    在程式中對變數遞增 1 或遞減 1 是很常見的運算,例如:

    int i = 0;

    i = i + 1;
    printf("%d\n", i);

    i = i - 1;
    printf("%d\n", i);


    這段程式會分別顯示出1與0兩個數,你也可以這麼寫這個程式:

    int i = 0;
    printf("%d\n", ++i);
    printf("%d\n", --i);


    其中寫在變數 i 之前的 ++ 與 — 就是 C 的遞增運算子(Increment operator)與遞減運算子(Decrement operator),當它們撰寫在變數之前時,其作用就相當於將變數遞增 1 與遞減 1:

    ++i;      // i = i + 1; 
    --i;      // i = i - 1;


    你可以將遞增或遞減運算子撰寫在變數之前或變數之後,但其實兩者是有差別的,將遞增(遞減)運算子撰寫在變數前時,表示先將變數的值加(減)1,然後再傳 回變數的值,將遞增(遞減)運算子撰寫在變數之後,表示先傳回變數值,然後再對變數加(減)1,例如:

    int i = 0;
    int num = 0;

    num = ++i;   // 相當於i = i + 1; num = i;
    printf("%d\n", num);

    num = --i;    // 相當於i = i - 1; num = i;
    printf("%d\n", num);


    在這段程式中,num 的值會前後分別顯示為 1 與 0,再看看下面這段:

    int i = 0;
    int num = 0;

    num = i++;    // 相當於num = i; i = i + 1;
    printf("%d\n", num);

    num = i--;     // 相當於 num = i; i = i - 1;
    printf("%d\n", num);


    在這段程式中,num 的值會顯示前後分別為 0 與 1。


    接下來看指定運算子(Assignment operator),到目前為止我們只看過一個指定運算子,也就是=這個運算子,事實上指定運算子還有以下的幾個:

    +=:a += b 就是 a = a + b
    -=:a -= b 就是 a = a - b
    *=:a *= b 就是 a = a * b
    /=:a /= b 就是 a = a / b
    %=:a %= b 就是 a = a % b
    &=:a &= b 就是 a = a & b
    |=:a |= b 就是 a = a | b
    ^=:a ^= b 就是 a = a ^ b
    <<=:a <<= b 就是 a = a << b
    >>=:a >>= b 就是 a = a >> b


★code:

#include <stdio.h>
#include <stdlib.h>

int main()
{
    /*01.算術運算&型態轉換*/
    printf("01.算術運算&型態轉換\n");
    printf("1 + 2 * 3 = %d\n", 1 + 2 * 3);
    printf("1 + 2 + 3 / 4 = %d\n", 1 + 2 + 3 / 4);
    printf("1 + 2 + 3) / 4 = %d\n", (1 + 2 + 3) / 4);
    printf("rand() %% 100 = %d\n", rand() % 100);
    printf("10 /3 = %d\n", 10 / 3);
    printf("(int)10.0/3 = %d\n", (int)10.0 / 3);
    printf("10.0/3 = %f\n", 10.0 / 3);
    printf("(double) 10 / 3 = %f\n", (double) 10 / 3);//顯性型態轉換
    printf("10 / 3.0 = %f\n", 10 / 3.0);//隱性型態轉換

    /*02.關係運算&條件運算*/
    printf("\n02.關係運算&條件運算\n");
    printf("10 > 5\t\t%d\n", 10 > 5);
    printf("10 >= 5\t\t%d\n", 10 >= 5);
    printf("10 < 5\t\t%d\n", 10 < 5);
    printf("10 <= 5\t\t%d\n", 10 <= 5);
    printf("10 == 5\t\t%d\n", 10 == 5);
    printf("10 != 5\t\t%d\n\n", 10 != 5);

    int score = 0;
    printf("輸入學生分數[判斷是否及格]:");
    scanf("%d", &score);
    printf("該生是否及格?%c\n\n", score >= 60 ? 'Y' : 'N');

    int input = 0;
    printf("輸入正整數[判斷是否為奇數 ~ 取餘數]:");
    scanf("%d", &input);
    printf("該數為奇數?%c\n\n", input % 2 ? 'Y' : 'N');

    /*03.邏輯運算&位元運算*/
    printf("\n03.邏輯運算&位元運算\n");
    int num = 75;
    printf("(75 > 70 && 75 < 80) = %d\n", num > 70 && num < 80);
    printf("(75 > 80 || 75 < 75) = %d\n", num > 80 || num < 75);
    printf("!(75 > 80 || 75 < 75) = %d\n\n", !(num > 80 || num < 75));

    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);

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

    input = 0;
    printf("輸入正整數[判斷是否為奇數 ~ 位元AND運算]:");
    scanf("%d", &input);
    printf("輸入為奇數?%c\n\n", input & 1 ? 'Y' : 'N');

    char ch = 'A';
    printf("65 ASCII code (before encoding):%c\n", ch);
    ch = ch ^ 0x7;
    printf("65^0x7 ASCII code (encoding):%c\n", ch);
    ch = ch ^ 0x7;
    printf("(65^0x7)^0x7 ASCII code (decoding):%c\n\n", ch);

    num = 1;
    puts("數字1的左移/效果\n");
    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\n", num);
    num = num >> 1;
    printf("2 的 2 次:%d\n", num);
    num = num >> 1;
    printf("2 的 1 次:%d\n", num);
    num = num >> 1;
    printf("2 的 0 次:%d\n\n", num);

    /*04.遞增&遞減&指定運算*/
    printf("\n04.遞增&遞減&指定運算\n");
    int i = 0;
    printf("i = 0\n");
    i = i + 1;
    printf("i = i + 1=> %d\n", i);
    i = i - 1;
    printf("i = i - 1=> %d\n\n", i);

    i = 0;
    printf("i = 0\n");

    puts("先指定後運算:");
    printf("i++=> %d\n", i++);//先指定後運算
    printf("i--=> %d\n\n", i--);

    puts("先運算後指定:");
    printf("++i=> %d\n", ++i);//先運算後指定
    printf("--i=> %d\n", --i);

    return 0;
}
/*
01.算術運算&型態轉換
1 + 2 * 3 = 7
1 + 2 + 3 / 4 = 3
1 + 2 + 3) / 4 = 1
rand() % 100 = 41
10 /3 = 3
(int)10.0/3 = 3
10.0/3 = 3.333333
(double) 10 / 3 = 3.333333
10 / 3.0 = 3.333333

02.關係運算&條件運算
10 > 5          1
10 >= 5         1
10 < 5          0
10 <= 5         0
10 == 5         0
10 != 5         1

輸入學生分數[判斷是否及格]:59
該生是否及格?N

輸入正整數[判斷是否為奇數 ~ 取餘數]:60
該數為奇數?N


03.邏輯運算&位元運算
(75 > 70 && 75 < 80) = 1
(75 > 80 || 75 < 75) = 0
!(75 > 80 || 75 < 75) = 1

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

~255 = -256
(char)~255 = 0

輸入正整數[判斷是否為奇數 ~ 位元AND運算]:59
輸入為奇數?Y

65 ASCII code (before encoding):A
65^0x7 ASCII code (encoding):F
(65^0x7)^0x7 ASCII code (decoding):A

數字1的左移/效果

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

2 的 2 次:4
2 的 1 次:2
2 的 0 次:1


04.遞增&遞減&指定運算
i = 0
i = i + 1=> 1
i = i - 1=> 0

i = 0
先指定後運算:
i++=> 0
i--=> 1

先運算後指定:
++i=> 1
--i=> 0
*/

★結果:


★延伸說明/重點回顧:

    01.算術運算中的重點就是先乘除後加減,如不確定先後次序,最保險的做法就是用括弧來定義


    02.型態轉換中的重點就是任何運算後的結果都要放到合適資料型態的記憶體空間否則資料可能會被系統捨棄,另外資料型態轉換可分成『隱性』和顯性兩種


    03.關係運算&條件運算的結果只會有0或1,其中1代表成立而0代表不成立


    04.邏輯運算&位元運算這一部分個人認為比較偏電子/電機/資工領域,如果沒有邏輯設計相關背景比較難理解,雖然就算不會其實也不會影響撰寫一般商業程式,但是畢竟他是程式語言的一部分,所以我還是有舉例


    05.遞增&遞減&指定運算這一部分個人認為是為了讓程式碼看起來更精簡,如果是初學者會覺得沒什麼用,但是如果當撰寫程式久了,就會越來越喜歡使用

One thought on “一把梭系列 ~ C語言範例 (0007) [運算: 算術運算&型態轉換、關係運算&條件運算、邏輯運算&位元運算、遞增&遞減&指定運算]

發表迴響

你的電子郵件位址並不會被公開。 必要欄位標記為 *