取反运算符`~`与原码反码补码

1,354 阅读3分钟

今天在看【你不知道的JavaScript中卷】第一部分第四章的4.3节显示强制类型转换,里面有一个小部分“2.奇特的~运算符”(P59)有提到取反运算~,但是在日常开发中我从来没用到取反运算~。 下文中会有很多内容是直接从书中摘抄下来的,我就不搞二次加工了。

文章提到~42的值为-43,但是不知道怎么转换的,已经把很基础的东西给忘记了。

这里先提一下正负数在二进制的表示。

正负数原码、反码、补码

下面以8位有符号整型来示例。

  • 原码:符号位 + 数值位
  • 反码:
    • 正数反码:与原码一致
    • 负数反码:符号位 + 数值位取反
  • 补码
    • 正数补码:与原码一致
    • 负数补码:负数反码 + 1
数字 符号位 数值位 原码 反码 补码
5 0 0000101 00000101 00000101 00000101
-5 1 0000101 10000101 11111010 11111011

如果不使用补码,只用原码相加的结果是错误的, 但是使用补码后,对应正负数的补码相加结果为0,符合逻辑。

取反运算符~及其作用

-1是一个“哨位值”,哨位值是那些在各个类型中补赋予了特殊含义的值。在C语言中我们用-1来代表函数执行失败,用大于等于0的值来代表函数执行成功。

JavaScript中字符串的indexOf()方法也遵循这一惯例:

var a = "Hello World"
if (a.indexOf("lo") >= 0) { // true
    // 找到匹配
}
if (a.indexOf("lo") != -1) { // true
    // 找到匹配
}
if (a.indexOf("ol") < 0) { // true
    // 没有找到匹配
}
if (a.indexOf("ol") == -1) { // true
    // 没有找到匹配
}

>= 0== -1这样的写法不是很好,称为“抽象渗漏”,意思是在代码中暴露了底层的实现细节,这里是指用-1作为失败时的返回值,这些细节应该被屏蔽掉。

这里~取返运算就有作用了,他可以很好处理这个-1,因为~-1值为0,是一个falsy value,可以在代码中屏蔽掉底层实现细节。

var a = -1
~a // 0 <-- falsy value

在代码中我们可以如下写法:

var a = "Hello World"

~a.indexOf("lo") // -4 <-- truthy value

if (~a.indexOf("lo")) { // true
    // 找到匹配
}

~a.indexOf("ol") // 0 <-- falsy value
!~a.indexOf("ol") // true <-- !0

if (!~a.indexOf("ol")) { // true
    // 没有找到匹配
}

这里如果indexOf()的返回值为-1,那么~运算符会将其值转换为假值0,其他情况一律转换为真值。

你可能想问为什么~-1结果是0,前面一小节特意回忆了一下原码、反码、补码,下面是-1的各种码:

数字 符号位 数值位 原码 反码 补码
-1 1 0000001 10000001 11111110 11111111

-1的补码是11111111,此时我们做~取反运算得到00000000,最后得到的值就为0

个人感想

上面这种处理-1的方法可能真的很符合那种隐藏底层的实现细节,但是对于我来说应该还是会去使用>= 0这种方式,毕竟~取反运算符基乎很少用,也不容易理解,我在想如果遇上了!~a.indexOf("ol")这样的代码,估计是不容易阅读,首先会想到是位运算符,然后要想为什么要这样处理,当然这只是我平常开发时的想法,我想对自己说:很局限,没深度。

主要是这个思维,我从来没有想到过!继续加油吧!