# 第?4?章?分支語句
**目錄**
+ [1\. if語句](ch04s01.html)
+ [2\. if/else語句](ch04s02.html)
+ [3\. 布爾代數](ch04s03.html)
+ [4\. switch語句](ch04s04.html)
## 1.?if語句
目前我們寫的簡單函數中可以有多條語句,但這些語句總是從前到后順序執行的。除了順序執行之外,有時候我們需要檢查一個條件,然后根據檢查的結果執行不同的后續代碼,在C語言中可以用分支語句(Selection Statement)實現,比如:
```
if (x != 0) {
printf("x is nonzero.\n");
}
```
其中`x != 0`表示“x不等于0”的條件,這個表達式稱為控制表達式(Controlling Expression)如果條件成立,則{}中的語句被執行,否則{}中的語句不執行,直接跳到}后面。`if`和控制表達式改變了程序的控制流程(Control Flow),不再是從前到后順序執行,而是根據不同的條件執行不同的語句,這種控制流程稱為分支(Branch)。上例中的!=號表示“不等于”,像這樣的運算符有:
**表?4.1.?關系運算符和相等性運算符**
| 運算符 | 含義 |
| --- | --- |
| == | 等于 |
| != | 不等于 |
| > | 大于 |
| < | 小于 |
| >= | 大于或等于 |
| <= | 小于或等于 |
注意以下幾點:
1. 這里的==表示數學中的相等關系,相當于數學中的=號,初學者常犯的錯誤是在控制表達式中把==寫成=,在C語言中=號是賦值運算符,兩者的含義完全不同。
2. 如果表達式所表示的比較關系成立則值為真(True),否則為假(False),在C語言中分別用`int`型的1和0表示。如果變量`x`的值是-1,那么`x>0`這個表達式的值為0,`x>-2`這個表達式的值為1。
3. 在數學中`a<b<c`表示`b`既大于`a`又小于`c`,但作為C語言表達式卻不是這樣。以上幾種運算符都是左結合的,請讀者想一下這個表達式應如何求值。
4. 這些運算符的兩個操作數應該是相同類型的,兩邊都是整型或者都是浮點型可以做比較,但兩個字符串不能做比較,在[第?1.5?節 “比較字符串”](ch25s01.html#stdlib.strcmp)我們會介紹比較字符串的方法。
5. ==和!=稱為相等性運算符(Equality Operator),其余四個稱為關系運算符(Relational Operator),相等性運算符的優先級低于關系運算符。
總結一下,`if (x != 0) { ... }`這個語句的計算順序是:首先求`x != 0`這個表達式的值,如果值為0,就跳過{}中的語句直接執行后面的語句,如果值為1,就先執行{}中的語句,然后再執行后面的語句。事實上控制表達式取任何非0值都表示真值,例如`if (x) { ... }`和`if (x != 0) { ... }`是等價的,如果`x`的值是2,則`x != 0`的值是1,但對于`if`來說不管是2還是1都表示真值。
和`if`語句相關的語法規則如下:
語句?→?if?(控制表達式)?語句
語句?→?{?語句列表?}
語句?→?;
在C語言中,任何允許出現語句的地方既可以是由;號結尾的一條語句,也可以是由{}括起來的若干條語句或聲明組成的語句塊(Statement Block),語句塊和上一章介紹的函數體的語法相同。注意語句塊的}后面不需要加;號。如果}后面加了;號,則這個;號本身又是一條新的語句了,在C語言中一個單獨的;號表示一條空語句(Null Statement)。上例的語句塊中只有一條語句,其實沒必要寫成語句塊,可以簡單地寫成:
```
if (x != 0)
printf("x is nonzero.\n");
```
語句塊中也可以定義局部變量,例如:
```
void foo(void)
{
int i = 0;
{
int i = 1;
int j = 2;
printf("i=%d, j=%d\n", i, j);
}
printf("i=%d\n", i); /* cannot access j here */
}
```
和函數的局部變量同樣道理,每次進入語句塊時為變量`j`分配存儲空間,每次退出語句塊時釋放變量`j`的存儲空間。語句塊也構成一個作用域,和[例?3.6 “作用域”](ch03s04.html#func.scope)的分析類似,如果整個源文件是一張大紙,`foo`函數是蓋在上面的一張小紙,則函數中的語句塊是蓋在小紙上面的一張更小的紙。語句塊中的變量`i`和函數的局部變量`i`是兩個不同的變量,因此兩次打印的`i`值是不同的;語句塊中的變量`j`在退出語句塊之后就沒有了,因此最后一行的`printf`不能打印變量`j`,否則編譯器會報錯。語句塊可以用在任何允許出現語句的地方,不一定非得用在`if`語句中,單獨使用語句塊通常是為了定義一些比函數的局部變量更“局部”的變量。
### 習題
1、以下程序段編譯能通過,執行也不出錯,但是執行結果不正確(根據[第?3?節 “程序的調試”](ch01s03.html#intro.debug)的定義,這是一個語義錯誤),請分析一下哪里錯了。還有,既然錯了為什么編譯能通過呢?
```
int x = -1;
if (x > 0);
printf("x is positive.\n");
```
## 2.?if/else語句
`if`語句還可以帶一個`else`子句(Clause),例如:
```
if (x % 2 == 0)
printf("x is even.\n");
else
printf("x is odd.\n");
```
這里的%是取模(Modulo)運算符,`x%2`表示`x`除以2所得的余數(Remainder),C語言規定%運算符的兩個操作數必須是整型的。兩個正數相除取余數很好理解,如果操作數中有負數,結果應該是正是負呢?C99規定,如果`a`和`b`是整型,`b`不等于0,則表達式`(a/b)*b+a%b`的值總是等于`a`,再結合[第?5?節 “表達式”](expr.expression.html "5.?表達式")講過的整數除法運算要Truncate Toward Zero,可以得到一個結論:_%運算符的結果總是與被除數同號_(想一想為什么)。其它編程語言對取模運算的規定各不相同,也有規定結果和除數同號的,也有不做明確規定的。
取模運算在程序中是非常有用的,例如上面的例子判斷`x`的奇偶性(Parity),看`x`除以2的余數是不是0,如果是0則打印`x is even.`,如果不是0則打印`x is odd.`,讀者應該能看出`else`在這里的作用了,如果在上面的例子中去掉`else`,則不管`x`是奇是偶,`printf("x is odd.\n");`總是執行。為了讓這條語句更有用,可以把它封裝(Encapsulate)成一個函數:
```
void print_parity(int x)
{
if (x % 2 == 0)
printf("x is even.\n");
else
printf("x is odd.\n");
}
```
把語句封裝成函數的基本步驟是:_把語句放到函數體中,把變量改成函數的參數_。這樣,以后要檢查一個數的奇偶性只需調用這個函數而不必重復寫這條語句了,例如:
```
print_parity(17);
print_parity(18);
```
`if/else`語句的語法規則如下:
語句?→?if?(控制表達式)?語句?else?語句
右邊的“語句”既可以是一條語句,也可以是由{}括起來的語句塊。一條`if`語句中包含一條子語句,一條`if/else`語句中包含兩條子語句,子語句可以是任何語句或語句塊,當然也可以是另外一條`if`或`if/else`語句。根據組合規則,`if`或`if/else`可以嵌套使用。例如可以這樣:
```
if (x > 0)
printf("x is positive.\n");
else if (x < 0)
printf("x is negative.\n");
else
printf("x is zero.\n");
```
也可以這樣:
```
if (x > 0) {
printf("x is positive.\n");
} else {
if (x < 0)
printf("x is negative.\n");
else
printf("x is zero.\n");
}
```
現在有一個問題,類似`if (A) if (B) C; else D;`形式的語句怎么理解呢?可以理解成
```
if (A)
if (B)
C;
else
D;
```
也可以理解成
```
if (A)
if (B)
C;
else
D;
```
在[第?1?節 “繼續Hello World”](ch02s01.html#expr.helloworldcont)講過,C代碼的縮進只是為了程序員看起來方便,實際上對編譯器不起任何作用,你的代碼不管寫成上面哪一種縮進格式,在編譯器看起來都是一樣的。那么編譯器到底按哪種方式理解呢?也就是說,`else`到底是和`if (A)`配對還是和`if (B)`配對?很多編程語言的語法都有這個問題,稱為Dangling-else問題。C語言規定,_`else`總是和它上面最近的一個`if`配對_,因此應該理解成`else`和`if (B)`配對,也就是按第二種方式理解。如果你寫成上面第一種縮進的格式就很危險了:你看到的是這樣,而編譯器理解的卻是那樣。如果你希望編譯器按第一種方式理解,應該明確加上{}:
```
if (A) {
if (B)
C;
} else
D;
```
順便提一下,浮點型的精度有限,不適合用==運算符做精確比較。以下代碼可以說明問題:
```
double i = 20.0;
double j = i / 7.0;
if (j * 7.0 == i)
printf("Equal.\n");
else
printf("Unequal.\n");
```
不同平臺的浮點數實現有很多不同之處,在我的平臺上運行這段程序結果為`Unequal`,即使在你的平臺上運行結果為`Equal`,你再把`i`改成其它值試試,總有些值會使得結果為`Unequal`。等學習了[第?4?節 “浮點數”](ch14s04.html#number.float)你就知道為什么浮點型不能做精確比較了。
### 習題
1、寫兩個表達式,分別取整型變量`x`的個位和十位。
2、寫一個函數,參數是整型變量`x`,功能是打印`x`的個位和十位。
## 3.?布爾代數
在[第?1?節 “if語句”](ch04s01.html#cond.if)講過,`a<b<c`不表示`b`既大于`a`又小于`c`,那么如果想表示這個含義該怎么寫呢?可以這樣:
```
if (a < b) {
if (b < c) {
printf("b is between a and c.\n");
}
}
```
我們也可以用邏輯與(Logical AND)運算符表示這兩個條件同時成立。邏輯與運算符在C語言中寫成兩個&號(Ampersand),上面的語句可以改寫為:
```
if (a < b && b < c) {
printf("b is between a and c.\n");
}
```
對于`a < b && b < c`這個控制表達式,要求“`a < b`的值非0”和“`b < c`的值非0”這兩個條件同時成立整個表達式的值才為1,否則整個表達式的值為0。也就是只有兩個條件都為真,它們做邏輯與運算的結果才為真,有一個條件為假,則邏輯與運算的結果為假,如下表所示:
**表?4.2.?AND的真值表**
| A | B | A AND B |
| --- | --- | --- |
| 0 | 0 | 0 |
| 0 | 1 | 0 |
| 1 | 0 | 0 |
| 1 | 1 | 1 |
這種表稱為真值表(Truth Table)。注意邏輯與運算的操作數以非0表示真以0表示假,而運算結果以1表示真以0表示假(類型是`int`),我們忽略這些細微的差別,在表中全部以1表示真以0表示假。C語言還提供了邏輯或(Logical OR)運算符,寫成兩個|線(Pipe Sign),邏輯非(Logical NOT)運算符,寫成一個!號(Exclamation Mark),它們的真值表如下:
**表?4.3.?OR的真值表**
| A | B | A OR B |
| --- | --- | --- |
| 0 | 0 | 0 |
| 0 | 1 | 1 |
| 1 | 0 | 1 |
| 1 | 1 | 1 |
**表?4.4.?NOT的真值表**
| A | NOT A |
| --- | --- |
| 0 | 1 |
| 1 | 0 |
邏輯或表示兩個條件只要有一個為真,它們做邏輯或運算的結果就為真,只有兩個條件都為假,邏輯或運算的結果才為假。邏輯非的作用是對原來的邏輯值取反,原來是真的就是假,原來是假的就是真。邏輯非運算符只有一個操作數,稱為單目運算符(Unary Operator),以前講過的加減乘除、賦值、相等性、關系、邏輯與、邏輯或運算符都有兩個操作數,稱為雙目運算符(Binary Operator)。
關于邏輯運算的數學體系稱為布爾代數(Boolean Algebra),以它的創始人布爾命名。在編程語言中表示真和假的數據類型叫做布爾類型,在C語言中通常用`int`型來表示,非0表示真,0表示假<sup>[[6](#ftn.id2719335)]</sup>。布爾邏輯是寫程序的基本功之一,程序中的很多錯誤都可以歸因于邏輯錯誤。以下是一些布爾代數的基本定理,為了簡潔易讀,真和假用1和0表示,AND用*號表示,OR用+號表示(從真值表可以看出AND和OR運算確實有點像乘法和加法運算),NOT用?表示,變量`x`、`y`、`z`的值可能是0也可能是1。
```
??x=x
x*0=0
x+1=1
x*1=x
x+0=x
x*x=x
x+x=x
x*?x=0
x+?x=1
x*y=y*x
x+y=y+x
x*(y*z)=(x*y)*z
x+(y+z)=(x+y)+z
x*(y+z)=x*y+x*z
x+y*z=(x+y)*(x+z)
x+x*y=x
x*(x+y)=x
x*y+x*?y=x
(x+y)*(x+?y)=x
?(x*y)=?x+?y
?(x+y)=?x*?y
x+?x*y=x+y
x*(?x+y)=x*y
x*y+?x*z+y*z=x*y+?x*z
(x+y)*(?x+z)*(y+z)=(x+y)*(?x+z)
```
除了第1行之外,這些公式都是每兩行一組的,每組的兩個公式就像對聯一樣:把其中一個公式中的*換成+、+換成*、0換成1、1換成0,就變成了與它對稱的另一個公式。這些定理都可以通過真值表證明,更多細節可參考有關數字邏輯的教材,例如[[數字邏輯基礎]](bi01.html#bibli.vhdl "Fundamentals of Digital Logic with VHDL Design")。我們將在本節的練習題中強化訓練對這些定理的理解。
目前為止介紹的這些運算符的優先級順序是:!高于* / %,高于+ -,高于> < >= <=,高于== !=,高于&&,高于||,高于=。寫一個控制表達式很可能同時用到這些運算符中的多個,如果記不清楚運算符的優先級一定要多套括號。我們將在[第?4?節 “運算符總結”](ch16s04.html#op.summary)總結C語言所有運算符的優先級和結合性。
### 習題
1、把代碼段
```
if (x > 0 && x < 10);
else
printf("x is out of range.\n");
```
改寫成下面這種形式:
```
if (____ || ____)
printf("x is out of range.\n");
```
____應該怎么填?
2、把代碼段:
```
if (x > 0)
printf("Test OK!\n");
else if (x <= 0 && y > 0)
printf("Test OK!\n");
else
printf("Test failed!\n");
```
改寫成下面這種形式:
```
if (____ && ____)
printf("Test failed!\n");
else
printf("Test OK!\n");
```
____應該怎么填?
3、有這樣一段代碼:
```
if (x > 1 && y != 1) {
...
} else if (x < 1 && y != 1) {
...
} else {
...
}
```
要進入最后一個`else`,x和y需要滿足條件____ || ____。這里應該怎么填?
4、以下哪一個if判斷條件是多余的可以去掉?這里所謂的“多余”是指,某種情況下如果本來應該打印`Test OK!`,去掉這個多余條件后仍然打印`Test OK!`,如果本來應該打印`Test failed!`,去掉這個多余條件后仍然打印`Test failed!`。
```
if (x<3 && y>3)
printf("Test OK!\n");
else if (x>=3 && y>=3)
printf("Test OK!\n");
else if (z>3 && x>=3)
printf("Test OK!\n");
else if (z<=3 && y>=3)
printf("Test OK!\n");
else
printf("Test failed!\n");
```
* * *
<sup>[[6](#id2719335)]</sup> C99也定義了專門的布爾類型`_Bool`,但目前沒有被廣泛使用。
## 4.?switch語句
`switch`語句可以產生具有多個分支的控制流程。它的格式是:
switch?(控制表達式)?{
case?常量表達式:?語句列表
case?常量表達式:?語句列表
...
default:?語句列表
}
例如以下程序根據傳入的參數1~7分別打印Monday~Sunday:
**例?4.1.?switch語句**

如果傳入的參數是2,則從`case 2`分支開始執行,先是打印相應的信息,然后遇到`break`語句,它的作用是跳出整個`switch`語句塊。C語言規定各`case`分支的常量表達式必須互不相同,如果控制表達式不等于任何一個常量表達式,則從`default`分支開始執行,通常把`default`分支寫在最后,但不是必須的。使用`switch`語句要注意幾點:
1. `case`后面跟表達式的必須是常量表達式,這個值和全局變量的初始值一樣必須在編譯時計算出來。
2. [第?2?節 “if/else語句”](ch04s02.html#cond.ifelse)講過浮點型不適合做精確比較,所以C語言規定`case`后面跟的必須是整型常量表達式。
3. 進入`case`后如果沒有遇到`break`語句就會一直往下執行,后面其它`case`或`default`分支的語句也會被執行到,直到遇到`break`,或者執行到整個`switch`語句塊的末尾。通常每個`case`后面都要加上`break`語句,但有時會故意不加`break`來利用這個特性,例如:
**例?4.2.?缺break的switch語句**

`switch`語句不是必不可缺的,顯然可以用一組`if ... else if ... else if ... else ...`代替,但是一方面用`switch`語句會使代碼更清晰,另一方面,有時候編譯器會對`switch`語句進行整體優化,使它比等價的`if/else`語句所生成的指令效率更高。
- Linux C編程一站式學習
- 歷史
- 前言
- 部分?I.?C語言入門
- 第?1?章?程序的基本概念
- 第?2?章?常量、變量和表達式
- 第?3?章?簡單函數
- 第?4?章?分支語句
- 第?5?章?深入理解函數
- 第?6?章?循環語句
- 第?7?章?結構體
- 第?8?章?數組
- 第?9?章?編碼風格
- 第?10?章?gdb
- 第?11?章?排序與查找
- 第?12?章?棧與隊列
- 第?13?章?本階段總結
- 部分?II.?C語言本質
- 第?14?章?計算機中數的表示
- 第?15?章?數據類型詳解
- 第?16?章?運算符詳解
- 第?17?章?計算機體系結構基礎
- 第?18?章?x86匯編程序基礎
- 第?19?章?匯編與C之間的關系
- 第?20?章?鏈接詳解
- 第?21?章?預處理
- 第?22?章?Makefile基礎
- 第?23?章?指針
- 第?24?章?函數接口
- 第?25?章?C標準庫
- 第?26?章?鏈表、二叉樹和哈希表
- 第?27?章?本階段總結
- 部分?III.?Linux系統編程
- 第?28?章?文件與I/O
- 第?29?章?文件系統
- 第?30?章?進程
- 第?31?章?Shell腳本
- 第?32?章?正則表達式
- 第?33?章?信號
- 第?34?章?終端、作業控制與守護進程
- 第?35?章?線程
- 第?36?章?TCP/IP協議基礎
- 第?37?章?socket編程
- 附錄?A.?字符編碼
- 附錄?B.?GNU Free Documentation License Version 1.3, 3 November 2008
- 參考書目
- 索引