條件控制敘述與邏輯運算式

 

 
實 習 內 容






1. 數值與邏輯值

2. 邏輯運算式

3. if 敘述

4. switch 敘述

1

邏輯值:

C 程式中沒有設計特別的資料型態來表示邏輯的 "真"/"偽", 任何資料都可以解釋為邏輯的真或是偽:

資料中每一個位元都是 0 的解釋為 邏輯的 「偽 (false)」,
     只要有任何位元不是 0 就解釋為 邏輯的 「真 true」

如果直接把那個資料的所有位元用二進位整數去解釋

數值 0 解釋為 邏輯的 「偽 (false)」, 所有非 0 的數值 解釋為 邏輯的 「真 true」

(在 C++ 語言中, 使用一個新增加的型態 bool 來表示邏輯值, 使用範例如 bool x; x = true; 或是 x = false;)

編譯並且輸入各種數值來測試下列程式:

#include <stdio.h>
int main(void)
{
    int x;
    printf("Please input an integer: ");
    scanf("%d", &x);
    if (x)
    {
        printf("true\n");
    }
    else
    {
        printf("false\n");
    }
    return 0;
}

請由鍵盤輸入下列紅字部份

11 (x = 11;)

0 (x = 0;)

-5 (x = -5;)

#include <stdio.h>
int main(void)
{
    double x;
    printf("Please input a real number: ");
    scanf("%lf", &x);
    if (x)
        printf("true\n");
    else
        printf("false\n");
    return 0;
}

1.23 (x = 1.23;)

1.0 (x = 1.0;)

0.4 (x = 0.4;)

0.0 (x = 0.0;)

-0.4 (x = -0.4;)

-5.78 (x = -5.78;)

#include <stdio.h>
int main(void)
{
    char x;
    printf("Please input a character: ");
    scanf("%c", &x);
    if (x)
        printf("true\n");
    else
        printf("false\n");
    return 0;
}

b (x = 'b';)

R (x = 'R';)

0 (x = '0';)

1 (x = '1';)

" (x = '"';)

space (x = ' ';)

上面的測試中, 你也可以用右邊括號裡面的敘述來取代左邊程式裡的 printf() 及 scanf()

char 型態的變數裡面存放的是「那個字元的內碼」,在這裡是 ASCII 編碼 ,你也可以在 if 敘述前面加上 printf("x=%d", x); 來用十進位格式看看所存放的字元的編碼值

space 代表 "鍵盤上面的空格鍵"

2

邏輯運算式:

在一個可以執行的程式中測試下列敘述:

運算子

範例
==, !=

int x; x = 15; if (x == 10) { ... } else { ... }

int z; z = 10; if (z != 11) { ... } else { ... }

char y; y = 'z'; if (y == 'z') { ... } else { ... }

double z; z = 0.3+0.15; if (z == 0.45) ...
if (0.1 + 0.2 == 0.3) ...

這兩個敘述可能都不會有你預期的結果,稍後會另加說明。

>=, >

int x; x = 15; if (x >= 15) { ... } else { ... }

char y; y = 'z'; if (y >= 'a') { ... } else { ... }

double z; z = 0.3+0.15; if (z >= 0.45) { ... } else { ... }

大部分情況應該是可以預期的, 偶而會發生一些狀況, 如果你心裡曉得浮點數在使用有限的位元數表示時或是在十進位和二進位之間轉換時,是會發生誤差的,應該就可以解釋你所看到的現象;
你還可以用 printf("%.20f\n", z); 來看一下 z 的數值,但是留心超過 15 位以後已經在誤差範圍內了,並不具意義。

<=, <

int x; x = 15; if (x <= 15) { ... } else { ... }

char y; y = 'z'; if (y <= 'a') { ... } else { ... }

double z; z = 0.15*3; if (z < 0.45) { ... } else { ... }

&&
int x = 3;
double y = 4.5;
if ( (x>10) && (x<20) && (y>0.15) ) { ... } else { ... }
||

int x = 3;
double y = 14.5;
if ( (x>10) || (y>0.15) ) { ... } else { ... }

if ( ((x>10)&&(x<20)) || (x < -5.1) ) { ... } else { ... }

!
double y = 14.5;
if ( !(y>0.15) ) { ... } else { ... }

==, !=, >, >=, <, <= 這些運算子叫做 "關係運算子 (relational operator)"

&&, ||, ! 叫做 "邏輯運算子 (logical operator)"

接下來請測試下列程式

int x=0;
if (x=0)
    printf("x is 0\n");
else
    printf("x is not 0\n");

告訴我執行的結果是?? 為什麼有這種結果?

再試一下

int x=0;
if (0=x)
    printf("x is 0\n");
else
    printf("x is not 0\n");

你會發現上面這個 0=x 的寫法是編譯器不能接受的, 因為一個等號是資料的設定, 等號左手邊一定要是一個變數, 但是上面是 0, 所以編譯器不能接受, 當然如果你寫 0 == x, 這是完全合法的比對大小, 你就會發現如果你習慣打 0 == x, 當你不小心漏打一個等號的時候, 編譯器也會陰錯陽差地幫你找到問題...

另外有一個很邪惡的錯誤是這樣的,下面的條件看起來數學上是完全可以理解的,試試看,你會發現詭異的結果:

int x=2;
if (3<=x<10)
    printf("%d is inside the region [3, 10)\n", x);
else
    printf("%d is outside the region [3, 10)\n", x);
會印出 2 is inside the region [3,10),哇! 為什麼會這樣?怎麼可以這樣? 太邪惡啦!

其實沒有人很邪惡啦,學了十幾年的數學表示方法使得你很直覺地想要寫上面的敘述,但是當編譯器看到 3<=x<10 的邏輯運算式時,它會像在加減乘除的運算式中決定誰先加、誰先乘一樣來決定 <= 和 < 兩個運算的先後順序,會把 3<=x<10 看成 ((3<=x)<10),於是 (3<=2) 運算後會得到 0,(0<10) 運算後會得到 1,所以執行的時候就列印出 2 is inside the region [3,10) 的結果。

哈哈,所以編譯器很邪惡!! 機器也很邪惡!!

其實沒有啦,它只是笨而已,看你的程式時只有語法裡既定的一種解釋方法。

3

if 敘述練習

請根據下列流程圖撰寫 if 敘述來完成, 請特別注意 "大括號的使用"。

  範例程式碼

上述流程圖是用來輔助設計 "複雜" 的條件邏輯的, 並不是先寫完程式, 測試正確以後再畫出來欣賞的, 既然如此,你覺得上面這個流程有什麼不好的呢?

好像不太規律哦! 看起來沒有比直接看程式好到哪裡去! 畫流程圖也是有技巧的, 流程圖仔細設計出來以後, 很容易運用它檢查邏輯的完整性, 可是隨便畫的流程圖就不一定可以達成這個目的, 先寫出程式再根據它畫的就很容易是很不規律的。下面這張圖有稍微好一點嗎? (什麼地方不一樣了?)

  範例程式碼

你的程式應該要避免出現多層的 if 敘述 (巢狀的 if 敘述, nested-if)

if ( ... )
{
    ...
    if ( ... )
    {
        ...
    }
    else
    {
        ...
        if ( ... )
        {
            ...
        }
    }
}
else
{
    ...
}

請在合適的地方使用 "多選項的 if (multiple alternative if)" 敘述實現扁平化的邏輯判斷來簡化程式
if ( ... )
{
    ...
}
else if ( ... )
{
    ...
}
else if ( ... )
{
    ...
}
else
{
    ...
}

只有一層的條件敘述是比較容易了解的, 請根據下圖重新設計這個條件測試!

  範例程式碼

請注意: 程式的最終目的是讓電腦執行你教它的演算法, 照理說讓編譯器看得懂, 執行結果對了就好, 可是請注意, 你的程式是需要修改, 需要新增功能, 需要維護的, 電腦不會做這些事, 是你需要做的, 你要進行這些事情之前, 你需要看懂程式碼在寫什麼, 你需要讀懂自己寫的程式 (也許是三個月後或是三年後), 你也需要讀懂別人寫的程式, 你才能夠正確地修改程式或是新增功能, 所以程式一定要寫成 "人很容易看懂" 的形式。根據統計(有人實驗把寫程式時操作編輯器的動作記錄下來, 分析他輸入程式和上移, 下移觀看程式的比例), 寫程式的人花在讀程式的時間和寫程式的時間的比例至少是 10 比 1 (你需要看懂程式的上下相關的部份才能夠寫出正確運作的程式)。所以請不要故意設計複雜的邏輯, 不要用不具意義或是容易誤導的變數名稱或是函數名稱, 讓同學, 助教, 或是我看不懂也許有一點厲害, 不過副作用是讓自己也看不懂或是很容易誤解, 當你需要花很多時間才能夠看懂自己寫的程式的時候, 等到看懂了你就已經沒力氣去加新的功能了, 常常寫這樣的程式, 習慣了以後就是在跟自己的時間與精力過不去, 你的工作效率會因此低落, 你的工作夥伴會很厭惡你 (當然不會直接跟你說啦! 但是會躲著你), 不是很聰明的一個作法。下面是幾段常常在資訊界被引用的話, 都跟上面講的東西有關係

Any fool can write code that a computer can understand. Good programmers write code that humans can understand. (Anonymous)

Any code of your own that you haven't looked at for six or more months might as well have been written by someone else. (Eagleson's law)

Programs must be written for people to read, and only incidentally for machines to execute. (Abelson / Sussman)

Programming can be fun, so can cryptography; however they should not be combined. (Kreitzberg and Shneiderman)

4

switch 敘述練習

根據下表請運用 "switch" 敘述寫一小段程式, 根據變數 watts 裡面記錄的燈泡瓦數, 找到對應的燈泡亮度記錄在變數 lumens 裡面, 作一個很簡單的資訊轉換:

Watts Brightness (in Lumens)

15

125
25 215
40 500
60 880
75 1000
100 1675

例如:

int watts, lumens=-1;
scanf("%d", &watts);
switch (watts)
{
case 15:
    lumens = 125;
    break;
case 25:
    lumens = 215;
    break;
...
default:
    printf(":)\n");
}
if (lumens>0) printf("%d\n", lumens);

  範例程式碼

很多時候因為轉換沒有很簡單的公式, 所以就一個一個地比對然後轉換, 以後如果你知道迴圈陣列的語法, 就可以用更簡潔的方式來撰寫,也不怕表格內容太多了。

請注意:

  1. "switch" 敘述的控制變數只能是 int 或是char 型態, 語法上不允許是float 或是 double 型態.
  2. 仔細想一下, 記憶比對其實就是最基本的智慧表現 (記不住東西的人, 沒辦法和過去經歷過情境比對的人...就是腦包了)

5

浮點數的測試: 不要使用 == 或是 != 來測試浮點數或是倍精準的浮點數

請編譯、執行、並且仔細觀察下列程式的執行結果:

#include <stdio.h>

int main(void)
{
    double x = 3.13;
    printf("x = %20.16f,   3.13 is %20.16f\n", x, 3.13);
    if (x == 3.13)
        printf("x is 3.13\n");
    else
        printf("x is not 3.13\n");
            
    x = 3.09;
    x = x / 2.0 + 1.51; // 1.545 + 1.51 should be 3.055
    printf("x = %20.16f,   3.055 is %20.16f\n", x, 3.055);
    if (x == 3.055)
        printf("x is 3.055\n");
    else
        printf("x is not 3.055\n");
        
    if ((x>3.055*(1-1e-10))&&(x<3.055*(1+1e-10)))
        printf("x is close to 3.055\n");
    else 
        printf("x is not close to 3.055\n");

    return 0;
}

測試程式 testDouble.exe

鍵盤輸入的情況和直接設定的情況接近 (例如: double x; scanf("%lf", &x); if (x!=0){ ... })

6

請寫一個程式, 由鍵盤輸入一個英文字母字元, 如果是小寫字元請轉換為大寫字元, 將轉換過的字元列印出來, 如果是大寫字元, 就直接印出。請注意這個轉換是不需要用表格的, 可以找到很簡單的轉換公式

'a' - 'z' 在 ASCII 編碼表格中是連續的 26 個數字
'A' - 'Z' 也是連續的 26 個數字

如果找到這兩段數值平移的差距 ('a' - 'A') 就可以很快地轉換了

範例執行程式

範例執行結果

Please input a character (e.g. e): e
input: e     output: E

Please input a character (e.g. e): A
input: A     output: A

7

線上繳交

請寫一個程式, 由鍵盤輸入小於1000的十進位非負整數 (e.g. 123 或 15) 轉換並且列印出對應的中文數字 (e.g. 一百二十三 或是 十五), 測試完成以後, 請上傳 程式

範例執行程式
完成以後, 請在 e-Tutor 線上繳交, 繳交截止時間 110/10/12 (三) 21:00

最直接的想法是看到一個阿拉伯數字立刻就輸出對應的中文數字: 請參考 arabic to chinese

不過我們要求你寫的程式不像上面這個那麼直接, 原因是如果你看到 11, 你不會直接翻譯唸成『一一』,也不是直接把『十』、『百』加進來唸 『一十一』, 而是唸成『十一』; 看到 20 不是唸成『二零』更不是『二十零』而是『二十』; 看到 105 不是唸成『一零五』更不是『一百零十五』而是『一百零五』; 這些例子告訴你的是這個轉換規則有點複雜, 不能用單一的轉換規則來做, 會和輸入數字的範圍有一些關係


完整的轉換表 (太多了, 不能像步驟 3 或是 4 中直接用 if 或是 switch 把整個表格變成程式碼來轉換, 萬一需要轉換 1 到 100000 的數字怎麼辦?)


轉換程式的流程圖 (請注意: 流程圖對於設計這種比較複雜的控制敘述特別有用)

對於你的程式請特別測試下列數字的轉換:

    5
    10
    15 十五
    25 二十五
    30 三十
    100 一百
    105 一百零五
    110 一百一十
    125 一百二十五

詳細設計請參考解說投影片 (bw, splitted)

程式設計課程 首頁

製作日期: 10/06/2012 by 丁培毅 (Pei-yih Ting)
E-mail: [email protected] TEL: 02 24622192x6615
海洋大學 電機資訊學院 資訊工程學系 Lagoon