看最近的新聞,我很好奇,如果用if-else來表達美國大選那些事,你有哪些想法?
if(bTrumpWin)
{
// balabala...
}
else
{
// balabala...
}
也沒啥毛病,簡單地就是這種方式。
if(bBidenWin)
{
// balabala...
}
else
{
// balabala...
}
是不是一樣的?是的,就他們倆競選。
想想,bTrumpWin和bBidenWin是bool變量來的,也就是說,是有別的地方產生這個結果。那如果沒有這種直接的結果呢?
if(isBidenWin())
{
// balabala...
}
else
{
// balabala...
}
那麼,這個isBidenWin()這個是返回bool變量的函數。再想想啊,這個函數,如果是同步的,能直接返回真實結果的,那當然沒問題。
沒完啊,美國大選是很複雜的吧,哪就一條if-else語句就搞定了的。萬一,睡王Biden出了啥三長兩短呢?畢竟懂王不服啊,睡王年紀也一大把了,而且美國的疫情那麼兇……
if(isBidenWin())
{
if(bBidenAlive)
{
if(isTrumpAcceptVoteResult())
{
// balabala...
}
else
{
// balabala...
}
}
else
{
// don't miss the else, or america will be crash
}
}
else
{
// balabala...
}
呃,突然覺得當程式設計師,好操心……
以上,用if-else繼續寫下去,你可能會抓狂。這個不是寫代碼的錯,而且一開始就要做好程序設計,想好要用什麼模式或者模型來實現這個過程。要不試試面向對象?哈哈哈!
扯遠了,講真,寫if真的別忘了else,即使你真的不需要else,但你也別忘了!
你想到了成功會怎樣,即使你有100%的把握,但也要是萬一失敗了呢?程序我不講情感的,天地萬物也是不講情感的,老子曰,「天地不仁,以萬物為芻狗。」有成功,就會有失敗,有真,就會有假。
不講大選,我們講投票吧。如果你要做個投票管理的程序,怎麼搞?
不是選睡王就是選懂王啊,還不簡單……
if(bVoteForBiden)
{
// support Biden
}
else
{
// support Trump?
}
不是投給睡王,就投懂王,難道錯了麼?
我都不喜歡啊,我棄權還不行麼。所以,你還得有else if
if(bVoteForBiden)
{
// support Biden
}
else if(bVoteForTrump))
{
// support Trump
}
else
{
// abstentions
}
考慮事情,要全面哦!
到這裡,你是不是覺得寫個代碼,就好像比白宮的官員還操心!
如果真的要鑽這牛角尖,那該怎麼做呢?其實,可以用事件驅動的方式。把可能發生的情況當做是一個個事件,然後對事件發生後做一系列的條件判斷,符合某種條件的,就執行對應的函數。
類似這樣:
需要枚舉所有能想得到的情況,然後逐個觸發,逐個條件檢查,逐個執行動作。
這個具體怎麼實現呢,這裡就不展開講了,另可參考其他類似的文章(X-MACRO)。
凡事預則立不預則廢,如果真的要讓自己(的代碼)立於不敗之地,真的要各方面都考慮清楚。為什麼代碼裡面那麼多bug,其實很多都是因為程式設計師們碼走偏鋒了,就覺得有if就行了,要else幹啥……
程式語言中的if-else我曾聽說過好多人在給理工科但沒學過編程的人講自己的工作細節的時候,通常都說自己在用程式語言寫那些if-else邏輯的代碼。
話說,if-else非常通俗易懂,但是在程序設計中,你真的用對了嗎?
切!不就是if和else嗎!呵呵呵……其實也沒錯。
條件判斷語句看個筆試題:
請填寫bool , float, 指針變量 與「零值」比較的if 語句。
是不是很簡單,是的,但你有深層挖掘個其中的「為什麼」嗎?
1. 看第一個bool。
bool b;
if(b == 1)
if(b == TRUE)
以上這個答案是有問題的,為什麼?首先要知道什麼叫bool,什麼是真,什麼是假?
很簡單:0是假,非0是真。
那麼,什麼叫非0?-1,算不算,1算不算,10000算不算?都算!反正不是0就是真。
你也可以這樣想:平安夜將襪子掛在床頭,如果沒有聖誕老人給你禮物,那你將襪子翻過來啊,翻過來的襪子是不是裝了全世界!哈哈哈……一般人我是不告訴他的哦。
所以,如果有個值i = 5,那麼if(i == TRUE)就不靈了。正確的應該這樣:
bool b;
if(b)
if(!b)
2. 看第2個,float:
float f;
if(f == 0)
我告訴你這是錯的,一點兒正確的成分都沒有。那if(f == 0.0)可以嗎?也不是,要不你試試這個:
if(0.1 + 0.2 == 0.3)
這個條件成立嗎?不成立,真的,你去試試。0.1+0.2其實計算機算出來是0.30000000000000004。別忘了,計算機真正能懂得只有0和1,其他的一切都是靠這倆拼起來的,所以就存在浮點精度問題。
那么正確的答案應該是:
const float EPSINON = 0.00001;
float f;
if((f >= -EPSINON)&&(f <= EPSINON))
3. 看指針判斷。
char* p;
if(p == 0)
if(p)
以上這風格不好。
char在C/C++中是個特殊的類型,char*認的是\0結尾的字符串。你說char就是signed char或就是unsigned char,其實都不準確,你得看編譯器。
另一方面,if(p == 0)會被人理解為p是個整數,if(p)會被人認為p是個bool。那麼,就這樣咯
char* p;
if(p == NULL)
其實,這也不好,這很容易寫成if(p = NULL),這編譯器不報錯的哦。那好的風格應該這樣的:
char* p;
if(NULL == p)
if(NULL != p)
即使敲錯了成if(NULL = p),編譯器會告訴你這是錯的。
if-else與switch-case既生瑜,何生亮。
邏輯上,真的if-else可以搞定switch-case的事,但是你看看下面的代碼。
// solution 1
switch (color)
{
case RED:
printf("red");
break;
case GREEN:
printf("green");
break;
case BLUE:
printf("blue");
break;
default:
printf("unknown color");
break;
}
// solution 2
if (color === RED)
{
printf("red");
}
else if (color === GREEN)
{
printf("green");
}
else if (color = BLUE)
{
printf("blue");
}
else
{
printf("unknown color");
}
很明顯,這裡,我們會選擇switch-case的形式,最直觀的原因是,結構清晰。其實還有另外一個原因——效率高。你看if-else和switch-case的執行流程:
我想,switch-case應該是在編譯的時候產生了對應的跳轉表來指示case的地址,一般編譯器會對這些內容進行優化,所以這樣就會有很高的執行效率。
if-else的注意事項if和else在語法上使用很自由,寫了if可以不寫else,那麼一段代碼中,怎麼知道else是配哪個if的?
if(0 == x)
if(0 == y) FuncY();
else{
//bala bala
}
想想,else是誰的,哪個if才是else的原配?
別想了,試試吧。這個其實C語言有規定:else始終與同一括號內最近的未匹配的if語句結合。
如果你是初學者,也不要去鑽這牛角尖,浪費時間。
建議1:寫if-else語句時,記得加上{和},別偷懶!
如果不愛用{和},還有個問題:
if(condition);
func();
if後面多了個;,編譯器是不會報錯的,但是這個;是多餘的,除非你裝逼故意的。如果你真的有需要這麼做,建議寫成NULL;而不是單獨一個;,好多編譯器都會將其解釋成NOP指令。
網上有為以下兩種風格吵得不可開交的,還為此說對方是異教徒。
if(condtion){
// bala bala
}
if(condtion)
{
// bala bala
}
按我說,哪種都沒問題,但是你要堅持整個代碼統一。
還有個縮進問題,TAB?2空格?還是4空格?
別吵,我用4空格,當然你可以用2空格,請堅持統一。但不建議用TAB,因為好多編輯器對TAB的處理不一樣,懶得設置它。
建議2:用同一種風格,不要混合用。
建議3:寫if-else語句,先處理正常情況(或者發生頻率高的情況),再處理異常。
別以為這個沒意義,如果你將這個寫在一個大循環裡面,提高效率是非常顯著的。
建議4:對於簡單的if-else語句,可以用冒號:表達式來替代,會更加簡潔。
if(y < 0)
{
x = 10;
}
else
{
x = 20;
}
可以改成以下一行代碼即可
x = (y < 0) ? 10 : 20;
建議5:使用likely()與unlikely
Linux內核中有兩個這樣的東西:likely()與unlikely
#define likely(x) __builtin_expect(!!(x), 1)
#define unlikely(x) __builtin_expect(!!(x), 0)
我們可以根據高概率發生的情況放在if分支,低概率的放在else分支,以提高程序運行效率。
if(likely(XXX))
{/*...*/}
else
{/*...*/}
或者
if(unlikely(XXX))
{/*...*/}
else
{/*...*/}
好了,下面講switch-case。
建議6:每個case後面記得別漏了break,除非你是有意的。
switch (color)
{
case RED:
break;
case GREEN:
break;
case BLUE:
break;
default:
break;
}
switch (color)
{
case RED:
case GREEN:
case BLUE:
break;
default:
break;
}
上面兩段代碼含義是不一樣的,請注意。
建議7:最後加上default分支,即使你不需要,也建議你加上。
這個沒什麼好解釋的,良好的編程習慣,防止漏了而已。
有個問題:case後面可以跟什麼值?
整型?浮點?字符串?如果你真的異或,親自試試就好了。
記住: case 後面只能是整型或字符型的常量或常量表達式。
再來一個問題:case順序是不是無所謂的?
語法上是無所謂的,但是不建議你亂放。自己排好一個順序方便你以後看代碼查找內容。如果你寫的case非常多的時候,你就會懂的了。
建議8:按字母或數字順序排列各條 case 語句,也可以按執行頻率來排case語句。
建議9:簡化case後的語句,不要千篇大論什麼都寫裡面,這樣會導致switch-case臃腫而難以維護。
建議10:使用結構體數組方法或X-MACRO來處理多而複雜的case語句。
你對比下以下兩種用法:
switch-case方法:
#define FUNC_IN() printf("enter %s \r\n", __FUNCTION__)
void func_cmd_play(void* p)
{
FUNC_IN();
}
void func_cmd_pause(void* p)
{
FUNC_IN();
}
void func_cmd_stop(void* p)
{
FUNC_IN();
}
void func_cmd_play_next(void* p)
{
FUNC_IN();
}
void func_cmd_play_prev(void* p)
{
FUNC_IN();
}
void player_cmd_handle(int cmd, void* p)
{
switch(cmd)
{
case CMD_PLAY:
func_cmd_play(p);
break;
case CMD_PAUSE:
func_cmd_pause(p);
break;
case CMD_STOP:
func_cmd_stop(p);
break;
case CMD_PLAY_NEXT:
func_cmd_play_next(p);
break;
case CMD_PLAY_PREV:
func_cmd_play_prev(p);
break;
default:
break;
}
}
結構體數組方法:
#include <stdio.h>
#define FUNC_IN() printf("enter %s \r\n", __FUNCTION__)
#define CMD_FUNC \
DEF_X(CMD_PLAY, func_cmd_play) \
DEF_X(CMD_PAUSE, func_cmd_pause) \
DEF_X(CMD_STOP, func_cmd_stop) \
DEF_X(CMD_PLAY_NEXT, func_cmd_play_next) \
DEF_X(CMD_PLAY_PREV, func_cmd_play_prev) \
typedef enum
{
#define DEF_X(a,b) a,
CMD_FUNC
#undef DEF_X
CMD_MAX
}tCmd;
const char* str_cmd[] =
{
#define DEF_X(a,b) #a,
CMD_FUNC
#undef DEF_X
};
typedef void(*pFunc)(void* p);
void func_cmd_play(void* p)
{
FUNC_IN();
}
void func_cmd_pause(void* p)
{
FUNC_IN();
}
void func_cmd_stop(void* p)
{
FUNC_IN();
}
void func_cmd_play_next(void* p)
{
FUNC_IN();
}
void func_cmd_play_prev(void* p)
{
FUNC_IN();
}
const pFunc player_funcs[] =
{
#define DEF_X(a,b) b,
CMD_FUNC
#undef DEF_X
};
void player_cmd_handle(tCmd cmd, void* p)
{
if(cmd < CMD_MAX)
{
player_funcs[cmd](p);
}
else
{
printf("Command(%d) invalid!\n", cmd);
}
}
int main(void)
{
player_cmd_handle(CMD_PAUSE, (void*)0);
player_cmd_handle(100, (void*)0);
return 0;
}
X-MACRO方法:
#include <stdio.h>
#define FUNC_IN() printf("enter %s \r\n", __FUNCTION__)
#define CMD_FUNC \
DEF_X(CMD_PLAY, func_cmd_play) \
DEF_X(CMD_PAUSE, func_cmd_pause) \
DEF_X(CMD_STOP, func_cmd_stop) \
DEF_X(CMD_PLAY_NEXT, func_cmd_play_next) \
DEF_X(CMD_PLAY_PREV, func_cmd_play_prev) \
typedef enum
{
#define DEF_X(a,b) a,
CMD_FUNC
#undef DEF_X
CMD_MAX
}tCmd;
const char* str_cmd[] =
{
#define DEF_X(a,b) #a,
CMD_FUNC
#undef DEF_X
};
typedef void(*pFunc)(void* p);
void func_cmd_play(void* p)
{
FUNC_IN();
}
void func_cmd_pause(void* p)
{
FUNC_IN();
}
void func_cmd_stop(void* p)
{
FUNC_IN();
}
void func_cmd_play_next(void* p)
{
FUNC_IN();
}
void func_cmd_play_prev(void* p)
{
FUNC_IN();
}
const pFunc player_funcs[] =
{
#define DEF_X(a,b) b,
CMD_FUNC
#undef DEF_X
};
void player_cmd_handle(tCmd cmd, void* p)
{
if(cmd < CMD_MAX)
{
player_funcs[cmd](p);
}
else
{
printf("Command(%d) invalid!\n", cmd);
}
}
int main(void)
{
player_cmd_handle(CMD_PAUSE, (void*)0);
player_cmd_handle(100, (void*)0);
return 0;
}
更詳細內容,請見《宏的高級用法——X-MACRO》