2024年3月9日 星期六

Chapter 05:條件判別

  目標:
  1. 知道流程圖
  2. 認識關係(邏輯)運算子
  3. 學會布林運算
  4. 學會if條件
  5. 學會if...else...條件
  6. 學會if...else if...else條件
  7. 學會switch...case句型
筆者小講堂:

在這個章節,會學到流程控制。其實生活中有很多時候回應用到結構選擇則和流程控制,像是做選擇時,會因應不同狀況(條件)做出對應的作為(達成或不達成條件的作為)。 

除了用在單向選擇(一步一步接下去的步驟,如煮菜),還可以用在重複結構中(如判斷何時終止或何時繼續)。用在重複結構中的終止條件,即是符合了程式的有限性。

零、流程圖

流程圖是一個好用的工具,它可以把腦海中的思維轉換成圖形,且是一步一漸進的。一開始也許會覺得沒用,但遇到演算法的推演、大計畫(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:

  1. 先宣告變數a、b。
  2. 讓使用者輸入兩個變數,分別是a、b
  3. 使用條件運算子,成立為Y,否定為N。
  4. 輸出結果

流程圖

 


另外一種是布林運算,舉例如下:

#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,完全不符合設定條件的會因沒有指示而不被執行。 

六、switch case

這個和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="">

 

其實,去絕對值有另外一種方法。就是使用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的變化與熟稔度(後面基本上會很常遇到)。前面的一些題目雖然沒有畫流程圖給各位,但各位讀者可以自己試著畫,並且講講你的想法喔! 

沒有留言:

張貼留言