读大学时,我的C语言老师课间喜欢和我们吹牛,动不动就说老子当年写过几万行的代码。才读大学的我,被老师忽悠的一愣一愣,心想我什么时候也学有所成,写出几万行的代码,月薪过万不是梦啊!
然而事实是,工作接触了一些大神和工程师后才知道,很多人虽然真的可以写出成千上万行代码,但不意味着他们多有水平。那些动不动就吹嘘自己写过多少代码的人,往往是没什么事迹可吹。真正的大神,都以代码简短、高效、稳定、可读性强为荣。今天要说的是代码的简短和高效,很多人往往认为简短的代码就代表高效,殊不知,这和那些吹嘘自己写过多少万代码的人一样肤浅。下面我会通过单片机编程中常见的代码说明,简短的代码未必是高效的代码。
1 自增、自减操作
我们经常会看到这些写法:
i+ +
i- -
同样的,我们还知道这种写法:
i = i+1
i = i-1
那么要问,哪种写法更好呢?大部分人会说i+ +
这种,为什么呢?他们往往会说,i+ +
这种写起来简单,高效。i+ +
的执行效率比i = i+1
要高。
书写效率的优势是肯定的,敲3次键盘和敲5次键盘当然不一样,关于输入效率,其他文章里我会提到,这里暂且不谈,此处只讨论代码效率。
有人说,i+ +
只操作一个变量,i = i+1
执行了+1后,还需要赋值一次,当然效率就低。这个观点看起来没什么问题,却犯了形式主义错误。
大多数人的看法,都是通过语言本身分析。也就是说,i = i+1
给人的直觉,是先执行了i+1,再赋值,是两个动作;而i++
无论怎么看,都是一个动作,但这并不是事实。事实是,在Keil-C51开发环境下,他们都会被编译为同一条指令,如图1所示。
红色框线是i+ +
编译后的汇编指令,蓝色框线是j = j+1
编译后的汇编指令。就算不懂汇编也可以看出,都是INC指令。0x08和0x09是变量i和j的物理地址,仅仅是地址不同。这意味着,不管你是写i+ +
还是j = j+1
,他们都被编译为同一条指令被CPU执行。
结论:i+ +
和i = i+1
这两种写法在单独使用时,执行效率是完全一样的。出于个人习惯,写哪种都可以。
2、较少的行数效率就高
有一种看起来简单高效的写法,蛊惑了很多初学者多年,即:if(i++ == x)
和if(++i == x)
很多老师都会花很多篇幅讲他们的区别和用法。然而在Keil-C51编译环境中,纠结这两种用法是蠢的事情。本文为了更好的说明,我还是先解释一下if(i++ == x)
和if(++i == x)
的区别。
△代码①
if(i++ == 2)
{
k=k+5;
//用户代码
}
代码①中,当i的值等于3时,执行括号里面的代码。
△代码②
if(++i == 2)
{
k=k+5;
//用户代码
}
代码②中,当i的值等于2时,执行括号里面的代码。
很多人知道这个结果,并以知道这个“技巧”为荣。这就苦了很多初学者,两种写法得到不同的结果,导致调试很久。如果初学者问有经验的人,他们大多是这么解释:
if(i++ == 2)
是先执行i++
,让在执行if条件语句判断,而if(++i == 2)
是先引用i的值判断,再执行++i
。有道理吗?有,因为编译器确实是这么编译的。有兴趣的读者可以自己debug试一试。所以呢,很多人写程序的之后,会巧用这个技巧,写出自以为高明的代码。实际上并不怎么高明,我来解释一下为什么。
我们写的代码,大多数是要拿给别人看的,除非你是初学者,写的代码到处是拼音、错拼的英文,不规范的缩进。但作为老师、硬件开发者,代码除了要达到目的,还要兼顾维护性。可维护性包含的内容很多,最直观的就是阅读性,所以先说一下阅读性。
对于初学者,最开始是先接触i++
、++i
、i = i+1
这种基础内容。我们也很容易在老师那知道,i++
、++i
都是自增操作。所以直觉告诉我们,前文提到的两段代码执行结果应该相同,然而测试后才发现不同。这里说直觉,是的,人就是无法避免直觉性的判断,虽然很多时候我们被直觉欺骗。就比如前文提到的i++
和i = i+1
,很多是根据直觉判断谁的效率高,但没有任何事实依据。我不反对老师讲课的时候讲解if(i++ == x)
和if(++i == x)
的区别,但我希望最后加上一句:不要纠结这些用法,忘记了网上搜索一下即可。因为你们可以把代码写为这种格式:
unsigned char i,k;
i = i + 1;
if(i == 2)
{
k = k + 5;
}
这样不管写为i++
、++i
还是i = i + 1
;都可以实现预期结果。这种写法,读者不需要考虑if(i++ == x)
和if(++i == x)
的区别。我的代码里,都是这种写法,有朋友看了后对此嗤之以鼻。他说,说明明可以写为if(++i == 2)
这种格式,一行不比两行效率高吗?一行不比两行看起来简洁吗?对于这种人,最开始做朋友没什么,时间久了了,类似“高明”的建议多了,我就敬而远之。
我们来看看所说的1行是不是真的比2行高效,先设计一段2行代码:
△代码③
unsigned char i,k;
++i;
if(i == 2)
{
k = k + 5;
}
Debug结果如图2所示
++i和if(i == 2)编译后汇编语句如下:
INC 0x08
MOV A,0x08
CJNE A,#0x02,C:0010
读者可能说,我不懂汇编啊,没关系,姑且先记下这3行汇编,我们看一看下面的代码。
△代码④
unsigned char i,k;
if(++i == 2)
{
k = k + 5;
}
Debug结果如图3所示
红色框线内是if(++i == 2)
编译后的汇编语:
INC 0x08
MOV A,0x08
CJNE A,#0x02,C:0010
这三行是不是似曾相识呢?没错,和代码③编译的结果完全相同,这也意味着,不管怎么写,编译后的汇编语句相同,执行效率自然也相同。所以不存在后者比前者高效的结论。
至于阅读效率,我只谈谈我的个人感受。当写为代码③的格式时,不管你是老师授课,还是研发者,当把代码给他人参考、修改,只要读者不是太离谱,都看得懂。但是写为代码④的格式后,至少初学单片机C编程的人,阅读起来会产生麻烦,不幸这个人还很健忘,忘记了if(++i == 2)
和if(i++ == 2)
的区别,没准老师还没讲过区别,那么几乎都会出现理解问题。
相比Python这种优雅的语言,就取消了自增自减操作,这不是倒退,这是一种进步,至少,没有了自增自减,程序设计者不需要再纠结上述困扰。
3.简短的语句未必高效
我写程序的时候,switch case
语句和if语句都经常用到。早期的开源设计代码中,几乎都是使用switch case,后来逐渐的使用if
或if elseif
。具体原因我会在其他章节专门介绍这两种语句的时候详细说明。本文着重说一下两个语句效率问题。
朋友、同事之间互看代码是常有的事情,也是一个朋友,看到我一段代码里是这样的:
unsigned char test1()
{
unsigned char m=2,n;
if(m == 0)
{
n = m * 10 + 1;
}
if(m == 1)
{
n = m * 10 + 1;
}
if(m == 2)
{
n = m * 10 + 1;
}
return n;
}
当时我的整个程序是有问题的,找朋友是请教一下,给我看看代码可能哪里出错。结果看到上面这个函数后(这是精简后的示例,不是实际代码,但结构相同),就开始喷了。
朋友:你这种水平的代码,我看都懒得看,你先优化一下代码,我再给你检查问题。
我:哪里体现出水平有问题呢?
朋友:代码讲究的是简短、高效,你这么多if,看着都难受。你这里如果用switch case语句,看着多舒服,执行起来效率也更高。
我:••••••
后来再也没找他请教过问题,因为我们的水平完全不再一个档次,和这种人讨论编程简直就是侮辱自己的智商。
实际情况真的有如我朋友所言吗?switch case语句就真的比if这种高效吗?这次我们换个方式看一下效率,看看switch case语句是否在效率上比if语句更快。测试代码如下:
unsigned char test1()
{
unsigned char m=2,n;
if(m == 0)
{
n = m * 10 + 1;
}
if(m == 1)
{
n = m * 10 + 2;
}
if(m == 2)
{
n = m * 10 + 3;
}
return n;
}
unsigned char test2()
{
unsigned char m=2,n;
switch (m)
{
case 0 : n = m * 10 + 1; break;
case 1 : n = m * 10 + 2; break;
case 2 : n = m * 10 + 3; break;
default: break;
}
return n;
}
void main()
{
unsigned char tmp;
while(1)
{
tmp = test1();
tmp = test2();
}
}
两个测试函数(test1()
和test2()
)的功能是一样的,我们通过debug功能看一下效率问题。Debug调试结果如图4所示。
红色箭头所指的“sec”为执行代码所经过的时间,在执行tmp = test1()
之前,时间已经经过了0.00019450秒,记为t0。黄色箭头所指的即为即将执行的代码,按下“F10”键(或单击“Step Over”)后,如图5所示。
执行tmp = test1()
后,时间变为0.00020500秒,记为t1。
此时即将执行的代码为tmp = test2(),再次按“F10”键,时间变为0.00021550秒,记为t2。如图6所示。
根据两次执行结果,可得到:
执行tmp = test1()
的时间 = t1 - t0 = 0.00001050秒
执行tmp = test2()
的时间 = t2 - t1 = 0.00001050秒
至此,我们可以说,把test1()里面的if语句改为switch case语句并没有提高效率,反倒是完全一样。
读者们,你们能根据前文的方法,发现为什么两种写法效率一样吗?有兴趣的读者可以把if语句改为if else语句与switch case语句再次对比,能否发现什么
总结:if语句改为switch case语句,确实让代码行数少了很多,尤其是在出现多个if的时候,但“看起来”简短的语句,也不能代表代码效率的高效。这里还要补充一点,虽然if语句行看起来没有switch case语句简短,但阅读效率未必就低下,同时,如果需要多重判断(往往上百次上千其次),switch case语句也会显得臃肿,那种情况下,会有更好的写法,我会在其他文章详细介绍。
** 上述测试环境为Keil4 C51 @12Mhz **
我的那些“朋友”,至今也坚信i++
比i=i+1
高效,switch case
比if
高效。他们都犯了形式主义错误,很多单片机C语言老师或工程师,往往把其他编译环境的结论带到单片机领域,在他们眼中,并无不可,结果却误导了很多人。我在学习的过程中,也经常被各种人被所谓的经验误导。学习编程,就是要敢于质疑权威,这些权威可以是老师,你的师兄,也可以是我,只有这样,才能学到真正的知识。
作者水平有限,编写过程中难免出现不当之处,还望读者诸君不吝赐教,或许您有好的建议,欢迎与我联系QQ:136678431,作者将报以实质性奖励。