没人比我更懂if-else和switch-case
看最近的新闻,我很好奇,如果用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的注意事项
好了,下面讲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
#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
#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》