創作內容

74 GP

[達人專欄] 跟著豬腳 C 起來:條件分歧,邏輯整個都出來了

作者:解凍豬腳│2020-09-17 20:04:41│巴幣:148│人氣:2347
 
  前篇:跟著豬腳 C 起來:利用迴圈重複動作,省時又省力

  好久不見了,這裡是有生之年系列(誤)的《跟著豬腳 C 起來》。

  上一次,也就是一年多前,我們講到了程式設計當中體現電腦精髓的「迴圈」,也就是讓電腦去重複執行指定好的一套動作。

  然而,單單使用「迴圈」也只不過是速度快而已,只有「快」並沒有什麼用。現實世界畢竟是複雜的,我們在做料理的時候,總得要知道哪些東西要切丁、哪些東西要切絲,總不是拿起菜刀來就朝著食材一陣瞎機巴亂砍。

  這種時候我們就得讓電腦去判斷某些條件是否成立。這非常簡單,我們只要知道兩個單字就好:if 和 else。

  就跟字面意思一樣,if 就是「如果」,else 就是「除此之外」。回顧上次說過的 for 迴圈用法,條件分歧也是「如果符合條件,就執行大括號裡面的東西;如果不符合條件,就不執行大括號裡面的東西」,也就是說:

    if (條件 A) {
        xxx;
    }

  只要符合條件 A,那電腦就會執行 xxx,不符合的話這條 xxx 就會當作沒看到。

  但我們的需求不見得往往都是這麼簡單。有時候我們也許會需要在不符合狀況的時候也做些什麼,比如說「如果 1099736 是 3 的倍數的話,電腦告訴使用者 1099736 是 3 的倍數;如果 1099736 不是 3 的倍數的話,電腦告訴使用者 1099736 不是 3 的倍數。」我們可以寫成:

    if (1099736 % 3 == 0) {
        printf("1099736 是 3 的倍數\n");
    }
    if (1099736 % 3 != 0) {
        printf("1099736 不是 3 的倍數\n");
    }

  但這樣寫條件實在太麻煩了,而且即使 1099736 是 3 的倍數,執行完上面的條件後,電腦之後又會再計算一次 1099736 % 3 的值,造成不必要的效能浪費。我們可以改寫成:

    if (1099736 % 3 == 0) {
        printf("1099736 是 3 的倍數\n");
    } else {
        printf("1099736 不是 3 的倍數\n");
    }

  如此一來,電腦最後一定只會走其中一條路,也不需擔心重複執行的問題。

  但老樣子,現實生活中的問題並不總是非黑即白,有的時候也許是個多向岔路口。比如說你今天想買車:財產達五千萬的時候,你可能會去買一部跑車;財產達三百萬、小於五千萬的時候,你可能會去買一部休旅車;財產達二十萬、小於三百萬的時候,你可能會去買一部二手的轎車;沒錢的時候,你就只能回家。

  我們可以寫成:

    int money = 20000000;
    if (money >= 50000000) {
        printf("買跑車\n");
    } else if (money >= 3000000) {
        printf("買休旅車\n");
    } else if (money >= 200000) {
        printf("買二手轎車\n");
    } else {
        printf("有夢最美\n");
    }

  這裡值得注意的是,程式的執行順序都是由上往下,所以下面的 else if 都是建立在「先前的所有狀況都不成立」的前提上,我們自然也就沒必要把買休旅車的條件寫為「else if (money < 50000000 and money >= 3000000)」這麼麻煩了,後面的其他條件同理。

  因此,我們在寫多條 else-if 的條件分歧的時候,可以優先把最難達成的條件擺在最前面處理,這樣就能把這些反面條件累積起來利用了。

  順帶一提,遇到這種很多個 0 的情況,可以用科學記號來取代,比如把 50000000 簡寫成 5e7,代表 5×10^7 的意思,但使用科學記號就要注意型別轉換,直接把它當成整數來操作的話可能會遇上一些問題,比如說用 printf("%d\n", 5e3); 沒有辦法正常顯示 5000。

  有了 if-else 的基本概念,假如我們想要寫一個身分證字號的驗證器,那當然就會需要告訴電腦「如果身分證字號的第一位不是英文字母,那就可以直接當作無效的身分證字號」、「如果身分證字號的第一位數字不是 1 或 2」……諸如此類的條件。

  這裡向海外的朋友解釋一下,臺灣的身分證字號格式是一個英文字母 + 九個數字,數字的第一位以 1 或 2 表示生理男性或女性,而每一位數字依照位置個別乘上固定的係數之後再加起來,如果可以被 10 整除就是有效的編號。

  我們整理一下可以得到規則如下:
  1. 身分證字號必須是 10 個字元
  2. 第一個字元必須是英文字母
  3. 第二個字元必須是數字 1 或 2
  4. 按規則計算後的加權數值必須是 10 的倍數

  也就是這樣的流程:


  知道了流程,下一個問題自然就是該如何把它寫成 C 語言。按照身分證字號的判斷流程,可以知道它寫成 C 語言之後大概會長這樣:

    if (長度不是 10 個字元) {
        得到答案是無效
    } else {
        if (第一個字元不是英文字母) {
            得到答案是無效
        } else {
            if (第二個字元不是 1 也不是 2) {
                得到答案是無效
            } else {
                計算加權數值
                if (加權數值不是 10 的倍數) {
                    得到答案是無效
                } else {
                    得到答案是有效
                }
            }
        }
    }

  這看起來非常複雜,更不用說如果程式設計師沒有適當地加上縮排,這 code 想必是亂成一團(所以為什麼會強調縮排很重要),而身分證字號畢竟是要求每個條件都成立才能算是有效的,所以我們也可以把問題簡化成:

if (長度不是 10 個字元 或 第一個字元不是英文字母 或 第二個字元不是 1 也不是 2) {
    得到答案是無效
} else {
    計算加權數值
    if (加權數值不是 10 的倍數) {
        得到答案是無效
    } else {
        得到答案是有效
    }
}

  寫法可以有很多種,端看你怎麼去組合它。

  接下來就是實作了。為了取得字元陣列內容的長度,我們可以用「#include <string.h>」引入 string.h 標頭檔,利用裡面的 strlen() 函式來取得 char[] 型態變數的長度:  

    char id[16]="test12345";
    printf("%d\n", strlen(id)); // → 得到長度為 9

  其實流程出來了,也就只剩下加減乘除、陣列的運用和 char-int 之間的轉換而已。畢竟身分證的英文字母規律有點亂,為了不要在條件分歧以外的話題著墨太多,直接把 code 貼出來吧,細節就留給需要熟悉 chat-int 轉換的人慢慢看了:

#include <stdio.h>
#include <string.h>

int main(void) {
    char id[16] = "A123456789";
    int checkArr[16];
    int checkMultiply[16] = {1, 9, 8, 7, 6, 5, 4, 3, 2, 1, 1};
    int checksum = 0;

    // to uppercase
    if (id[0] >= 97 && id[0] <= 122) {
        id[0] -= 32;
    }

    // check the format
    if (strlen(id) != 10 || id[0] < 65 || id[0] > 90 || (id[1]!='1' && id[1]!='2')) {
        printf("無效的格式\n");
    } else {
        // prepare for calculation
        if (id[0] >= 'A' && id[0] <= 'H') {
            checkArr[0] = 1;
            checkArr[1] = id[0]-'A';
        } else if (id[0] == 'I') {
            checkArr[0] = 3;
            checkArr[1] = 4;
        } else if (id[0] == 'J' || id[0] == 'K') {
            checkArr[0] = 1;
            checkArr[1] = id[0]-'J'+8;
        } else if (id[0] >= 'L' && id[0] == 'N') {
            checkArr[0] = 2;
            checkArr[1] = id[0]-'L';
        } else if (id[0] == 'O') {
            checkArr[0] = 3;
            checkArr[1] = 5;
        } else if (id[0] >= 'P' && id[0] <= 'V') {
            checkArr[0] = 2;
            checkArr[1] = id[0]-'P'+3;
        } else if (id[0] == 'W') {
            checkArr[0] = 3;
            checkArr[1] = 2;
        } else if (id[0] == 'X') {
            checkArr[0] = 3;
            checkArr[1] = 0;
        } else if (id[0] == 'Y') {
            checkArr[0] = 3;
            checkArr[1] = 1;
        } else if (id[0] == 'Z') {
            checkArr[0] = 3;
            checkArr[1] = 3;
        }
        int i;
        for (i=1; i<=9; i++) {
            checkArr[i+1] = id[i]-'0';
        }

        // calculate the checksum
        for (i=0; i<11; i++) {
            checksum += checkArr[i]*checkMultiply[i];
        }

        // check the checksum
        if (checksum % 10 == 0) {
            printf("%s 為有效的身分證字號\n", id);
        } else {
            printf("%s 為無效的身分證字號\n", id);
        }
    }
    return 0;
}

  這裡補充一點,「&&」可以表示條件句的「and」,「||」可以表示條件句的「or」,「!」可以表示條件句的「not」。



  在絕大多數的程式語言當中,你都可以看得到 if / else-if / else 的用法,只是可能語法會長得有點不一樣就是了。

  如果你想判斷的對象非常單純,只有一個變數,但有非常多種可能性的話,你還可以選擇不要使用 if 句法,而是 switch-case 句法。用法非常簡單,填入變數和值交給電腦去判斷就可以了:

    switch (id[0]) {
        case 'A': // 如果 id[0] == 'A'
            printf("出生於台北市\n");
            break;
        case 'B':
            printf("出生於台中市\n");
            break;
        case 'D':
            printf("出生於台南市\n");
            break;
        case 'E':
            printf("出生於高雄市\n");
            break;
        case 'F':
            printf("出生於新北市\n");
            break;
        case 'H':
            printf("出生於桃園市\n");
            break;
        default:
            printf("反正不是直轄市的就對了\n");
            break;
    }

  要注意 switch-case 句法必須在執行內容的尾部加上一個 break(如果沒有加上 break 的話就會繼續檢查或執行其他的 case),而下面的 default 則相當於 else,觀念上大同小異。

  只要會了這兩種句法,程式寫起來就會變得很輕鬆,需要判斷多個條件就用 and、or 和小括號來組合、串接,達到想要的效果。至於如何把判斷的內容和順序寫得精簡又漂亮,就仰賴個人的經驗累積了。

  雖然如此,講到了條件分歧,不免還是要講一下所謂的「三元運算子」。在 C 語言裡,如果你碰上了非常單純的「非黑即白」的狀況,可以使用三元運算子來讓程式碼變得更簡短。

  用法就是「A ? B : C」。代表的是「A 成立嗎?成立的話就 B,不成立的話就 C」。像前面的「有效」和「無效」之分,我們就可以直接把它從五行縮短成一行,利用三元運算子來表示:

    printf("%s 為%s的身分證字號\n", id, (checksum%10 == 0)?"有效":"無效");

  只是這麼做就會犧牲掉部分的可讀性了,畢竟簡化得太多容易讓其他程式設計師一時看不懂你在寫什麼(e.g., 北車, 高火)。

  截至目前為止,我們已經講了如何顯示文字、如何操作變數、如何使用迴圈、如何一次宣告大量變數、如何使用條件分歧……有了這些,你就備齊了解決問題前需要的各項工具。

  讓我們一起踏上偉大的爆肝之路吧。
 
引用網址:https://home.gamer.com.tw/TrackBack.php?sn=4919771
All rights reserved. 版權所有,保留一切權利

相關創作

同標籤作品搜尋:第一次踏入墳場就上手|從入門到入墳|從入門到放棄|C語言|程式設計|C|C++|寫程式|程式|回收業者

留言共 27 篇留言

Pekocado
霸...

09-17 20:06

純淨好水
我愛豬腳,謝謝教學

09-17 20:06

兆C
C

09-17 20:07

乃乃
上一篇發文時我才剛要升大學
這篇發文時我已經修完一學期的C了[e21]

09-17 20:08

解凍豬腳
笑死 屌打獵人系列09-17 20:08
曰光
能不能在場外也這樣正常 噁噁噁

09-17 20:10

北極熊
有生之年系列

09-17 20:12

美好的過去漸行漸遠
好電ㄛ

09-17 20:13

怕爆
才華洋溢(錯頻

09-17 20:14

通知達人
我也要C啦

09-17 20:16

雞塊
初學很容易寫出if(15 <= x <= 16)這種code
每次看到都要中風

09-17 20:18

解凍豬腳
Python 是支援這種寫法的,真的很人性化,也很容易把人養慣 [e6]09-17 20:23
美好的過去漸行漸遠
是說如果是用C的話,就沒有class這個語法了吧?

09-17 20:18

解凍豬腳
對,要到 C++,物件導向的概念才比較完整09-17 20:25
淀治脊髓劍‘-ωก̀
可是換成組合語言打起來就有點麻煩

09-17 20:26

Weiiiiiii
教ㄇ...

09-17 20:58

中華民國國G197T二等兵
\csy/

09-17 21:38

你說的對
卡晚點看

09-17 21:40

福利熊送幸福
這篇淺顯易懂 推推

09-17 21:49

しろ
if(小豬腳>30cm){
需要潤滑
}else{
我要進去了
}

09-17 22:12

解凍豬腳
void use687(void){ ... }09-17 22:14
夫風若雪
感謝分享~

09-17 22:41

先生不要這樣好嗎
本來以為這系列停更了

09-17 22:46

⊰⊱求出處學術用⊰⊱
我還以為到程式版

09-17 22:57

Waterlow
我要追到講完c的所有函式庫

09-18 06:37

雪之王女‧F‧巧可奈
外行純推

09-18 08:50

熾心凝
現在看到else if都想寫成elif(python寫太多

09-18 17:02

繁華-POOR THINGS
感謝大佬 我google||意思才看到你有補充 太貼心了吧(´▽`)

09-18 18:12

odie_ou
?:

09-21 00:12

兔子胖
C

09-23 17:11

poko兔
純推舉例XD

03-26 12:26

我要留言提醒:您尚未登入,請先登入再留言

74喜歡★johnny860726 可決定是否刪除您的留言,請勿發表違反站規文字。

前一篇:發現了喜歡的 Vtube... 後一篇:[達人專欄] 只靠圖片連...

追蹤私訊切換新版閱覽

作品資料夾

------------------ (0)

豬腳生活 (1)
日常雜談、巴哈大小事 (193)
煞氣a國中生 (7)
高中生活日誌 (55)
大學生活日誌 (34)
冬令營回憶錄 (19)
也許藏有一些小祕密吧? (3)
各式各樣的開箱文 (11)
貓科動物時間 (15)

------------------ (0)

繪圖創作 (1)
電繪插圖、草稿 (199)
短篇漫畫、單幅標語 (61)
上課太無聊的手繪塗鴉 (8)
不知道該怎麼分類的綜合作品 (18)

文字創作 (1)
草莓兵的國軍紀實 (14)
我與らい的點點滴滴 (12)
那些榮耀的時刻與心跳加速的瞬間 (60)
有感而發的隨筆之作、無法分類的短文 (17)

------------------ (0)

語言學習 (1)
日語:天気がいいから (5)
粵語:唔好再淨係識講粗口喇 (6)
英語:Hey, you! (1)

程式設計及電腦網路 (1)
系列文:跟著豬腳 C 起來 (10)
系列文:論壇網站運作原理 (3)
Go(Golang) (11)
Ruby / RGSS (7)
Visual Basic (13)
JavaScript (1)
各種原理 (17)

思想:多思考一下,世界會更不一樣 (1)
網路經驗、社會觀察 (23)
檸檬庫 (21)

數學:我來拯救你的期中考了 (1)
各類基礎觀念 (5)
國中生也能懂的微積分 (9)
微分方程 (0)

小說:用筆鋒劃出新世界的入口 (1)

繪圖:我也想畫出私巴拉西的美圖 (10)

------------------ (0)

施工中 (22)

不堪回首的痕跡、雜物堆放 (31)

------------------ (0)

未分類 (1)

Triangle360⭐喜歡動畫的你⭐
⭐點我頭像看4月新番快評👉https://home.gamer.com.tw/artwork.php?sn=5921394看更多我要大聲說昨天15:38


face基於日前微軟官方表示 Internet Explorer 不再支援新的網路標準,可能無法使用新的應用程式來呈現網站內容,在瀏覽器支援度及網站安全性的雙重考量下,為了讓巴友們有更好的使用體驗,巴哈姆特即將於 2019年9月2日 停止支援 Internet Explorer 瀏覽器的頁面呈現和功能。
屆時建議您使用下述瀏覽器來瀏覽巴哈姆特:
。Google Chrome(推薦)
。Mozilla Firefox
。Microsoft Edge(Windows10以上的作業系統版本才可使用)

face我們了解您不想看到廣告的心情⋯ 若您願意支持巴哈姆特永續經營,請將 gamer.com.tw 加入廣告阻擋工具的白名單中,謝謝 !【教學】