经典代码总结(译)
原文作者:Steve Baker 原文地址:http://www.sjbaker.org/wiki/index.php?title=Cool_Code_list
译者:多多它爹 译文地址:http://blog.csdn.net/liuyu790810/archive/2008/04/15/2293880.aspx
下面是我收集的一些精妙的C代码和一些C++技巧。下面的代码可能需要我们做些调整才能够工作,因为我尽力保持代码实际执行更快,或者更紧凑。
除非我明确指出,否则所有变量都是无符号的32位整数。
倒转一个32位word的所有位
我在Linux fortune cookie代码中看到了如下的代码 (!)
n = ((n >> 1 ) & 0x55555555 ) | ((n << 1 ) & 0xaaaaaaaa ) ;
n = ((n >> 2 ) & 0x33333333 ) | ((n << 2 ) & 0xcccccccc ) ;
n = ((n >> 4 ) & 0x0f0f0f0f ) | ((n << 4 ) & 0xf0f0f0f0 ) ;
n = ((n >> 8 ) & 0x00ff00ff ) | ((n << 8 ) & 0xff00ff00 ) ;
n = ((n >> 16 ) & 0x0000ffff ) | ((n << 16 ) & 0xffff0000 ) ;
可以根据word的大小定制你自己的转换函数。
计算32位word中包含'1'的个数
John C. Wren把如下代码发给我。令人吃惊的是,它很象前一个技巧——位倒转。
n = (n & 0x55555555 ) + ((n & 0xaaaaaaaa ) >> 1 );
n = (n & 0x33333333 ) + ((n & 0xcccccccc ) >> 2 );
n = (n & 0x0f0f0f0f ) + ((n & 0xf0f0f0f0 ) >> 4 );
n = (n & 0x00ff00ff ) + ((n & 0xff00ff00 ) >> 8 );
n = (n & 0x0000ffff ) + ((n & 0xffff0000 ) >> 16 );
判断数n是否为2的幂
b = ((n & (n - 1 )) == 0 ) ;
(注意:如果数n为2的幂,上述代码将b设置为真。在此语意下,0和1也被认为是2的幂)
上述代码通过将数n的最低有效‘1’位的值(least significant '1' bit )置为‘0’实现。如果数n是2的幂,那么,它只有一位是‘1’,经过运算后,n&(n-1)为0,即b为真。
因此,上述表达式中间的那部分也是一个小技巧……
最低有效‘1’位置0
n & (n - 1 )
最低有效n位置为1
~ ( ~ 0 << n)
交换两个整数的值
这段代码的精妙就在于,它没有使用临时变量,并且相对于普通的方法不容易出错。这个方法已经流传了好多年了,所以我并不知道是谁发明了这个方法。
x = x ^ y ;
y = x ^ y ;
x = x ^ y ;
(操作符‘^’代表C/C++语言中的位运算符——异或,而不是人们所想的ex-FORTRAN语言中的幂运算符)
然而,Scott Smith指出,下面这种更为优雅的写法和上面是等价的:
x ^= y ^= x ^= y ;
因为同样的变量在一个语句中被改变两次,上面的语句是不符合C++语法的。
转换数字为ASCII的16进制表示
另一段古老的代码。我第一次见到此段代码是在vi的代码中,但是,它或许比那还要早很多。
" 0123456789ABCDEF " [ n ]
(n的范围: 0..15)
我也见过如下表示法:
n [ " 0123456789ABCDEF " ]
如果变量n为字符变量,并且你关闭了足够多的编译错误检查开关,上述代码即可工作。
转换所有非零值为1
Pat Down发送这段代码给我:
b = !! a ;
如果a为0,则b为0,否则b为1。
小端序还是大端序?
一些计算机存储整数的最重要字节(MSB)在最前面,而后是次重要字节(LSB),另外一些计算机则采用了相反的方法。前面的那种形式我们称之为大端序(big-endian),后面的叫小端序(little-endian)。Intel的计算机均采用了小端序,其余的采用了大端序。
大多数人们并没有意识到术语大端序和小端序是来自小说《格利佛游记》(Gulliver's Travels)。两个国家Lilliput和 Blefuscu 由于争论煮熟的鸡蛋到底是应该从哪头打开而发生了一起可怕、血腥的战争,这即是大端序和小端序的由来。
CPU制造厂商之间的争论同样无聊,而且还有着一些损害。
格利佛说道:“...all true Believers shall break their Eggs at the convenient End: and which is the convenient End, seems, in my humble Opinion, to be left to every Man's Conscience, or at least in the power of the Chief Magistrate to determine。”
总之,采用如下方法来决定你所使用的计算机是大端序还是小端序:
int i = 1 ;
little_endian = * (( char * ) & i ) ;
达夫设备(Duffs Device)
任何经典代码集都不能少了达夫设备:
int a = some_number ;
int n = ( a + 4 ) / 5 ;
switch ( a % 5 )
... {
case 0: do
...{
putchar ( '*' ) ;
case 4: putchar ( '*' ) ;
case 3: putchar ( '*' ) ;
case 2: putchar ( '*' ) ;
case 1: putchar ( '*' ) ;
} while ( --n ) ;
}
printf ( " " ) ;
上面循环打印了‘a’个星号,但是它的循环条件是部分解开的(这对于某些应用提高循环速度很重要)。包括编译器在内的大多数人都会为此而吃惊。
据说,达夫设备最大的问题就是如何缩进的问题。
如果你有任何好的例子,可以发email给我: steve@sjbaker.org
译者注:Duff's Device是一个加速循环语句的C编码技巧。其基本思想是,如果在一个for循环中,其中操作执行得如果足够快(比如说,嗯,一个赋值)——那么测试循环条件占用了循环所用时间的很大部分。循环应该被部分解开,这样数个操作一次完成,测试操作也做的较少。比如,如果你填充一个对象区间,你可能要在一次循环中赋二个或更多连续对象的值。你必须注意终止条件的细节及其他。在这里Duff's Device是个新颖的,有创造力的解决方案。我们来很快地看一个基于Duff's Device的泛型填充算法的实现。函数包含一个switch语句,它的 case语句同时位于一个while循环体内(有一个case语句在外面)。switch内的表达式计算被八除的余数。执行开始于while循环内的哪个位置由这个余数决定。最终循环退出。(没有break)Duff's Device这样就简单漂亮地解决了边界条件的问题。顺便提一下,为什么“case 0”标记在循环外面呢?这样不是打破了对称的美观吗?这样做的唯一理由是为了处理空序列。当余数为零,“case 0”内就需要执行一个多余的测试来判断空序列的可能性。总之所有这些都无懈可击。
结果是判断语句少执行八倍。这样,测试本身在循环持续期间所占的开销比例下降了八倍这个因数。当然,你也同样可以尝试用其他因数。Duff's Device对效率的负面影响可能来自于代码膨胀(一些处理器更善于处理紧凑的循环而不是大的循环)和特别的结构。优化器被做成当遇到更熟悉简单的循环代码时说“啊哈!”,而遇到一些更加技巧性的结构时可能会不知所措从而生成比较保守的代码。