JS中的位操作符

166 阅读4分钟

位操作符

位操作符用于数值的底层操作,也就是操作内存中表示数据的比特(位),位操作符会先把数值转换成 32 位整数再进行操作,对于开发者而言,只需要考虑32位整数即可。

有符号整数使用 32 位的前 31 位表示数值,第 32 位表示数值的符号,0 表示1 表示,这一位也表示符号位

符号位的取值决定了数值其余部分的格式

  • 正值:以真正的二进制格式存储
    • 31 位中的每一位都代表 2 的幂,第一位是 20 次幂,以此类推,为空的位用0填补
  • 负值:以一种称为二补数的二进制编码存储
      1. 先获得目标值的绝对值的二进制表示的结果
      1. 对这个二进制数取反(0->1,1->0),得到反码
      1. 反码+1 得到它的补码
    • 这个补码就是这个负数的二进制表示结果

默认情况下,所有整数都表示有符号数,但是也存在无符号整数,那么它只有正值 ,它的第 32 位就可以用来存储数据,数据范围就更大

注意:特殊值 NaNundefinedInfinity在参与位运算时,被当作0来处理

如果将位操作符应用到非数值的数据上,那么会先对这个数据调用Number()转换成数值,之后再计算,最终结果是数值。

按位非~

它的作用是返回数值的一补数(就是二进制取反后的结果)

let num1 = 40;
let num2 = ~num1;
console.log(num2); //-41

由此可见,相当于对数值取反再减 1

按位与&

有两个操作数,将两个操作数的二进制结果按位进行比较,每一位上都为 1 时返回 1,否则返回 0

按位或|

有两个操作数,将两个操作数的二进制结果按位进行比较,每一位上至少有一个为1则返回1,都为0返回0

按位异或^

按位异或也叫无进位相加

0与任何数异或都为它本身,同时异或操作也满足交换律和结合律

任何数和它自身异或都为 0

// 异或操作实现二者数值交换
let a = 4;
let b = 8;
a = a ^ b;
b = a ^ b;
a = a ^ b;
console.log(a, b);

leetcode 一道算法题:leetcode.cn/problems/si…

给的数组中只有一个数出现奇数次,找出这个出现奇数次的数

/**
 * @param {number[]} nums
 * @return {number}
 */
var singleNumber = function (nums) {
  let number;
  for (const i of nums) {
    number ^= i;
  }
  return number;
};
console.log(singleNumber([1, 2, 3, 3, 2]));

变式:再数组中有两个数出现奇数次,其他的数都出现偶数次,找出这两个出现奇数次的数

这个题目我也是反复观看了两边,然后自己捋了一遍才弄清楚

我的理解是这样的:

/**
 * @param {number[]} nums
 * @return {number}
 */
var singleNumber = function (nums) {
  // 假设两个奇数是a和b
  let twoNums = 0; //这里记录a^b
  for (const i of nums) {
    twoNums ^= i;
  }
  // twoNums记录a^b,a!=b那么就说明twoNums一定不等于0,所以一定有一位是1
  let rightOne = twoNums & (~twoNums + 1); //找到这个1
  // 一个不为0的数和他的补码取&得到的是这个数的最右边的1
  // 这个rightOne就类似于00000000100
  // 随后我们遍历数组和rightOne取与,a、b中一定有一个值在这个位上是1和它取与就为1
  let aNumber = 0;
  for (const j of nums) {
    if ((rightOne & j) == 0) {
      aNumber ^= j;
    }
  }
  let otherNumber = twoNums ^ aNumber;
  return [aNumber, otherNumber];
};
console.log(singleNumber([1, 2, 3, 3, 3, 2]));

左移<<

左移符号右边是移动位数,左边是要操作的数值,它会按照指定位数把数值的所有位往左移动。空位用0补齐

let num1 = 2; //10
let num2 = num1 << 5; //1000000  2**6=64
console.log(num2 == 2 ** 6); //true

值得注意的是,如果说这个数值是负值,那么可能说这个符号位会被移除,但是实际上,左移会保存数值的符号

有符号右移>>

同左移,会保留符号,是左移的逆运算

let num1 = 64; //1000000
let num2 = num1 >> 5; //10  2**1
console.log(num2 == 2 ** 1); //true

无符号右移>>>

它在移动时会对空位补零,不会保留符号

对于正数来说没有区别,对于负数来说就有大影响

let num1 = -64; //1111111···00000
let num2 = num1 >>> 5;
console.log(num2); //0000011111···0   134217726