「位运算学习」C++卡常之i++ -> i=-~i
其实本篇文章不是为了卡常,而是为了讲解位运算的一个应用(
前置芝士
原码表示法是整数的一种简单的表示法,符号位用 \(0\) 表示正号,用 \(1\) 表示负号,数值一般用二进制形式表示。
整数的反码和补码
正数 | 负数 | \(+0\)「看作正数」 | \(-0\)「看作负数」 | |
---|---|---|---|---|
原码 | 符号位为 \(0\) 数值位用二进制表示 |
符号位为 \(1\) 数值位用二进制表示 |
\(00000000\) | \(10000000\) |
反码 | 与原码相同 | 符号位为 \(1\) 数值位在原码基础上按位取反 |
\(00000000\) | \(11111111\) |
补码 | 与原码相同 | 在反码基础上 \(+1\) | \(00000000\) | \(00000000\) |
(关于补码的详细知识,可以参考知乎上的一篇回答)
简单来记:负数的补码形式可以由对应正数的二进制按位取反后 \(+1\) 得到,且负数符号位为 \(1\)。
取反
~:取反(把运算数的各个二进制按位求反)
\(e.g.\)
-
~\(9\)
原码:\(00001001\)
「这里最左位的 \(0\) 表示正,如果是 \(1\) 就表示负,其余位按二进制表示」
反码:\(00001001\)
「正数的反码等于它的原码」
补码:\(00001001\)
「正数的补码等于它的原码」
按位取反:\(11110110\)
「这时得到的是取反后的补码,我们要把它转化成原码」
「因为第一位是 \(1\),所以这是负数,要按照负数的转换法则倒退回去」
「\(负数的反码+1=补码\),因此\(负数的反码=补码-1\)」
反码:\(11110101\)
「负数的原码按位取反(符号位除外)即为反码,因此负数的反码按位取反(符号位除外)就可转换为原码」
原码:\(10001010\)
转换为十进制,即为:\(-10\)
-
~\(-9\)
原码:\(10001001\)
「最左位是 \(1\) 表示是负数」
反码:\(11110110\)
「负数的原码转换为反码,符号位不变,其余位按位取反」
补码:\(11110111\)
「负数的反码转换为补码,\(+1\) 即可」
按位取反:\(00001000\)
「按位取反后为正数,因为正数的补码等于原码,所以直接转十进制即可」
转十进制,即为:\(8\)
这时可以进入正题了。
C++ 循环的一个奇技淫巧
for(int i=1;i<=n;i++)
这应该算写的最多的代码之一了,殊不知它还可以写成这样:
for(int i=1;i<=n;i=-~i)
i=-~i
等价于 i++
,但据说要比 i++
快一些。大概类似于 ++i
比 i++
快一些吧。「其实也快不了多少,毕竟有编译器优化……」
update:
见善则迁,有过则改。
——《周易》
此处对上面的说法绳愆纠缪。具体详见【Debug心得&笔记——⑳ i++
和 ++i
的那些事儿】。
正确性证明
设\(x\)在二进制下表示为:\(abcdefgh\)
\(a\)取反后为\(A\)
-
当\(x>=0\)时,\(a=0\)
原码:\(0bcdefgh\)
反码:\(0bcdefgh\)
补码:\(0bcdefgh\)
取反:\(1BCDEFGH\)
反码:\(1BCDEFGH\color{red}-1\)
「这里\(\color{red}-1\)就是将从右往左数第一个为\(1\)的数变为\(0\),它右边所有的\(0\)变为\(1\)」
「所以在变为原码时,从右往左数第一个为\(0\)的数变为\(1\),它右边所有的\(1\)变为\(0\),其余位按位取反」
原码:\(1bcdefgh\color{red}+1\)
这时转为十进制即为:\(-x-1\)
再取相反数即为:\(x+1\)
-
当\(x<0\)时,\(a=1\)
原码:\(1bcdefgh\)
反码:\(1BCDEFGH\)
补码:\(1bcdefgh\color{red}-1\)
取反:\(0BCDEFGH\color{red}+1\)
反码:\(0BCDEFGH\color{red}+1\)
这时转为十进制即为:\(-x-1\)
再取相反数即为:\(x+1\)
证毕。