相信大家或多或少的聽說過,少用點if-else吧?但是為什么要少用呢,有人說他會影響程序運行效率,但是這并不是他最大的罪狀...
if-else 的罪狀
if-else 作為三種最基本的程序結構之一,是我們從最開始學習編程時就接觸的基本語句。但是到后面的階段就不斷聽人說少用if-else。
如果詢問原因的話,你得到的結果大概率是if-else導致程序運行效率下降。這次來扯扯為什么我們說要少用if-else。
導致程序運行效率下降(大部分時候可以忽略) 破壞程序結構,導致代碼難以維護(核心原因 ?)
if 語句與運行效率
說起if語句導致程序運行效率下降,就不得不提到CPU的流水線結構,效率降低主要是由于多級流水線結構造成的。
現代的大部分CPU在執行代碼的時候并不是讀取一條指令然后執行一條執行的,而是使用了一種叫做流水線技術的方式,同時去執行多個操作。
流水線的影響
比如三級流水線就是指,CPU在執行一條指令時,同時會讀取后面的指令,并對進行譯碼。(讀取、譯碼、執行)
這樣處理的優勢很明顯,使用流水線技術可以大大的提高執行效率。
但是它并不是所有時刻有效的,在程序中執行跳轉代碼時,CPU 會丟棄流水線現有的結果。
原因嘛,很簡單!我都不執行后面的代碼了,你提前讀取有啥用~
所以在這個時候if語句相對于順序執行的指令,會有幾個時鐘周期的差距。但這不是if語句所特有的,所有帶跳轉結構的語句都會這樣(if、switch、for)。
分支預測的影響
多級流水線在遇到跳轉時,會有幾個時鐘的周期的影響,但這并不是它被指控運行效率低的主要原因。
而是在因為它分支預測部分,它有可能有10-20個時鐘周期的影響,在大量使用if的地方這種影響將被放大。
下面說說分支預測為什么會有這么大的影響。
在上面說到多級流水在遇到跳轉指令時會清空當前流水線,CPU的設計者在設計引入了一種叫做分支預測的技術來進行處理這個問題。
分支預測簡單說就是猜測后面的程序會執行那一段代碼,并提前將它讀取。
例如一輛火車,在有很多岔道的路上前進,為了不讓他每次都在岔道停下等待(清空流水線),于是想出了一個辦法。
猜測火車需要前進的方向,如果猜中了火車就可以不用停下等待,而提高效率。
但是如果猜錯了,則需要倒車回到岔路口重新選擇。這樣的錯誤代價就比較高了。
而大家所說的效率降低主要源于此。
if-else 對程序結構的影響
在大部分情況下,我們是不需要考慮if語句對代碼執行效率的影響,我們甚至感覺不到它的存在。
因為大部分情況下,CPU的性能是足夠的(性能優化時除外)。
但是if-else對程序結構的影響卻是不容忽視的,因為我們可以直觀的感受到它的存在,而且對開發和維護有極大的影響。
看下面一段代碼:
if (condition1==true)
{f1();}
else if (condition2==true)
{f2();}
else if (condition3==true)
{f3();}
這個代碼非常簡單:判斷不同條件時執行不同的代碼塊。
這段代碼寫完測試時發現有點問題
condition1和condition3同時滿足時應該先執行f4 condition3和condition4滿足任意一個時執行f3
修改代碼測試通過后,于是乎代碼變成了下面的模樣:
else if (condition1==true)
{
if (condition3==true)
{ f4(); }
f1();
}
else if (condition2==true)
{ f2(); }
else if (condition3==true || condition4==true)
{ f3(); }
這只是我簡單模擬的一段代碼,對于稍微復雜的邏輯,if-else的數量遠遠大于上面的數量。
在這樣的代碼中,如果各種condition都是使用flag變量進行標記時,將會是一種巨大的災難。
我之前碰到這樣的代碼時,心情只能用下圖表示。

大量使用if-else,會使代碼變得難以理解,同時增加后期開發和維護的成本。
這個才是少用if-else真的原因!
如何消除if-else
既然上面說到了if-else有這么多的問題,那應該怎樣減少使用它呢,
1. 巧妙使用算術表達式
比如下面的代碼,在num不能被5整除時,num加一
if(num%5>0)
{
num++;
}
可以替換成 num = num + !!(num%5);
這種一般是在對計算結果進行簡單判斷時可使用,它的優化點在于消除了分支結構,提高了執行效率。(雖然說很小)。
使用斷言(assert)
在對函數參數進行合法性檢查時常用,可以減少大量對參數進行時的if-else,適用場景也比較簡單。
查找表(函數轉移表)
查找表或者函數轉移表,可以對程序的整體結構進行優化或者改進。
比如下面一個計算器的代碼:
if(oper == ADD)
{ Result=add(op1,op2);}
else if(oper == SUB)
{ Result=add(op1,op2);}
if(oper == MUL)
{ Result=mul(op1,op2);}
else if(oper == DIV)
{ Result=div(op1,op2);}
使用函數轉移表可改進為
typedef int (*oper_t)(int, int);
oper_t oper_table[]={add, sub, mul, div};
...
result = oper_table[oper](op1,op2);
查找表則相對更靈活,可以對不同類型的數據進行查找;
#define arrayof(x) (sizeof(x)/sizeof(x[0]))
typedef int (*oper_t)(int, int);
struct find_table_t {
char *oper_name;
oper_t oper_func;
}
find_table_t oper_table[]=
{{"add",add}, {"sub",sub}, {"mul",mul}, {"div",div}};
for(int i=0; i<arrayof(oper_table);i++)
{
if(strcmp(oper,oper[i].oper_name)==0)
{
result = oper[i].func(op1,op2);
return result;
}
}
責任鏈(職責鏈)
責任鏈將一個復雜邏輯的流程進行分解,將每個判斷條件的判斷交給責任鏈節點進行處理,在處理完成后將結果傳遞給下一個節點。
在后面有專門一篇文章寫責任鏈模式,在這就不展開了。
狀態機
狀態機也是消除if-else的一種方法,在狀態機中對所有條件的判斷變成的狀態轉移。
在后面也會有單獨的文章講解有限狀態機的實現和應用。
end
往期推薦