前腐后继 发表于 2006-12-8 20:00:00

[转帖]《撒旦的语法》

<strong><br/><br/></strong>译者:Panic&nbsp;<p></p><p>&nbsp;&nbsp;&nbsp;&nbsp;很多人写代码是照猫画虎,这些“猫”最终就变成了教条(注1)。一旦教条被人熟知,不同的变量,数值,功能就被按照教条使用,然后用一些“胶水”代码组合起来,实现需要的方案。通过对语法的深入了解,我们可以消除很多的“胶水”。这篇文章举了几个怪异的C语法的例子,以及如何在不导致歧义(注2)的情况下,利用(滥用?)他们实现更高效的代码。</p><p>记得返回值</p><p>我的第一个关于“教条编程”的例子将讨论格式化输出函数sprintf。下面这段代码的写法并不鲜见:<br/>&nbsp;&nbsp;&nbsp;&nbsp;sprintf(str1,&nbsp;"Old&nbsp;v=%d\t",v);<br/>&nbsp;&nbsp;&nbsp;&nbsp;/*&nbsp;Some&nbsp;code&nbsp;that&nbsp;plays&nbsp;with&nbsp;v&nbsp;*/<br/>&nbsp;&nbsp;&nbsp;&nbsp;sprintf(str2,&nbsp;"New&nbsp;v=%d",v);<br/>&nbsp;&nbsp;&nbsp;&nbsp;strcat(str1,&nbsp;str2);<br/>&nbsp;&nbsp;&nbsp;&nbsp;printf(str1);</p><p>大部分的sprintf实例使用一个临时的字符串变量作为它的第一个参数。这就是那个根深蒂固的教条:“sprintf需要一个临时字符串”。然而更好的教条是符合语法的,“sprintf需要一个指向字符数组的指针”。这提醒我们,可以用一个返回char&nbsp;*的函数替代它作为第一个参数,从而节省一个临时缓冲区的空间。例如:<br/>&nbsp;&nbsp;&nbsp;&nbsp;sprintf(str,&nbsp;"Old&nbsp;v=%d\t",v);<br/>&nbsp;&nbsp;&nbsp;&nbsp;/*&nbsp;Some&nbsp;code&nbsp;that&nbsp;plays&nbsp;with&nbsp;v&nbsp;*/<br/>&nbsp;&nbsp;&nbsp;&nbsp;sprintf(strchr(str,&nbsp;'\0'),&nbsp;"New=%d",v);<br/>&nbsp;&nbsp;&nbsp;&nbsp;printf(str);<br/>&nbsp;&nbsp;&nbsp;&nbsp;<br/>当直接使用指针的时候,你必须谨慎的避免NULL指针。在这个例子里,str(str,'\0')的有效得益于第一个sprintf。sprintf还隐藏了一个好处-它返回已经写入缓冲区的字符数量-这个数量可以节省一次对strlen的调用!检查你的代码吧。祝你好运!</p><p>另一个例子,如果得到一个字符串的长度,是为了在某些操作时判定它是否有效,你也许会这样写:<br/>&nbsp;&nbsp;&nbsp;&nbsp;len&nbsp;=&nbsp;strlen(GetFileName());<br/>&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;(len&nbsp;&gt;&nbsp;0)<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;;&nbsp;/*&nbsp;File&nbsp;name&nbsp;is&nbsp;not&nbsp;null&nbsp;*/</p><p>然而,如果你只是打算用它和0比较的话,为什么不这样写:<br/>&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;(GetFileName()&nbsp;!=&nbsp;'\0')<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;;&nbsp;/*&nbsp;File&nbsp;name&nbsp;is&nbsp;not&nbsp;null&nbsp;*/</p><p>这次,我们不仅节省了一个缓冲区(这很普通,我们直接使用返回值(注3)-这也避免了一次额外的拷贝),而且还在正确的检查了返回值的同时,消除了前面strlen的调用开销。别忘了,相同的结果可以用等价的方法得到。<br/>&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;(*GetFileName&nbsp;()&nbsp;!=&nbsp;'\0')<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;;&nbsp;/*&nbsp;File&nbsp;name&nbsp;is&nbsp;not&nbsp;null&nbsp;*/</p><p>因为字符串只是一个(可以用char&nbsp;*&nbsp;指向的)结构,上面的方法同样可以用于返回结构体,C++类或其指针的函数,直接提取你需要的元素。例如:<br/>&nbsp;&nbsp;&nbsp;&nbsp;struct&nbsp;POINT&nbsp;GetCurrentPos(void);</p><p>&nbsp;&nbsp;&nbsp;&nbsp;int&nbsp;y;</p><p>&nbsp;&nbsp;&nbsp;&nbsp;y&nbsp;=&nbsp;GetCurrentPos().y;</p><p><br/>或者,</p><p><br/>&nbsp;&nbsp;&nbsp;&nbsp;printf(GetDevice()-&gt;pName);</p><p>编译器可能会把结构创建为临时对象,然后把他们的指针作为隐含参数,所以你不必顾虑把结构体拷贝进栈或者出栈的开销。</p><p>从另一个角度,你还记得“函数无法返回一个字符串”的课程吗?其实你可以。方法很多。例如,创建一个名为STRING的结构体,里面包含一个字符串数组,你可以在函数中返回这样一个STRING,然后像上面那样引用其中的字符串。<br/>&nbsp;&nbsp;&nbsp;&nbsp;struct&nbsp;STRING&nbsp;{&nbsp;char&nbsp;str;&nbsp;};</p><p>&nbsp;&nbsp;&nbsp;&nbsp;struct&nbsp;STRING&nbsp;GetName(void)<br/>&nbsp;&nbsp;&nbsp;&nbsp;{<br/>&nbsp;&nbsp;&nbsp;&nbsp;.<br/>&nbsp;&nbsp;&nbsp;&nbsp;}</p><p>&nbsp;&nbsp;&nbsp;&nbsp;printf(GetName().str);</p><p>实际开发中,这和传递字符串指针相比,用处很少甚至完全无益,但是它的确减少了程序员创建临时变量的需求。</p><p>表达式</p><p>函数调用只是表达式。如果他们返回一个类型(不包括void,那并不是一种真正的类型),那么你就可以把它作为一个普通的表达式用在任何需要的地方,例如一个while循环。这使得循环或者类似的流程被快速短路成为可能,从C的懒惰表达式计算中获益。(看补充)<br/>&nbsp;&nbsp;&nbsp;&nbsp;while&nbsp;(1&nbsp;&amp;&amp;&nbsp;GetNextLine(&amp;str))<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;;</p><p>把表达式中的1改成0,导致整个表达式的结果变成0,GetNextLine函数不会被调用,循环永远都不会运行。我经常使用这种方法在测试代码中取消复杂的if语句块。<br/>&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;(1<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&amp;&amp;&nbsp;ComplicatedExpression1<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&amp;&amp;&nbsp;ComplicatedExpression2<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&amp;&amp;&nbsp;ComplicatedExpression3<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;)<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;;</p><p>通过把1改成全局,静态,变量,我可以在调试时,取消这些代码的运行,或者通过菜单选项改变它,从而在一次编译中测试多个项目。</p><p>结束</p><p>我希望还有一些新的想法告诉你。我忘了讲,代码也可以使用这种方式移除:<br/>&nbsp;&nbsp;&nbsp;&nbsp;/*<br/>&nbsp;&nbsp;&nbsp;&nbsp;.&nbsp;code&nbsp;here&nbsp;.<br/>&nbsp;&nbsp;&nbsp;&nbsp;//*/<br/>然后用在首行添加一个单独的/字符来重新插入这段代码。</p><p>补充</p><p>C使用一个懒惰表达式计算器。这就是说,它只计算需要的表达式来推导最终的结果是TRUE还是FALSE,所以一行类似<br/>&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;(fn1()&nbsp;&amp;&amp;&nbsp;fn2()&nbsp;&amp;&amp;&nbsp;fn3())<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.<br/>的代码,会先计算fn1(),只有在必要时才继续计算fn2(),也就是说,当fn1()返回TRUE的时候。如果它返回FALSE,无论其他表达式如何,都无法使得最终结果是TRUE。大部分人从如下的代码段中凭直觉了解了这一点:<br/>&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;(ptr&nbsp;&amp;&amp;&nbsp;ptr-&gt;Name)<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;printf(ptr-&gt;Name);</p><p>这和Pascal是不同的。</p><p><br/>译者:<br/>&nbsp;&nbsp;&nbsp;&nbsp;本文是Steve&nbsp;Goodwin的大作,译者水平有限,无法很严谨的翻译文中要表达的内容,只能就个人的理解,尽量在含义上贴近原文。尽管对这篇文章的某些观点并不十分理解和赞同,但是对于开发者来说,仍然是很有价值的参考。<br/>&nbsp;&nbsp;&nbsp;&nbsp;原文链接:<a href="http://www.gamedev.net/reference/articles/article1239.asp" target="_blank"><font color="#9c0000">http://www.gamedev.net/reference/articles/article1239.asp</font></a></p><p>注1:原文:This&nbsp;example&nbsp;becomes&nbsp;a&nbsp;template,template本意是模板,但是这里理解为教条更能表达感情的好恶。<br/>注2:原文:without&nbsp;qualifying&nbsp;for&nbsp;the&nbsp;IOCCC&nbsp;(International&nbsp;Obfuscated&nbsp;C&nbsp;Code&nbsp;Contest),意译,不是很准确。<br/>注3:原文:seeing&nbsp;as&nbsp;we&nbsp;use&nbsp;the&nbsp;result&nbsp;directly&nbsp;,这里的result指的应该是函数返回值。</p><p><br/></p>

hengle_lee 发表于 2007-2-2 16:34:00

<p>写的不错。其实感觉和&nbsp; “代码切片”应该有点联系</p><p>也是为了“代码重载”</p>
页: [1]
查看完整版本: [转帖]《撒旦的语法》