計算機程序的思惟邏輯 (9) - 條件執行的本質

本系列文章經補充和完善,已修訂整理成書《Java編程的邏輯》(馬俊昌著),由機械工業出版社華章分社出版,於2018年1月上市熱銷,讀者好評如潮!各大網店和書店有售,歡迎購買:京東自營連接 html

條件執行

前面幾節咱們介紹瞭如何定義數據和進行基本運算,爲了對數據有透徹的理解,咱們介紹了各類類型數據的二進制表示。java

如今,讓咱們回顧程序自己,只進行基本操做是不夠的,爲了進行有現實意義的操做,咱們須要對操做的過程進行流程控制。流程控制中最基本的就是條件執行,也就 是說,某些操做只能在某些條件知足的狀況下才執行,在一些條件下執行某種操做,在另一些條件下執行另外某種操做。這與交通控制中的紅燈停、綠燈行條件執行是相似的。編程

Java中表達這種流程控制的基本語法是If語句。數組

if

if的語法爲:微信

if(條件語句){
  代碼塊
}
複製代碼

性能

if(條件語句) 代碼; 
複製代碼

它表達的含義也很是簡單,只在條件語句爲真的狀況下,才執行後面的代碼,爲假就不作了。具體來講,條件語句必須爲布爾值,能夠是一個直接的布爾變量,也能夠是變量運算後的結果,咱們在第3節介紹過,比較運算和邏輯運算的結果都是布爾值,因此可做爲條件語句。條件語句爲true,則執行括號{}中的代碼,若是後面沒有括號,則執行後面第一個分號(;)前的代碼。優化

如,只在變量爲偶數的狀況下輸出:spa

int a=10;
if(a%2==0){
   System.out.println("偶數");
}
複製代碼

3d

int a=10;
if(a%2==0) System.out.println("偶數");
複製代碼

if的陷阱

初學者有時會忘記在if後面的代碼塊中加括號,有時但願執行多條語句而沒有加括號,結果只會執行第一條語句。建議全部if後面都跟括號。code

if/else

if實現的是條件知足的時候作什麼操做,若是須要根據條件作分支,即知足的時候執行某種邏輯,而不知足的時候執行另外一種邏輯,則能夠用if/else。

if/else的語法是:

if(判斷條件){
   代碼塊1
}else{
   代碼塊2
}
複製代碼

if/else也很是簡單,判斷條件是一個布爾值,爲true的時候執行代碼塊1,爲假的時候執行代碼塊2。

三元運算符

咱們以前介紹了各類基本運算,這裏介紹一個條件運算,和if/else很像,叫三元運算符,語法爲:

判斷條件 ? 表達式 1 : 表達式2
複製代碼

三元運算符會獲得一個結果,判斷條件爲真的時候就返回表達式1的值,不然就返回表達式2的值。三元運算符常常用於對某個變量賦值,例如求兩個數的最大值:

int max = x > y ? x : y;
複製代碼

三元運算符徹底能夠用if/else代替,但在某些場景下書寫更簡潔。

if/else if/else

若是有多個判斷條件,並且須要根據這些判斷條件的組合執行某些操做,則可使用if/else if/else。

語法是

if(條件1){
  代碼塊1
}else if(條件2){
  代碼塊2
}...
else if(條件n){
   代碼塊n
}else{
   代碼塊n+1
} 
複製代碼

if/else if/else也比較簡單,但能夠表達複雜的條件執行邏輯,它逐個檢查條件,條件1知足則執行代碼塊1,不知足則檢查條件2,...,最後若是沒有條件滿 足,且有else語句,則執行else裏面的代碼。最後的else語句不是必須的,沒有就什麼都不執行。

if/else if/else陷阱

須要注意的是,在if/else if/else中,判斷的順序是很重要的,後面的判斷只有在前面的條件爲false的時候纔會執行。初學者有時會搞錯這個順序,以下面的代碼:

if(score>60){
  return "及格";
}else if(score>80){
  return "良好";
}else{
  return "優秀"
}
複製代碼

看出問題了吧?若是score是90,可能指望返回"優秀",但實際只會返回"及格".

switch

在if/else if/else中,若是判斷的條件基於的是同一個變量,只是根據變量值的不一樣而有不一樣的分支,若是值比較多,好比根據星期幾進行判斷,有7種可能性,或者根據英文字母進行判斷,有26種可能性,使用if/else if/else顯的比較囉嗦,這種狀況可使用switch,switch的語法是:

switch(表達式){
   case1:
           代碼1; break;
   case2:
           代碼2; break;
           ...
    case 值n:
          代碼n; break;
    default: 代碼n+1
}
複製代碼

switch也比較簡單,根據表達式的值執行不一樣的分支,具體來講,根據表達式的值找匹配的case,找到後,執行後面的代碼,碰到break時結束,若是沒有找到匹配的值則執行default中的語句。

表達式值的數據類型只能是 byte, short, int, char, 枚舉, 和String (Java 1.7之後)。枚舉和String咱們在後續文章介紹。

switch會簡化一些代碼的編寫,但break和case語法會對初學者形成一些困惑。

容易忽略的break

break是指跳出switch語句,執行switch後面的語句。每條case語句後面都應該跟break語句,不然的話它會繼續執行後面case中的代碼直到碰到break語句或switch結束,例如:下面的代碼會輸出全部數字而不僅是1.

int a = 1;
switch(a){
case 1:
    System.out.println("1");
case 2:
    System.out.println("2");
default:
    System.out.println("3");
}
複製代碼

case堆疊

case語句後面能夠沒有要執行的代碼,以下所示:

char c = 'x';//某字符
switch(c){
   case 'A':
   case 'B':
   case 'C':
        System.out.println("A-Z");break;
   case 'D':
       ....
}
複製代碼

case 'A'/'B'後都沒有緊跟要執行的代碼,他們實際會執行第一塊碰到的代碼,即case 'C'匹配的代碼

條件小結

條件執行整體上是比較簡單的,單一條件知足時執行某操做使用if,根據一個條件是否知足執行不一樣分支使用if/else,表達複雜的條件使用if/else if/elese,條件賦值使用三元運算符,根據某一個表達式的值不一樣執行不一樣的分支使用switch。

從邏輯上講,if/else, if/else if/else,三元運算符,switch均可以只用if代替,但使用不一樣的語法表達更簡潔,在條件比較多的時候,switch從性能上也更高(立刻解釋爲何)。

條件本質

正如咱們探討數據類型的時候,研究數據的二進制表示同樣,咱們也來看下這些條件執行具體是怎麼實現的。

程序最終都是一條條的指令,CPU有一個指令指示器,指向下一條要執行的指令,CPU根據指示器的指示加載指令而且執行。指令大部分是具體的操做和運算,在執行這些操做時,執行完一個操做後,指令指示器會自動指向挨着的下一個指令。

但有一些特殊的指令,稱爲跳轉指令,這些指令會修改指令指示器的值,讓CPU跳到一個指定的地方執行。跳轉有兩種,一種是條件跳轉,另外一種是無條件跳轉。條件跳轉檢查某個條件,知足則進行跳轉,無條件跳轉則是直接進行跳轉。

if, else實際上會轉換爲這些跳轉指令,好比說下面的代碼:

1 int a=10;
2 if(a%2==0)
3 {
4    System.out.println("偶數");
5 }
6 //其餘代碼
複製代碼

轉換到的轉移指令多是:

1 int a=10;
2 條件跳轉: 若是a%2==0,跳轉到第43 無條件跳轉:跳轉到第74 {
5    System.out.println("偶數");
6 }
7 //其餘代碼
複製代碼

你可能會奇怪其中的無條件跳轉指令,沒有它不行嗎?不行,沒有這條指令,無論什麼條件,括號中的代碼都會執行。

不過,對應的跳轉指令也多是:

1 int a=10;
2 條件跳轉: 若是a%2!=0,跳轉到第63 {
4    System.out.println("偶數");
5 }
6 //其餘代碼
複製代碼

這個就沒有無條件跳轉指令,具體怎麼對應和編譯器實現有關。在單一if的狀況下可能不用無條件跳轉指令,但稍微複雜一些的狀況都須要。if, if/else, if/else if/else, 三元運算符都會轉換爲條件跳轉和無條件跳轉。但switch不太同樣。

switch的轉換和具體系統實現有關,若是分支比較少,可能會轉換爲跳轉指令。但若是分支比較多,使用條件跳轉會進行不少次的比較運算,效率比較低,可能會使用一種更爲高效的方式,叫跳轉表。跳轉表是一個映射表,存儲了可能的值以及要跳轉到的地址,形如:

跳轉地址
值1 代碼塊1的地址
值2 代碼塊2的地址
...
值n 代碼塊n的地址

跳轉表爲何會更爲高效呢?由於,其中的值必須爲整數,且按大小順序排序。按大小排序的整數可使用高效的二分查找,即先與中間的值比,若是小於中間的值則在開始和中間值之間找,不然在中間值和末尾值之間找,每找一次縮小一倍查找範圍。若是值是連續的,則跳轉表還會進行特殊優化,優化爲一個數組,連找都不用找了,值就是數組的下標索引,直接根據值就能夠找到跳轉的地址。即便值不是連續的,但數字比較密集,差的很少,編譯器也可能會優化爲一個數組型的跳轉表,沒有的值指向default分支。

程序源代碼中的case值排列不要求是排序的,編譯器會自動排序。以前說switch值的類型能夠是byte, short, int, char, 枚舉和String。其中byte/short/int原本就是整數,在上節咱們也說過,char本質上也是整數,而枚舉類型也有對應的整數,String用於switch時也會轉換爲整數(經過hashCode方法,後文介紹),爲何不可使用long呢?跳轉表值的存儲空間通常爲32位,容納不下long。

總結

條件執行的語法是比較天然和容易理解的,須要注意的是其中的一些語法細節和陷阱。它執行的本質依賴於條件跳轉、無條件跳轉和跳轉表。

條件執行中的跳轉只會跳轉到跳轉語句之後的指令,能不能跳轉到以前的指令呢?


未完待續,查看最新文章,敬請關注微信公衆號「老馬說編程」(掃描下方二維碼),深刻淺出,老馬和你一塊兒探索Java編程及計算機技術的本質。原創文章,保留全部版權。

相關文章
相關標籤/搜索