原文: https://www.programiz.com/swift-programming/bitwise-operators

在本教程中,您将学习 Swift 中的不同按位运算。 这些用于表达式中的位级别计算。

位用于表示二进制数字。 二进制数可以有两个可能的值 0 或 1。作为初学者级程序员,您不必在位级别进行操作。

使用原始数据类型,例如:整数,浮点数,布尔值,字符串等就足够了。 在处理低级编程时,可能需要在位级工作。

除了基本运算符之外,Swift 还提供了丰富的运算符集来操作位。 这些运算符与逻辑运算符相似,不同之处在于它们使用数据(位)的二进制表示形式工作。

按位运算符是用于更改操作数的各个位的运算符。 操作数是完成操作的变量或常量。

迅速列出所有可用的按位运算符:


1.按位非运算符

它由代字号~表示,可以应用于单个操作数。 这会将所有位反转。 即将 1 更改为 0,将 0 更改为 1。

如果x是保存二进制值(即 0 或 1)的变量/常数,则x变量的按位非运算可以表示在下表中:

x ~x
0 1
1 0

示例 1:无符号整数的按位非运算符

  1. let initalNumber:UInt8 = 1
  2. let invertedNumber = ~initalNumber
  3. print(invertedNumber)

当您运行上述程序时,输出将是:

  1. 254

在上面的程序中,语句let initalNumber:UInt8 = 1的类型为Unsigned int,大小为 8 位。 因此,十进制的 1 可以表示为二进制的00000001

按位非运算符更改变量或常量的所有位,位 0 更改为 1,而 1 更改为 0。因此invertedNumber包含位11111110。 将其转换为十进制后,将其表示为 254。因此,语句print(invertedNumber)在屏幕中输出 254。

您还可以直接在位中执行按位运算符,如下所示:


示例 2:以位为单位的按位非运算符

  1. let initialBits: UInt8 = 0b11111111
  2. let invertedBits = ~initialBits
  3. print(invertedBits)

运行该程序时,输出为:

  1. 0

initialBits包含对应于十进制 255 的二进制值11111111。 为了用二进制表示数字,我们在字面值中以0b作为前缀。 如果没有0b作为前缀,它将把它当作普通整数对待,并且会出现溢出错误(UInt8只能存储 0 到 255 之间的数字)。

由于我们使用了按位非运算符,因此将所有 1 都更改为 0。因此,常量initialBits包含00000000,它等于UInt8中的 0。


示例 3:带符号整数的按位非运算符

  1. let initalNumber:Int = 1
  2. let invertedNumber = ~initalNumber
  3. print(invertedNumber)

运行该程序时,输出为:

  1. -2

在上面的程序中,十进制的 1 可以表示为00000001的二进制。 按位非运算符更改变量或常量的所有位,位 0 更改为 1,1 更改为 0。因此,invertedNumber包含位11111110。 这应该在屏幕中输出 254。 而是返回-2。 奇怪吧? 让我们在下面探讨这是如何发生的。

let initalNumber:Int = 1是一个可以容纳正整数和负整数的有符号整数。 这就是为什么当我们不对有符号整数应用运算符时,返回的二进制也可能表示负数的原因。

编译器如何将 -2 解释为二进制形式的11111110

编译器使用二进制补码表示整数。 要获得整数的二进制补码负号,您应该首先以二进制形式写出数字,然后反转数字,然后在结果中加一个。

找出 -2 的二进制补码的步骤:

  1. 以二进制形式写 2:00000010
  2. 反转数字。 0 变成 1,而 1 变成 0:11111101
  3. 加 1:11111110

这就是编译器将二进制数1111110解释为十进制-2的方式。 但是,编译器有些许变化,我们没有注意到。 它还将invertedNumber的类型推断为Int8类型。


为了理解这一点,让我们看下面的例子:

  1. print(Int8(bitPattern: 0b11111110))
  2. print(0b11111110)

运行该程序时,输出为:

  1. -2
  2. 254

在上面的示例中,编译器仅对带符号的 8 位整数将二进制数处理为十进制的-2。 因此,语句print(Int8(bitPattern: 0b11111110))在屏幕上输出 -2。

但是对于大小为 32/64 位并且可以容纳较大值的普通整数类型,它将其解释为254。 因此,语句print(0b11111110)在屏幕上输出 254


2.按位与运算符

它由&表示,可以应用于两个操作数。 与运算符比较两个位,如果两个位均为 1,则返回 1,否则返回 0。

如果xy是变量/常数,且保持二进制值(即 0 或 1),则xy的按位与运算如下表所示:

x y x & y
0 0 0
0 1 0
1 1 1
1 0 0

例 5:按位与运算

  1. let xBits = 0b10000011
  2. let yBits = 0b11111111
  3. let result = xBits & yBits
  4. print("Binary:",String(result, radix: 2))
  5. print(result)

运行该程序时,输出为:

  1. Binary: 10000011
  2. 131

在上面的程序中,语句let result = xBits & yBits组合了两个操作数xBitsyBits的位。 如果两个位均为 1,则返回 1,否则返回 0。

String(value , radix: )初始化器用于表示不同编号系统中的编号。 如果提供基数 2。它将数字转换为二进制数系统。 同样,十六进制可以使用 16,十进制可以使用 10。

语句print("Binary:",String(result, radix: 2))在屏幕上输出Binary: 1000001110000011等于十进制的 131,语句print(result)在控制台中输出 131。


3.按位或运算符

它表示为|,可以应用于两个操作数。 如果按位或运算符的一个或多个输入为 1,则将两个位进行比较,并生成结果 1;否则为 0。

如果xy是变量/常数,且保持二进制值,即 0 或 1。则xy的按位或运算如下表所示:

x y `x y`
0 0 0
0 1 1
1 1 1
1 0 1

例 6:按位或运算

  1. let xBits = 0b10000011
  2. let yBits = 0b11111111
  3. let result = xBits | yBits
  4. print("Binary:", String(result, radix: 2))
  5. print(result)

运行该程序时,输出为:

  1. Binary: 11111111
  2. 255

在上述程序中,语句let result = xBits | yBits组合了两个常量xBitsyBits的位。 如果任何位为 1,则返回 1,否则返回 0。

语句print("Binary:",String(result, radix: 2))在屏幕上输出Binary: 11111111。 由于11111111等于十进制的255,因此语句print(result)在屏幕上输出 255


4.按位异或运算符

它表示为^,可以应用于两个操作数。 如果异或运算符的输入之一恰好是 1,则异或运算符将比较两个位并生成结果 1,否则返回 0。

如果xy是变量/常数,且保持二进制值,即 0 或 1。则xy的按位异或运算如下表所示:

异或

x y x ^ y
0 0 0
0 1 1
1 1 0
1 0 1

示例 7:按位异或操作

  1. let xBits = 0b10000011
  2. let yBits = 0b11111111
  3. let result = xBits ^ yBits
  4. print("Binary:", String(result, radix: 2))
  5. print(result)

运行该程序时,输出为:

  1. Binary: 1111100
  2. 124

在上述程序中,语句let result = xBits ^ yBits组合了两个常量xBitsyBits的位。 如果正好一位为 1,则返回 1,否则返回 0。

语句print("Binary:",String(result, radix: 2))在屏幕上输出Binary:1111100(相当于 01111100)。 由于1111100等于十进制的124,因此语句print(result)在屏幕上输出 124


5.按位移位运算符

该运算符用于将数字中的所有位向左或向右移动一定数量的位置,并且可以应用于单个操作数。 它表示为<<>>

移位运算符有两种:


按位左移运算符

  • 表示为<<
  • 这会导致这些位向左移动,由<<后面的数字指定。
  • 移位操作已腾空的位置用零填充。
  • 将整数左移一位将其值加倍

例 8:按位左移运算符

  1. let someBits:UInt8 = 0b11000100
  2. print(someBits << 1)

运行该程序时,输出为:

  1. 136

在上面的程序中,我们使用了左移运算符。 使用<< 1 表示将位向左移动 1。 这些数字向左移动一个位置,右侧的最后一个数字填充零。

您还可以看到丢失的数字从左侧“移出”。 它不会从右侧再次缠绕。 将其向左移动一位将二进制文件中的 1 删除,向右添加 0 以填充移位后的值,其余的其他位则向左移动 1。

这将返回10001000,它等效于UInt8中的136。 因此,print(someBits << 1)语句在屏幕上输出 136


按位右移运算符

  • 表示为>>
  • 这会导致这些位向右移动数字,后跟>>
  • 对于无符号数字,移位操作已腾空的位位置为零。
  • 对于带符号的数字(也可以为负的数字),符号位用于填充腾出的位位置。 换句话说,如果数字为正数,则使用 0;如果数字为负数,则使用 1。
  • 向右移动一个位置可将其值减半。

示例 9:无符号整数的按位右移运算符

  1. let someBits: UInt8 = 4
  2. print(someBits >> 1)

运行该程序时,输出为:

  1. 2

在上面的程序中,我们对无符号整数使用了右移运算符。 使用>> 1表示将位右移 1。 移位操作已腾空的位位置始终在无符号整数上填充零。

由于 4 以二进制形式表示为00000100。 向右移动一位,返回00000010,它等效于UInt8中的2。 因此,print(someBits >> 1)语句在屏幕上输出 2。


示例 10:有符号整数的按位右移运算符

  1. let someBits:Int = -4
  2. print(someBits >> 1)

运行该程序时,输出为:

  1. -2

在上面的程序中,我们对无符号整数使用了右移运算符。 与正数不同,对负数使用>>,使用 1 填充空位,而不是 0。

由于-4用二进制表示为11111100。 将其向右移动一位并将 1 放置在空闲位置,将返回11111110,与Int8类型的-2等效。 因此,print(someBits >> 1)语句在屏幕上输出 -2。