没人比我更懂if-else和switch-case

嵌入式Linux

共 2408字,需浏览 5分钟

 ·

2020-12-17 21:13

美国大选中的if-else

看最近的新闻,我很好奇,如果用if-else来表达美国大选那些事,你有哪些想法?

if(bTrumpWin)
{
// balabala...
}
else
{
// balabala...
}

也没啥毛病,简单地就是这种方式。

if(bBidenWin)
{
// balabala...
}
else
{
// balabala...
}

是不是一样的?是的,就他们俩竞选。

想想,bTrumpWinbBidenWinbool变量来的,也就是说,是有别的地方产生这个结果。那如果没有这种直接的结果呢?

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非常通俗易懂,但是在程序设计中,你真的用对了吗?

切!不就是ifelse吗!呵呵呵……其实也没错。

条件判断语句

看个笔试题:

请填写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-elseswitch-case的执行流程:

我想,switch-case应该是在编译的时候产生了对应的跳转表来指示case的地址,一般编译器会对这些内容进行优化,所以这样就会有很高的执行效率。

if-else的注意事项

ifelse在语法上使用很自由,写了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


推荐阅读:
专辑|Linux文章汇总
专辑|程序人生
专辑|C语言
我的知识小密圈

浏览 10
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报