您的位置 首页 测评

关于C言语枚举类型不得不说的故事

经济学家说过,路边是不会有100元的,但是如果有,你还是要捡起来。同理,在貌似万物免费的网络时代,你是很难找到有针对性的好资料的,但是如果有,希望你能认真学习吸收。比如笔者今天写的这一篇:)一今天这篇

经济学家说过,路旁边是不会有100元的,可是假如有,你仍是要捡起来。

同理,在形似万物免费的网络时代,你是很难找到有针对性的好材料的,可是假如有,期望你能认真学习吸收。

比方笔者今日写的这一篇:)

今日这篇文章要同享两个事例,第一个事例关于枚举,第二个事例也是关于枚举。

照旧例,先来几句简略的照猫画虎。C言语枚举类型用于针对某一类目标界说一个调集,依据该类目标的实践意义将调集中的元素逐个罗列出来,然后用实践取值为整数(枚举值)的文本式变量描绘这些元素。

这些枚举值相当于一种助记符,能够供给对某一类目标愈加靠近实践的描绘,所以不只能够添加程序的可读性,还能协助码农们别离并回忆它们。当然,在详细的编程活动中,枚举型也会暂时把码农从单调的计算机国际摆脱出来,找回一点人间烟火的感觉。

科普结束,咱们或许开端疑惑了。已然从数学概念上来了解,枚举界说了一个“调集”,用整型取值来表明调集中的“元素”,逻辑上如此明晰并且简略,这还或许出什么问题?

你想,平地里能够起惊雷,暗沟里也会翻了船,编程写出个bug来,莫非不是意料之外、情理之中的作业吗?

只不过,我一直搞不清楚,编程时,究竟一往无前无惊无喜是美好的,仍是遇到问题百转千回更美好?

提到美好,我不由想起范伟的一段经典台词,脑袋大脖子粗的范伟端着个大脸盘子,无神的眼睛里透露着看破红尘的沧桑,慢条斯理地答复:“什么是美好?美好就是我饿了,看他人拿个肉包子,那他就比我美好;我冷了,看他人穿了一件厚棉袄,他就比我美好;我想上茅房,就一个坑,你蹲那了,你就比我美好。”

同样是简略的枚举,你用时没碰到问题,而我碰上了,你说咱俩究竟谁比谁美好?

道家有一句很奥妙的话:天下本无事,庸人自扰之!

坚定地秉持唯物主义的四有青年们对这句话当然是不以为然孔兼鼻毛的。

你见或许不见,事儿就在那里,不来不去,可是依照老庄的思维,合着是咱们自己没事找事了?

对此等断言,笔者只能微微一笑很倾城,接着苦笑很悲情了。由于我遇到的枚举问题就是自己瞎搞出来的。

原本,搭档小周给我的代码里有这么两段代码:

void SendI2cAck(void)

{

SetSdaDir(IO_DIR_OUTPUT);

SetSdaLow();

ToogleScl();

}

void SendI2cNak(void)

{

SetSdaDir(IO_DIR_OUTPUT);

SetSdaHigh();

ToogleScl();

}

明眼人一眼就看出来了,虽然每段代码都很简略,彻底没有必要改写,可是由于这两段代码的重复度很高,它们彻底能够改写成一个带参量的函数。

特别对咱们这种对代码整理和重构有着偏执型激动的人来说,让咱们不重构几乎比杀了咱们还难过,此刻不改,更待可时?

所以我三下五除二,把代码改成了下面的姿态:

void i2c_ack(e_I2cAck ack)

{

SetSdaDir(IO_DIR_OUTPUT);

if(I2C_ACK == ack){

SetSdaLow();

}else{

SetSdaHigh();

}

ToogleScl();

}

在这儿,笔者界说了一个枚举类型

typedef enum{

I2C_ACK = 0,

I2C_NAK = 1

}e_I2cAck;

然后,由于鬼才知道的原因,笔者给出了如下函数声明,也在不经意间埋下了一颗炸弹。

void i2c_ack(uint8_t ack);

看到这儿,大咖们或许在捏着下巴上唏嘘的胡茬子会心一笑了,可是小白们或许仍是不知所以。

虽然函数的声明误写成了i2c_ack(uint8_t ack),可是它的界说i2c_ack(e_I2cAck ack)仍是对的,在调用函数传递函数参量的过程中,传进去的I2C_ACK莫非不仍是0,I2C_NAK不仍是1吧?

笔者也是这么想的,当然,刚开端的时分,我底子没有发现把声明写错的“笔误”。

不过,埋下的炸弹终会暴雷。由于重构后的程序运转不正常,我很快发现了声明和界说不一致,可是,so what?我仍然茫无头绪,所以只好架上仿真器单步调试,看看究竟会产生什么。

我追寻调试到调用i2c_ack的当地,眼见着把I2C_ACK=0传了进去,到了函数里边后,居然没有履行if(I2C_ACK == ack)这个分支。所以我试着添加了一个uint16_t型的暂时变量,将函数参量赋值给它。

不看不知道,一看吓一跳,传递进来的参量居然成了0x5A00。

追寻到这儿,又查阅了相关材料后,我好像有些开窍了。

虽然8位整型便能够包括这次枚举界说中的最大值,可是枚举类型的尺度是16位,而非所幻想的8位。

这样一来,假如函数声明中的参量是16位,那么,在参量传递时,传递进来的枚举类型的I2C_ACK会被处理成16位整型的‘0’,函数会依照‘0’分支进行正确的处理。可是,由于函数声明中的参量是8位,所以,实践上传递进来的枚举类型的I2C_ACK只取了1个8位整型的‘0’,进入函数内部后,它又会被扩展成16位整型,而函数内部的变量是局部变量,地址空间都在stack里边,它扩展时会选用相邻的高位地址来填充该16位整型的高8位。这样,在传递0时,数据低八位仍然是0,可是高八位就不必定了。

原本不改程序,还不会遇到这些问题,看看,是不是天下本无事,庸人自扰之?

千百年来,多少人苦苦思索,究竟是什么力气,掌握着咱们的命运,让咱们阅历苦楚和欢喜?

现在我了解了,生命不息,折腾不止,正是这种没事找事瞎折腾的力气主宰了咱们的喜怒哀乐呀!

笔者同享的第二个关于枚举类型的事例,是愈加便当地运用枚举类型进行数组索引的一种新用法,不敢藏私,与诸君同享之。

如前所述,枚举的一个重要效果是添加程序的可读性,以助记符的方式协助程序员回忆和了解代码。比方,笔者在完成软件守时器时(见文章《如何用单个守时器一致地完成多种守时运用》)就从前以枚举类型界说了软件守时器的ID或许说软件守时器的称号。

为了让读者愈加便于了解,仍是要花开两朵各表一枝,叨咕叨咕软件守时器。

一个嵌入式产品中会有许多守时逻辑,最好也是最通用的处理方式就是规划一种结构体方式的软件守时器,令一个软件守时器对应一种守时逻辑,一切软件守时器构成一个结构体数组,各种守时逻辑的完成时就是在结构体数组中的成员变量上进行处理。

在这儿,以可读性较强的枚举类型界说软件守时器的ID,枚举值依据各个守时运用的详细逻辑命名,比方说,检测输入信号的周期性守时器INPUT_DETECT_PTMR、喂看门狗的周期性守时器FEED_WATCHDOG_PTMR、监测体系状况的周期性守时器SYS_MONITOR_PTMR、蜂鸣器报警的屡次守时器BEEPTWEET_TTMR、总线busoff后康复通讯的单次守时器BUSOFF_TTMR等。

高智商的程序猿们打眼一看,就能从枚举值的命名上看出守时器背面的逻辑来,枚举增强程序可读性的功用可见一斑,可是,问题是,您老人家看了解了,单片机呢?

这么说吧,咱们在用Timer[INPUT_DETECT_PTMR]处理守时逻辑时,怎样确保这个守时器节点就能详细对应到检测输入信号的周期性守时器吗?

智商在线的你必定不会由于INPUT_DETECT_PTMR这个文本化的枚举写得如此得昭彰就想当然地以为单片机也能“心同此心”的。实践上,假如你不做一些特别的处理,单片机必定不知道Timer[INPUT_DETECT_PTMR]就能够表征检测输入信号的周期性守时器的。

愿你三冬暖,愿你春不寒,愿你天亮有灯,下雨有伞。程序猿想和单片机接下此等心心相映的缘,需求做点编程作业,自动手拉手线牵线。

明显,INPUT_DETECT_PTMR此类软件守时器节点ID想在数组中充任下标运用,下标和枚举之间要具有天然的一致性。

所幸,数组Timer[N]的下标规模是[0,N-1]间的正整数,而整型取值正是枚举类型的天然特点。所以,第一步是要确保守时器枚举也从0开端取值,然后取值顺次加一,在[0,N-1]间逐个占位。

第二步,在守时器数组的初始化阶段,要用整数型下标进行一次for循环,将各个软件守时器节点的ID初始化为对应的数组成员的下标,即Timer[i].timer_id = i,这儿的i有三个效果,一是for循环体中的循环变量,二是数组成员下标,三是赋值给守时器ID。

在体系运转阶段,引证某个软件守时器时,以该软件守时器对应的枚举类型常量做为数组下标,引证以该ID标识的软件守时器节点,即用Timer[timer_id]直接寻址详细的软件守时器,

这儿有一个优点是,避免了以整型变量为下标引证守时器时需求查找该守时器节点在软件守时器数组中对应的下标的繁琐,并且提高了程序的可读性。

其间妙处,你品,你细心品!

声明:本文内容来自网络转载或用户投稿,文章版权归原作者和原出处所有。文中观点,不代表本站立场。若有侵权请联系本站删除(kf@86ic.com)https://www.86ic.net/ceping/114416.html

为您推荐

联系我们

联系我们

在线咨询: QQ交谈

邮箱: kf@86ic.com

关注微信
微信扫一扫关注我们

微信扫一扫关注我们

返回顶部