面试奇葩——交换两变量值的一些邪门歪道
交换两个变量的值,最常见的写法是
1 2 3 4 5 | int i , j ; int temp ; temp = i ; i = j ; j = temp ; |
这种写法相信任何学过程序设计语言的都知道。
然而有些着三不着两的极品面试官却喜欢追问,不用中间变量应该怎么写?这一追问不要紧,追出了很多邪门歪道,例如
1 2 3 4 | int i , j ; i = i + j; j = i - j ; i = i - j ; |
这样的写法,非常晦涩,但挡不住这样写的人为少定义了一个变量而洋洋得意。然而这样写真的行得通吗?运行一下下面的代码就明白了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | #include <stdio.h> #include <limits.h> int main( void ) { int i , j ; int temp ; i = INT_MAX ; j = INT_MAX - 1; puts ( "交换前:" ); printf ( "i = %d , j = %d \n" , i , j ); temp = i ; i = j ; j = temp ; puts ( "交换后:" ); printf ( "i = %d , j = %d \n" , i , j ); i = INT_MAX ; j = INT_MAX - 1; puts ( "交换前:" ); printf ( "i = %d , j = %d \n" , i , j ); i = i + j ; j = i - j ; i = i - j ; puts ( "交换后:" ); printf ( "i = %d , j = %d \n" , i , j ); return 0; } |
运行结果通常都是荒诞不经的。原因很简单, 计算 i + j 时发生溢出,而溢出在C语言中是一种未定义行为,所以得到什么样的荒诞结果都不奇怪。
更极品的回答是用乘除法。
1 2 3 | i = i * j; j = i / j; i = i / j; |
这不明摆着更容易溢出吗?甚至会发生除以0的错误。可居然还有人认为“其实不然”,“不会溢出”。严重怀疑这是三鹿奶粉喝多了的后遗症。
有的面试官可能自以为自己还没愚蠢到这种程度,因为他心目中的“标准答案”是通过异或运算。
1 2 3 | i ^= j; j ^= i; i ^= j; |
这种写法虽然不会产生溢出,但使用条件受限,因为只能用于整数类型,浮点类型、结构体类型等完全不适用。而且除了少用了一个临时变量,没有其他任何好处。
有些人天真地以为这种写法对于整数类型变量来说会更快,其实这种猜测毫无根据。很难说异或运算就比赋值快,据一份公开发表的数据,在某环境下,异或用时为0.050μs,而赋值运算只用0.036μs。
就可读性而言,异或写法显然如同蝌蚪文天书。
更有极品将异或写法发挥到了极限:
1 | i ^= j ^= i ^= j; |
对于C语言来说,这种写法大错特错。因为C语言规定在两个序点之间同一个数据对象最多只能改变一次,否则就是未定义行为。(参见 “牙里长嘴”和“a+=a-=a*a” )这种代码压根就不可以写。如果运行结果碰巧正确,那么只是瞎猫碰上死耗子而已。况且已有报告称在某些环境下这种写法的运行结果是错误的。
异或写法还有一个潜在的问题,比如
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | #include <stdio.h> void swap( int * , int * ); int main( void ) { int i = 5 , j = 10 ; puts ( "交换前:" ); printf ( "i = %d , j = %d \n" , i , j ); swap( & i , & j); puts ( "交换后:" ); printf ( "i = %d , j = %d \n" , i , j ); //自己和自己交换 puts ( "交换前:" ); printf ( "i = %d\n" , i ); swap( & i , & i); puts ( "交换后:" ); printf ( "i = %d\n" , i ); return 0; } void swap( int * p , int * q ) { * p ^= * q ; * q ^= * p ; * p ^= * q ; } |
这段代码运行的结果是:
交换前:
i = 5 , j = 10
交换后:
i = 10 , j = 5
交换前:
i = 10
交换后:
i = 0
不难发现,如果swap()函数的两个实参为指向同一数据对象的指针时,结果是错误的。
当然,你可能认为自己和自己交换有些无聊。但我们不能排除有些算法可能存在这种交换的可能性。
除了这种可能性,还有另外一种可能性,那就是你把代码写错了,多写了一次不必要的变量自己和自己的交换。如果你用的是使用中间变量的算法,那么除了代码有瑕疵,运行结果的正确性还是有保证的。但如果你用的是异或的办法,软件灾难则是召之即来——保准比打车来的还快。
所以结论就是,不要用异或的办法交换变量值。
写到这里,本应打住。不过偶然兴起,心血来潮,随手一搜,竟发现还有更多的奇葩写法。
某java-er竟然一本正经地提出下面两种方法:
1 | a = a + b - (b = a); |
1 | b = a + (a = b)*0; |
我不清楚在java里这是否成立,但在C语言中,这都属于未定义行为。令人拍案的是,该java-er竟然洋洋得意地写到:
"这是java语言写的,但是语言不分种类,任何方法都是通用的,推荐使用a=b+(b=a)*0和a^b的方法,有些环境也许会不通过,但是方法和思想不会错得"
SHIT!
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 没有源码,如何修改代码逻辑?
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
· [.NET]调用本地 Deepseek 模型
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· .NET Core 托管堆内存泄露/CPU异常的常见思路
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· NetPad:一个.NET开源、跨平台的C#编辑器
· PowerShell开发游戏 · 打蜜蜂
· 凌晨三点救火实录:Java内存泄漏的七个神坑,你至少踩过三个!
2012-10-17 质问图灵公司,《C程序设计伴侣》这是不是抄袭、剽窃?
2012-10-17 劣质代码评析——猜数字问题(上)