在這個章節,會學到流程控制。其實生活中有很多時候回應用到結構選擇則和流程控制,像是做選擇時,會因應不同狀況(條件)做出對應的作為(達成或不達成條件的作為)。
除了用在單向選擇(一步一步接下去的步驟,如煮菜),還可以用在重複結構中(如判斷何時終止或何時繼續)。用在重複結構中的終止條件,即是符合了程式的有限性。
流程圖是一個好用的工具,它可以把腦海中的思維轉換成圖形,且是一步一漸進的。一開始也許會覺得沒用,但遇到演算法的推演、大計畫(big project)要處理時,可以簡化難度。
可以用手畫,也可以用電腦畫,下面推薦兩個畫流程圖的網址
draw.io,網址:https://www.draw.io/
Google繪圖,網址:docs.google.com/drawings
先介紹常用的符號(如下圖):
流程圖簡介
舉一個例子,以判斷是否是小數(如下圖):
一、關係(邏輯)運算子
在前一章節(Chapter 04)中,埋了一個伏筆,就是關係運算子。因為流程判斷中常用到,在此先介紹關係運算子。
交集(AND):意思是“且”。多個給定的條件中,使用AND運算即為“給定的條件須都成立時才成立”。C以“&&”表示。
聯集(OR):意思是“或”。多個給定的條件,使用OR運算即只要有任意一個條件成立,這個條件就成立。C以“||”表示。
要注意的是,位元運算子的and是“&”,邏輯運算子的and是“&&”;位元運算子的or是“|”,邏輯運算子的or是“||”。
特別的,C中沒有差集、餘集、補集之類的集合函數,可以利用排容原理來改寫(這就是著名的笛摩根原理)。例如Python有NOT關係運算子。
特別的是,a>b>c要用「a>b && b>c」表示。這個就是數學上的遞移律。
基本概念就是,正確(True)回傳1,錯誤(False)回傳0。講個題外話,在電子的流程控制中,True為關,Flase是開。
語法:資料型態 變數名稱 = 運算式 ? 條件成立時的結果 : 條件不成立時的結果;
e.g. c = a>b ? 'Y':'N'; /*a>b時是Y,a<b時是N*/
範例1.Q你能解釋下面的程式意義嗎?#include<stdio.h> int main(){ int a,b; scanf("%d %d",&a,&b); char c = a==b ? 'Y':'N'; printf("a=b? %c\n",c); c = a>b ? 'Y':'N'; printf("a>b? %c",c); return 0; }
A:
- 先宣告變數a、b。
- 讓使用者輸入兩個變數,分別是a、b
- 使用條件運算子,成立為Y,否定為N。
- 輸出結果
流程圖
另外一種是布林運算,舉例如下:
#include<stdio.h> int main(){ bool a = true;//注意大小寫 bool b = false; printf("%d",a);//ture=1, false=0 printf("\n"); printf("%d",b); return 0; system("pause"); }
三、if條件
if的意思即為中文的「若」,因此可以這麼說「若(條件)•••,就會(做)•••」。語法: if(條件){程式敘述;}e.g.若a等於偶數, 則輸出「偶數」二字。(僅部分程式內容)
if(a%2==0){
printf ("偶數");
}
流程圖(如下):
四、if else條件
中文的「若(if)•••,否則(else)•••」。有一點要注意的,是先if,在else。且else是“當前述的條件都不成立時”,才會有else。
語法:if{
程式敘述;
}
else{
程式敘述;
}
e.g.請問如何用if else寫出「若是偶數則輸出『偶數』,若是奇數則輸出『奇數』。只考慮正整數」#include<stdio.h> int main(){ int n; scanf("%d",&n); if(n%2==0){ printf("偶數"); } else{ printf("奇數"); } /*若if、else區塊中只有一句,則可以不加大括號。else if同理。 合法的: if printf("偶數"); 不合法的: if n=n*10; printf("%d",n); */ return 0; }
五、if else if else
若要在if和else中間加入其他條件,則可以用else if。語法和if、else的語法相同。
語法:else if{
程式敘述;
}
e.g. 「若是偶數則輸出『偶數』,若是奇數則輸出『奇數』,若是0則輸出零,若都不是的則輸出「錯誤」。不考慮非零、非正整數」
#include<stdio.h> int main(){ int n; scanf("%d",&n); if(n==0){ printf("零"); } else if(n%2==0){ printf("偶數"); } else if(n%2==1){ printf("奇數"); } else{ printf("錯誤"); } return 0; }
流程圖(如下):
有趣的,若要加入多個條件可以用多個else if,但有一個前提,必須要先有if描述句,程式是由上而下判斷的,也就是第一個條件不合便會往下判斷,直到有符合條件。
else是當前述條件都不符合時,才會執行。其實else可有可無,若沒有else,完全不符合設定條件的會因沒有指示而不被執行。
這個和if else if很像,但有一點點不同。switch是針對某一個變數進行條件判斷。什麼意思?if else if中,條件不一定是要針對同一個變數,而是可以非常有彈性的使用,後面會給出比較。
語法:
switch(變數名稱){
case'情況一':
程式區塊;
break;
case'情況二':
程式區塊;
break;
default:
程式區塊;
break;/*一般而言,可有可無*/
}
說明:switch中先宣告要判斷哪個變數,加上一對大括號。在case中表示不同的情況,並且加上冒號,注意縮排輸入程式敘述。若都不符合,則加上的default。每一個case或default要加上break,以便跳出switch。可以有多個case,相當於有多個else if一般。
範例&比較
題目:輸入一個運算式,僅限兩個整數做四則運算,如:1+1,結果要表示成「1+1=2」,若都不符合則輸出「不支持該運算式」
#include<stdio.h> #include<stdlib.h> int main() { int a,b;/*宣告兩變數*/ char o;/*一個字元變數,是運算符號(+、-、×、÷)*/ printf("輸入一個運算式,僅限兩個整數做四則運算,如:1+1\n");/*為了方便,引導使用者的話語*/ scanf("%d%c%d",&a,&o,&b);/*收集運算式,如1+1,分別對應變數a、o、b*/ switch(o)/*針對變數o的內容進行判斷*/ { case'+':/*若變數o是+號*/ printf("%d%c%d=%d",a,o,b,a+b); break;/*別忘了要加上break*/ case'-':/*若變數o是-號*/ printf("%d%c%d=%d",a,o,b,a-b); break; case'*': printf("%d%c%d=%d",a,o,b,a*b); break; case'/': printf("%d%c%d=%d",a,o,b,a/b); break; default:/*若都不是*/ printf("不支持該運算式"); break;/*一樣要加上break*/ } return 0; }
上面程式有一點要小小注意一下,題目並無要求輸出提示詞,因此在外比賽(如APCS),是用電腦判斷輸出結果(電腦,或是所謂AI,會丟入一些值進入程式(也就是測資),判斷整個輸出的畫面、結果是否正確),因此不要亂加提示詞,或是做出不符合輸出規範的行為,以免沒拿到分數。上面的提示詞是為了方便學習。
那麼,能不能用if else if寫?可以,但會麻煩一些。由於程式區塊只有一行,筆者懶惰不加大括號,因此要注意縮排。
#include<stdio.h>
#include<stdlib.h>
int main()
{
int a,b;
char o;
printf("輸入一個運算式,僅限兩個整數做四則運算,如:1+1\n");
scanf("%d%c%d",&a,&o,&b);
if(o=='+')/*字元記得加上單括號' '*/
printf("%d%c%d=%d",a,o,b,a+b);
else if(o=='-')
printf("%d%c%d=%d",a,o,b,a-b);
else if(o=='*')
printf("%d%c%d=%d",a,o,b,a*b);
else if(o=='/')
printf("%d%c%d=%d",a,o,b,a/b);
else
printf("不支持該運算式");
return 0;
}
七、本章綜合比較
由於這個章節裡對於同種題目有多種寫法,同時也容易有一些邏輯上的錯誤(其實寫程式就像算數學,也在矯正邏輯),因此整理一些可互換會易搞混的類別進行比較。也可以做為暖身與複習喔!
1.else的有無。
試比較下面兩個程式
程式一:
#include<stdio.h> #include<stdlib.h> int main(){ int n=0; scanf("%d",&n); if(n!=0){ n=pow(n,3); printf("%d",n); } return 0; }
程式二
#include<stdio.h> #include<stdlib.h> int main(){ int n=0; scanf("%d",&n); if(n!=0){ n=pow(n,3); printf("%d",n); } else{ printf("%d",n); } return 0; }
程式二較程式一多了n=0時的狀況要怎麼處理。
2.if else if的先後執行順序
這裡就用一個常見且實用的命題:請寫一個程式,讓使用者輸入西元年份,若是平年則輸出「平年」二字,若是閏年則輸出「閏年」二字(提示:四年一閏,百年不閏,四百年一閏)。
下方則僅舉一個會搞混的,是百年和四百年的誤判。
百年和四百年的誤判:一般而言,假設程式的其他部分都是正確的,應該要先把四百年排除,才能進行百年的判斷,因為四百年的元素包含在百年的元素範圍(可以這麼說,四百年也屬於百年的範圍,因此在此要獨立判斷)
正確解法:
#include<stdio.h> #include<stdlib.h> int main(){ int n=0; scanf("%d",&n); if(n%4!=0){ printf("平年"); } else{ if(n%400==0){ printf("閏年"); } else{ if(n%100==0){ printf("平年"); } else{ printf("閏年"); } } } return 0; }
錯誤解法
#include<stdio.h> #include<stdlib.h> int main(){ int n=0; scanf("%d",&n); if(n%4!=0){ printf("平年"); } else{ if(n%100==0){ printf("平年"); } else{ if(n%400==0){ printf("閏年"); } else{ printf("閏年"); } } } return 0;
其實筆者覺得這道命題是一個不錯又典型的命題,因為這可以當作邏輯錯誤的一個好範例。還記得前面所述的,「『else』是所有條件不成立的時候所用的」,在上面的題目中,符合400的倍數包含在符合100的倍數中,又包含在符合4的倍數中(假如都是4的倍數),因此只要一一排除,即可完成。
為何要強調呢?因為上面的例題可以用窮舉法(就是把所有條件列出來),但這樣會大大提升其複雜度,或是沒有效率,因此這時候透過較為縝密的邏輯來提升效率,又可以偷懶,是不是一舉兩得呢?
那麼窮舉法一定不好嗎?那不一定,若窮舉法的複雜度最低,那基本上就是選窮舉法。可以這麼說,「用程式解決問題時,建議優先選複雜度最低的」。
3.if一定只能判斷一個變數嗎?不。三元運算子只能夠針對一個變數進行判斷,但if else不是。if else是你開什麼條件,它就判斷什麼條件,後面章節會用到,期待一下吧!4.switch case可以像else一樣不要default嗎?
不行。default基本上在此是必要的。5.可以switch case中的break可以省略嗎?
在switch case中,只有default的break可以省略,其他的都不可以省略。若case中不加入break,會使得程式停留在該case中,因為case是一種程式區塊,break在C中就是跳出程式區塊(在後面的章節會講到對於程式區塊的終止、跳過等的處理)。
八、綜合範例
由於在前面的說明中有講解了一些範例,因此在此就直接帶入一些比較需要稍稍思考的範例。例:讓使用者第一行輸入一個整數(可以是0、正整數、負整數),判斷是正整數、0、負整數,並且輸出絕對值(若n=0則不輸出絕對值)。輸出的結果要再第二行,二個答案中間水平跳格一次。
答:
#include<stdio.h>
#include<stdlib.h>
int main(){
int n=0;
scanf("%d",&n);
/*判斷整數類型*/
if(n==0){
printf("0");
}
else{
if(n>0){
printf("正整數");
}
else{
printf("負整數");
}
}
printf("\t");
/*下方是取絕對值*/
if(n!=0) if(n<0 0="" d="" else="" n="" pre="" printf="" return="">0>
其實,去絕對值有另外一種方法。就是使用math.h中的abs();。
#include<stdio.h> #include<math.h> int main(){ int n=0; scanf("%d",&n); if(n==0){ printf("0"); } else{ if(n>0){ printf("正整數"); } else{ printf("負整數"); } } printf("\t"); /*用abs函數取絕對值*/ n=abs(n); printf("%d",n); return 0; }
九、實力挑戰
PART1.概念題
(一)簡答題(答案前面都有,試用自己的話來講)
1.試簡答case怎麼使用2.試解釋窮苦法和排容原理寫出來的if else差異。
(二)偵錯題
試偵錯下列局部的程式區塊(¢是一種自定義的計算方式,不考慮¢的對錯)case'¢':printf("%d",n%10*4-10);
PART2.實作挑戰
1.請寫出一個程式,讓使用者在第一行輸入分數(小數點以下兩位);第二行輸出及格與否(及格是60分),及格輸出「及格」,不及格輸出「不及格」;在第三行輸出距離及格分數的距離(差)(取整數,無條件捨去)。(提示:先判斷及不及格,記得換行!再用絕對值取值,並無條件捨去。)
2.讓使用者輸入,讓程式判斷這個數是不是五的倍數且不是十的倍數。第一行是輸入的值,第二行輸出「是」或「不是」。(不考慮負數或非整數)。參考解答:
#include<stdio.h> #include<math.h> int main(){ int n; scanf("%d",&n); if(n%5==0 && n%10!=0){ printf("是"); } else{ printf("不是"); } return 0; }
同樣的,實力挑戰的部分題目這次不會給解答,因為難度不高,且在這個小節裡,基本上就是一直判斷,因此各位讀者應該能夠交出令人滿意的答卷吧!
跟各位讀者提醒一下,這個章節有大兩點務必學好,一是邏輯處理(e.g.排容原理的應用),二是if else if的變化與熟稔度(後面基本上會很常遇到)。前面的一些題目雖然沒有畫流程圖給各位,但各位讀者可以自己試著畫,並且講講你的想法喔!
沒有留言:
張貼留言