[TOC]
# 第3章 分支結構
# [插畫:下述場景]
在前面的學習中,小墨編寫的程序都是順序結構的,今天,他將遇到一種新的程序結構:分支結構。除此之外他還遇到了布爾類型、關系表達式、邏輯運算符等內容,這些內容雖然很有意思,但是也充滿了陷阱,他會如何避免呢?最后,墨博士要求他必須做完三道練習才能回家,一起來看看他是怎么解決的吧。
時間:早上9:05
場景:墨馨書屋門外走廊里
小墨匆匆的跑過來...
墨博士:早上好,小墨
小墨:不好意思!今天早上我跟往常一樣起床,出門的時候發現下著雨就帶了把雨傘,走到半路風太大了,把我傘都吹壞了,我只好去找了家商店重新買了把,結果就遲到了。
墨博士:沒關系的,快進來吧。
小墨:博士,今天我們學習什么呢?
墨博士:今天來學習分支結構:前面我們編寫的程序都是從上到下順序執行的,比如有3行代碼,程序就會先執行第1行,再執行第2行,然后是第3行,然后程序運行結束。而程序是對現實世界的模擬,現實世界中的事情可不都是“順序執行”的,比如你早起出門,會根據天氣選擇帶不帶雨傘,雨傘壞了會去重新買一把,甚至可以說,你每天都會經歷無數的選擇。這種選擇,在程序中稱為**分支結構**。
好了,讓我們開始吧。
## 3.1 關系運算符
首先先來做一個練習:
> 編寫一個程序,能夠輸入你的python考試成績,如果成績小于60,則輸出“成績不及格”。
小墨:嗯,我想想,先定義一個變量,接收輸入的成績
```
score = input('請輸入你的Python成績:')
```
墨博士:嗯,不錯。然后呢?
小墨:然后判斷這個輸入的成績和60的關系,小于60的話就使用print語句輸出成績不及格。判斷成績和60的關系在數學上是使用大于、小于,不知道這個在Python中如何表示。
墨博士:思路很清晰。Python中的大于、小于和數學上的一樣,如果成績變量是score,那么表示成績小于60使用“score < 60”就可以了。
在編程語言中,操作一個以上數據進行運算的符號,稱為**運算符**,比如1+2中的+號,是算術運算符。a=2中的=號,是賦值運算符。>和<也是運算符,稱為關系運算符。由運算符和操作數組成的式子稱為表達式,比如1+2稱為算術表達式、a=2稱為賦值表達式,score<60稱為關系表達式等。
> 墨博士提醒:Python中完整的運算符及使用方法請參考附錄B。
python中的關系運算符一共有6個,如表3.1所示。
表3.1 python中的關系運算符
| 關系 | 符號 |
|:-:|:-:|
| 大于 | > |
| 小于 | < |
| 等于 | == |
| 大于等于 | >= |
| 小于等于 | <= |
| 不等于 | != |
小墨:這個等于是使用兩個等號嗎?
墨博士:還記得我們前面說過的賦值嗎,在Python中一個等號“=”表示賦值。因為賦值已經把一個等號“=”給占上了,Python中判斷是否等于就用了兩個等號“==”。
小墨:哦,原來是這樣,我記住了。那這個score > 60如何來用呢?
墨博士:不要著急,我們先來了解一個新的數據類型:布爾類型。
## 3.2 布爾類型
墨博士:以“score<60”為例,這里的score是個變量,表示真正的分數值可以變化,比如score可以是99,也可以是10,可以是任意一個數值,那么“score<60”就只會得到兩種結果:
1. 如果score本身大于等于60,那么“score<60”表達式不成立
2. 而如果score本身就是小于60的一個數,那么“score<60”表達式成立
這個成立還是不成立在程序中如何表示呢?看下面這段代碼:
```
score = 65
print(score < 60)
score = 55
print(score < 60)
```
運行,結果為:
```
False
True
```
這里的False和True就用來表示score<60是否成立,其中False表示不成立,True表示成立。
從數據類型上來說,這里的True和False,不同于1、2這種整數,也不同于'abc'這種字符串,這是一種新的數據類型,稱為布爾(bool、boolean)類型。關系表達式的運算結果就是布爾類型。
小墨:布爾,聽起來是個厲害的角色。
墨博士:布爾是19世紀英國的一個偉大數學家的名字,計算機中的邏輯運算稱為布爾運算,計算機中的True和False稱為布爾值。

> 墨博士提醒:如果是寫完整的程序,可以通過IDLE啟動Python編輯器然后在編輯器中編寫代碼,如果只是想測試下某個知識點,通過IDLE本身就可以了。這里我們測試下布爾類型,如下:
```
>>> a = True
>>> print(a)
True
>>> b = False
>>> print(b)
False
```
小墨:博士你懂的真多,不過你又啰嗦了,說了這么多,還是沒解決最初的練習題呢。
墨博士:下面我們就一起來解決一下吧。
## 3.3 if
墨博士:在python中,條件判斷的情況使用if去處理,語法為:
```
if 條件:
語句
```
注意條件后面跟的是英文的冒號,Python中所有的符號都是英文的;第二行語句前是四個空格。Python為了代碼的簡潔和優美,采用一個Tab或四個空格作為縮進,表示一種所屬關系。上面代碼就表示如果條件成立,則執行語句,如果不成立,則不執行語句。
小墨:一個Tab鍵和四個空格是一樣的嗎?
墨博士:雖然一個Tab鍵或四個空格都可以用來縮進,但是還是建議你使用四個空格。在IDLE中默認也是四個空格,如圖3.1所示。

{-:-}圖3.1 IDLE中默認四個空格作為縮進方案
在IDLE中寫完if語句后,敲下回車,光標也會自動定位在縮進4個空格處。
接下來我們用if語句把你上述的練習代碼補充完整:
```
score = int(input("請輸入你的python考試成績:"))
# 如果成績小于60分
if score < 60:
print('成績不及格') # 注意這句前面的4個空格
```
運行,結果為:
```
請輸入你的python考試成績:48
成績不及格
```
看,我們就把練習的場景模擬出來了。
注意程序中的第1行,用戶輸入成績,用score變量接收,由于**input輸入默認是字符串類型**,這里要外面包一層int(),將字符串轉為int整數類型。第2行以#號開頭的,稱為程序中的**注釋**。所謂注釋,就是針對代碼的解釋說明,它可能是留著自己看,可能是給一塊寫代碼的隊友看,總之呢是給人看的(代碼是給計算機看的),運行的時候計算機會忽略它。在Python中,單行注釋的寫法就是#開頭,到這一行的末尾結束,一般#號后面會留個空格。單行注釋可以加上要注釋的內容的上面,也可以加在要注釋的內容的后面。
小墨:好的,我已經記住了。
## 3.4 if-else
墨博士:小墨,現在我們來把問題復雜一點:
> 請輸入您的python考試成績,如果成績小于60,則輸出“成績不及格”,反之則輸出“恭喜及格”。
該如何做呢?
小墨:我知道我知道!
```
score = int(input("請輸入你的python考試成績:"))
# 如果成績小于60分
if score < 60:
print('成績不及格')
if score >= 60:
print('恭喜及格')
```
墨博士:嗯。這確實是一種辦法。除此之外,Python還提供給我們了一種方法,就是if-else結構:
```
if 條件:
執行代碼1
else:
執行代碼2
```
else表示剩下的、其他的、否則,也即不符合if條件的。它的執行順序是,如果條件1成立,則執行代碼塊1,否則,執行代碼塊2。
小墨:哦,這個好這個好,我寫的程序需要判斷兩次,這個if-else結構只需要進行一次判斷就可以了。
墨博士:嗯。上面的問題代碼實現如下:
```
# 接收用戶輸入的成績
score = int(input("請輸入你的python考試成績:"))
# 如果成績小于60
if score < 60:
print('不及格') # 打印不及格
# else否則,也即對成績小于60取反,那就是大于等于60分,就是及格
else:
print('恭喜及格')
```
## 3.5、if-elif-else
墨博士:小墨,現在我們繼續升級難度:
> 請輸入你的python考試成績,如果成績大于80,則輸出“優秀”,如果小于等于80但大于等于60,則輸出“及格”,如果小于60分則輸出“不及格”。
這個如何做呢?
小墨:博士,你先別講,我猜一下:Python提供了處理兩個分支的if-else,那估計也提供的有處理多個分支的情況。
墨博士:是的,如果程序有多個分支,則使用if-elif-else結構,如下:
```
if 條件1:
執行代碼塊1
elif 條件2:
執行代碼塊2
else:
執行代碼塊3
```
elif是else if的縮寫,后面也是跟條件,表示在前面if條件不滿足的里面挑選符合后跟條件的。
上述if-elif-else結構的執行流程為:如果條件1成立,則執行代碼塊1,如果條件1不成立,則往下走,**在不符合條件1的成績里面**判斷條件2,如果條件2成立,則執行代碼塊2,如果條件2也不成立,則執行else下的語句塊3。
使用if-elif-else完成上述練習,代碼如下:
```
score = int(input("請輸入您的python考試成績:"))
if score > 80:
print('優秀')
# 這里的elif,表示if條件不符合的情況,也即取score小于等于80的,
# 然后在這個小于等于80的成績中再判斷是否大于等于60
elif score >= 60:
print('及格')
else:
print('不及格')
```
小墨:博士,我懂了。并且我已經懂得你的套路了。咳咳,墨博士同學,聽好了,下面我們再把問題復雜一下,請輸入你的python考試成績,如果成績大于等于90,則輸出“優秀”,如果小于90但大于等于70,則輸出“良好”,如果小于70但大于等于60,輸出“一般”,而如果小于60分則輸出“不及格”。這個如何做呢?
墨博士:...
小墨:這個依然使用if-elif-else實現,如下:
```
score = int(input("請輸入您的python考試成績:"))
if score >= 90:
print('優秀')
# 這里的elif,是在不符合score >= 90的情況下再判斷是否大于等于70
# 也即在score<90的情況下判斷是否大于等于70
# 符合題目中如果小于90但大于等于70的要求
elif score >= 70:
print('良好')
# 這個elif,是在不符合條件score>=90,也不符合score>=70的條件下
# 取score>=60,也即取小于70大于等于60的范圍
elif score >= 60:
print('一般')
# 這個else是上述三個條件都不成立,也即小于60的情況
else:
print('不及格')
```
墨博士:做的不錯!既然你已經掌握了,那我們就來總結一下吧。
上面4個案例就是if語句的全部用法了,總結一下,在if語句中:
* if語句、elif語句后跟條件,當條件滿足時執行對應語句塊
* 如果所有條件都不滿足,則執行else語句塊
* if可以單獨使用,elif和else不能單獨使用
* if出現1次,elif可以出現0-多次,而else可以出現0-1次
## 3.6 if語句使用注意事項
墨博士:小墨,if語句語法簡單,也非常常用,后面的幾乎每個程序中你都可以看到它的身影。你現在已經掌握了它的基本用法,但還是需要注意下if語句使用時的一些問題。
小墨:嗯,if語句使用時都需要注意哪些問題呢?
### 3.6.1 縮進問題
墨博士:上面我們提到,python中使用縮進表示從屬關系,以為了讓代碼更簡潔,比如下面的代碼:
```{}
score = 80
if score < 60:
print('不及格')
print('1')
```
輸出結果為1
而
```{}
score = 80
if score < 60:
print('不及格')
print('1')
```
則什么都不會輸出。原因是第二個程序中的第3和4兩行都有縮進,它們組成了一個整體(多行代碼一起,稱為代碼塊),都屬于if條件的覆蓋范圍。
而第一個程序中的print('1')這句,由于沒有縮進,和前面if就不是從屬關系,而是并列關系,是新的一行,所以不受if的影響。
小墨:縮進與否還影響著代碼的邏輯,這確實是一個值得注意的問題!
### 3.6.2 變量作用范圍問題
墨博士:另一個需要注意的問題跟上面的問題有關,修改上述代碼為:
```
score = 50
if score < 60:
s = '不及格'
print(s)
```
運行輸出“不及格”,而如果改成這樣:
```
score = 50
if score < 60:
s = '不及格'
print(s)
```
運行仍然輸出“不及格”。
也就是說:if覆蓋范圍內定義的變量,在if覆蓋范圍外仍然可以訪問。
小墨:這個問題我也記住了。
墨博士:好,我們繼續。小墨同學,既然你已經知道了我的套路,那么現在我就再次將練習難度升級:
現在有python和數學兩科成績,請根據以下評分標準給出成績的等級。
* 1、python90(含)-100分 并且數學 90(含)-100分 輸出:優秀
* 2、python或數學中有一科90分(含)及以上,但是另一科60分以下 輸出:偏科
該如何來做呢?
## 3.7 邏輯運算符
小墨:博士,這個還是你來說吧。
墨博士:這個問題我們可以通過if分支結構的嵌套來解決。小墨你聽過偽代碼嗎?
小墨:沒有誒。
墨博士:所謂偽代碼就是表達思路但是不能運行的代碼,來看一個if嵌套的案例的偽代碼:
```
if 你英語考了100分:
if 你是中國人:
則說明你外語很好
elif 你是美國人:
則說明你母語很好
```
可以看到英語考了100分是大前提,在此大前提下又區分了你是中國人還是美國人,如果你是中國人,就說明你外語學的不錯,如果你是美國人,則說明你母語很好。
小墨:嗯我理解了,if里面還有if,某種情況下又細分了某些情況。
墨博士:是的,這就是if嵌套的思路。理解了這個偽代碼之后,我們嘗試著解決下上面的練習,代碼如下:
```{}
# 定義python的分數
p_score = 58
# 定義數學的分數
m_score = 98
if p_score >= 90: # 如果python分數大于等于90
if m_score >= 90:
print('優秀')
elif m_score < 60:
print('偏科')
elif p_score < 60:
if m_score >= 90:
print('偏科')
```
程序的第5行if p_score >= 90表示在python成績大于等于90的基礎上,執行后面跟的代碼塊(第6-9行)。
第6-9行又是一個新的if結構,第6行表示如果數學成績m_score大于等于90,則執行第7行。因為現在的大環境就是python成績大于等于90,而這里數學成績如果也滿足了大于等于90的條件,當然輸出優秀了。
小墨:哦,明白了。同樣的第10行表示python成績小于60的大環境,如果此時數學成績又大于等于了90分,則說明這位同學偏科。數學好但是python差。
墨博士:是的。除了使用if分支結構的嵌套外,你還可以使用邏輯運算符來解決該問題。
小墨:什么是邏輯運算符呢?
墨博士:邏輯運算符有三種:與、或、非,表示邏輯上的同時成立、一方成立、不成立關系。我們之前曾說過,程序是對現實世界的抽象和模擬,程序中的東西都可以在現實生活中找到原型。下面的句子都是生活中常見的“邏輯”的使用場景:
* 如果你英語考了100分,**并且**你是中國人,則說明你外語很好;
* 明天太熱**或**太冷,我都不會出門;
* 如果你成績**不**好,那你要加油了。
小墨,你能用邏輯詞來舉個例子嗎?
小墨:如果爸爸明天**不**上班,他就會帶我去科技館**或**圖書館。
墨博士:不錯的例子。回到Python中,邏輯運算符與、或和非對應的符號為:and(與)、or(或)和not(非)
and的用法如下:
```
a = 2
b = 2
print(a > 0 and b > 0)
print(a < 0 and b < 0)
print(a > 0 and b < 0)
print(a < 0 and b > 0)
```
運行,輸出結果為:
```
True
False
False
False
```
小墨,你發現什么規律了嗎?
小墨:嗯……,
* 如果and運算的兩邊都為True,則運算結果為True
* 如果and運算的其中一邊為False,則運算結果就為False
是這樣的吧博士。
墨博士:是的。再來看or的用法,如下:
```
a = 2
b = 2
print(a > 0 or b > 0)
print(a < 0 or b < 0)
print(a > 0 or b < 0)
print(a < 0 or b > 0)
```
運行輸出結果為:
```
True
False
True
True
```
聰明的小墨,這次的規律又是什么呢?
小墨:規律是:
* 如果or的兩邊有一邊為True,其運算結果就為True
* 如果兩邊都是False,則運算結果是False
沒說錯吧。
墨博士:沒錯。最后再來看下not的用法:
```
a = 2
print(not a > 0)
print(not a < 0)
```
運行結果為:
```
False
True
```
小墨,還是你來總結下規律吧。
小墨:not True為False,not False為True。不真就是假,不假就是真。
墨博士:是的,邏輯運算符的計算就這么多了。現在我們用邏輯運算符來解決下前面的練習吧。
代碼如下:
```
p_score = 58
m_score = 98
if p_score >= 90 and m_score >= 90:
print('優秀')
elif p_score >= 90 and m_score < 60 or p_score < 60 and m_score >= 90:
print('偏科')
```
小墨:厲害!這種寫法比if的嵌套簡短了不少,邏輯也清晰了很多。
墨博士:這三種邏輯運算符單獨或組合使用,就能解決大部分的邏輯問題,所以有必要好好掌握哦。
## 3.8 邏輯運算符陷阱
墨博士:小墨,邏輯運算符中也有一個陷阱,還是來看一個練習:已知有4個人a,b,c,d,現在4個人進行投票表決一件事情,已知a同意并且b,c,d三人其中一個同意就表示同意這件事,你來補充以下下面的if條件,用于模擬這種同意的條件:
```
a = True
b = True
c = False
d = False
if ____:
print("同意")
```
答案是什么呢?
小墨:這個……,應該填上“a and b or c or d”吧,你看,程序運行確實輸出了同意。
墨博士:那如果我們把代碼再改以下,你再運行試試:
```
a = False
b = False
c = True
d = False
if a and b or c or d:
print("同意")
```
小墨:咦……怎么回事,運行依然是同意。這個a=False已經說明a不同意了。
墨博士:這顯然有邏輯問題了。問題的原因是運算符的優先級問題,就像如下代碼:
```
>>> print(1 + 2 * 3)
7
```
運行之所以輸出7而不是9的原因就在于1+2*3先計算了2乘以3,結果為6,然后計算了1+6,結果為7。也就是說,乘號*的優先級是比加號+的優先級高的,優先級改變了表達式的運算順序。
> 墨博士提醒:完整的運算符的優先級介紹請參見附錄B
同樣,在Python中or運算優先級并不比and高,所以上述a and b or c or d會順序執行:a先和b用and計算,計算的結果再和c進行or運算,計算的結果再和d進行or運算。針對本題的情況,就出現邏輯問題了。
正確的邏輯應該是a作為一個整體,b or c or d作為一個整體,兩者進行and運算。這使用一個小括號提示下優先級就行了,修改后的代碼如下:
```
a = True
b = False
c = True
d = False
if a and (b or c or d):
print("同意")
```
小墨:哦,我明白了。
## 3.9 本章小結
墨博士總結:好了,分支結構的內容學到這里就結束了。我來總結一下今天的主要內容:
1、分支結構的完整用法是if-elif-elif-elif-else
2、布爾類型:True和False
3、運算符,尤其是邏輯運算符和關系運算符的使用。關系運算符運算的結果是布爾類型,邏輯運算符運算的結果也是。
4、另外還有一個,昨天我們學習了str()能將整數轉為字符串,今天學習了int()能將字符串轉為整數。
這些內容都很簡單,但是這些是后面的基礎,要能夠熟練使用。
另外小墨同學,你今天的表現非常棒,值得鼓勵。但是你今天要做的還沒有結束。編程的核心是編,只有不斷的動手去編、大膽的去試,你才能真正的掌握這項技能。下面三個練習,來挑戰一下吧。
## 3.10 小墨的練習題
下面是博士給小墨留的三個練習,其中第三題還附帶了一個小提示:
練習一:三角形的構成條件是:任意兩邊之和大于第三邊。請編寫程序,定義變量a、b和c表示三角形的三條邊長,判斷這三條邊長能夠構成三角形。
練習二:錄入三個正整數,判斷最大的數是多少,并指出第幾個數最大。
練習三:輸入年份、月份和日期,輸出該日期是今年的第幾天。需要考慮閏年問題。其中閏年的判斷條件是:
1、能整除4且不能整除100
2、能整除400
也就是四年一閏,百年不閏,四百年再閏。
小提示:在Python中如何表示能整除4呢?對4取余,判斷結果是否等于0就可以了。代碼為:
```
year % 4 == 0
```
其中的%就是Python中的取余運算符(Python中完整的運算符請參考**附錄B**)。
# [插畫:地球繞太陽轉,閏年相關]
> 閏年產生的原因:地球繞太陽運行周期為365天5小時48分46秒(合365.24219天)即一回歸年(tropical year)。公歷的平年只有365日,比回歸年短約0.2422 日,所余下的時間約為每四年累計一天,故第四年于2月末加1天,使當年的歷年長度為366日,這一年就為閏年。現行公歷中每400年有97個閏年。按照每四年一個閏年計算,平均每年就要多算出0.0078天,這樣經過四百年就會多算出大約3天來。因此每四百年中要減少三個閏年。所以公歷規定:年份是整百數時,必須是400的倍數才是閏年;不是400的倍數的世紀年,即使是4的倍數也不是閏年。
以下是小墨同學針對三個練習的答案。
練習一小墨的答案:
```
# 定義a、b和c表示三條邊
a = 2
b = 5
c = 9
# 三角形的判斷條件
if a + b > c and a + c > b and b + c > a:
print('是三角形')
else:
print('不是三角形')
```
練習二小墨的答案:
```
# 定義三個變量接收用戶的輸入
a = int(input('輸入第一個數:'))
b = int(input('輸入第一個數:'))
c = int(input('輸入第一個數:'))
# 定義變量max_num,表示最大值
max_num = -1
# 定義第幾個數是最大值
max_index = 0
# 如果a比最大值max_num大,則將a作為最大值
if max_num < a:
max_num = a
max_index = 1
# 如果b比max_num大,則將b作為最大值,此時max_num是a和b中最大的那個
if max_num < b:
max_num = b
max_index = 2
# 如果c比max_num大,則將c作為最大值,此時max_num是a、b和c中最大的那個
if max_num < c:
max_num = c
max_index = 3
print('第%s個數最大,最大值為:%s' % (max_index, max_num))
```
運行結果為:
```
輸入第一個數:5
輸入第一個數:4
輸入第一個數:8
第3個數最大,最大值為:8
```
練習三小墨的答案:
```
# 定義year、month和day表示輸入的年、月和日
year = int(input('請輸入年份:'))
month = int(input('請輸入月份:'))
day = int(input('請輸入日期:'))
# 定義變量february_days,賦值為28
february_days = 28
if year % 4 == 0 and year % 100 != 0 or year % 400 == 0:
# 如果符合閏年的條件,則將february_days更改為29
february_days = 29
# 定義變量days,用來表示這是一年中的第幾天
days = 0
# 如果月份是1,則輸入的日期就是今年中的第幾天,比如1月10日,就是一年中的第10天
if month == 1:
days += day
# 如果月份是2,則輸入的日期加上第一個月的31天就是一年中的第幾天
elif month == 2:
days = 31 + day
# 如果月份是3,則輸入的日期加上第一個月的31天,以及第二個月的天數february_days(可能28,可能29,上面已經計算好)
# 就是一年中的第幾天,其他月份依次類推
elif month == 3:
days = 31 + february_days + day
elif month == 4:
days = 31 + february_days + 31 + day
elif month == 5:
days = 31 + february_days + 31 + 30 + day
elif month == 6:
days = 31 + february_days + 31 + 30 + 31 + day
elif month == 7:
days = 31 + february_days + 31 + 30 + 31 + 30 + day
elif month == 8:
days = 31 + february_days + 31 + 30 + 31 + 30 + 31 + day
elif month == 9:
days = 31 + february_days + 31 + 30 + 31 + 30 + 31 + 31 + day
elif month == 10:
days = 31 + february_days + 31 + 30 + 31 + 30 + 31 + 31 + 30 + day
elif month == 11:
days = 31 + february_days + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + day
elif month == 12:
days = 31 + february_days + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30 + day
# 拼接結果后輸出
print('%s年%s月%s日是今年的第%s天' % (year, month, day, days))
```
運行結果為:
```
請輸入年份:2018
請輸入月份:12
請輸入日期:31
2018年12月31日是今年的第365天
```
小讀者們,這些練習你有更好的解決辦法嗎?快來和小墨比比吧。