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 篇留言
純淨好水:
我愛豬腳,謝謝教學
09-17 20:06
乃乃:
上一篇發文時我才剛要升大學
這篇發文時我已經修完一學期的C了[e21]
09-17 20:08
解凍豬腳:
笑死 屌打獵人系列
09-17 20:08
曰光:
能不能在場外也這樣正常 噁噁噁
09-17 20:10
雞塊:
初學很容易寫出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
しろ:
if(小豬腳>30cm){
需要潤滑
}else{
我要進去了
}
09-17 22:12
解凍豬腳:
void use687(void){ ... }
09-17 22:14
熾心凝:
現在看到else if都想寫成elif(python寫太多
09-18 17:02
poko兔:
純推舉例XD
03-26 12:26
我要留言提醒:您尚未登入,請先
登入再留言
74喜歡★johnny860726 可決定是否刪除您的留言,請勿發表違反站規文字。
前一篇:發現了喜歡的 Vtube...
後一篇:[達人專欄] 只靠圖片連...