Swift赋值和算术运算符
2019-10-08 本文已影响0人
Rathen
基本概念
- 一元运算符对一个目标进行操作。一元前缀运算符(如 !b),一元后缀运算符(b!)。
- 二元运算符对两个目标进行操作(比如 a + b )同时因为它们出现在两个目标之间,所以是中 缀。
- 三元运算符操作三个目标。Swift 语言也仅有一个三元运算符,三元条件运算符( a ? b : c )。
Swift 运算符的改进
Swift 在支持 C 中的大多数标准运算符的同时也增加了一些排除常见代码错误的能力:
- 赋值符号( = )不会返回值,以防它被误用于等于符号( == )的意图上。
- 算数符号( + , - , * , / , % 以及其他)可以检测并阻止值溢出,以避免你在操作比储存类型 允许的范围更大或者更小的数字时得到各种奇奇怪怪的结果。
赋值运算符
- 赋值运算符将一个值赋给另外一个值。
- 如果赋值符号右侧是拥有多个值的元组,它的元素将会一次性地拆分成常量或者变量。
- Swift 的赋值符号自身不会返回值。
算术运算符 - 标准运算符
- 标准算术运算符 + - * /
*加法运算符同时也支持 String 的拼接
*Swift 算术运算符默认不允许值溢出
算术运算符 - 余数运算符
- 余数运算符( a % b )可以求出多少个 b 的倍数能够刚好放进 a 中并且返回剩下的值 (就是我们所谓的余数)。
- 当 a 是负数时也使用相同的方法来进行计算。
- 当 b 为负数时它的正负号被忽略掉了。这意味着 a % b 与 a % -b 能够获得相同的答案。
算术运算符 - 一元
- 数字值的正负号可以用前缀 – 来切换,我们称之为一元减号运算符。
- 一元减号运算符( - )直接在要进行操作的值前边放置,不加任何空格。
- 一元加号运算符 ( + )直接返回它操作的值,不会对其进行任何的修改。
溢出运算符
- 在默认情况下,当向一个整数赋超过它容量的值时,Swift 会报错而不是生成一个无效的数,给 我们操作过大或者过小的数的时候提供了额外的安全性。
- 同时提供三个算数溢出运算符来让系统支持整数溢出运算:
溢出加法 ( &+ )
溢出减法 ( &- )
溢出乘法 ( &* )
值溢出
- 数值可以出现向上溢出或向下溢出
- 溢出也会发生在有符号整型数值上
- 对于无符号与有符号整型数值来说,当出现上溢时,它们会从数值所能容纳的最大数变成最小的
数。同样的,当发生下溢时,它们会从所能容纳的最小数变成最大的数。
合并空值运算符
- 合并空值运算符( a ?? b )如果可选项 a 有值则展开,如果没有值,是 nil ,则返 回默认值 b 。
- 表达式 a 必须是一个可选类型。表达式 b 必须与 a 的储存类型相同。
- 实际上是三元运算符作用到 Optional 上的缩写(a != nil ? a! : b)。
- 如果 a 的值是非空的,b 的值将不会被考虑,也就是合并空值运算符是短路的。
区间运算符
闭区间运算符
- 闭区间运算符( a...b )定义了从 a 到 b 的一组范围,并且包含 a 和 b 。a 的值不 能大于 b 。
半开区间运算符
- 半开区间运算符( a..<b )定义了从 a 到 b 但不包括 b 的区间。
- 如同闭区间运算符,a 的值也不能大于 b ,如果 a 与 b 的值相等,那返回的区间将 会是空的。
单侧区间
- 闭区间有另外一种形式来让区间朝一个方向尽可能的远,这种区间叫做单侧区间。
- 半开区间运算符同样可以有单侧形式,只需要写它最终的值。
- 比如说,一个包含数组所有元素的区间,从索引 2 到数组的结束。在这种情况下,你可 以省略区间运算符一侧的值。
- 单侧区间可以在其他上下文中使用,不仅仅是下标。
- 不能遍历省略了第一个值的单侧区间,因为遍历根本不知道该从哪里开始。你可以遍历
省略了最终值的单侧区间。
字符串索引区间
- 字符串范围也可以使用区间运算符
倒序索引
- 通过 reversed() 方法,我们可以将一个正序循环变成逆序循环。
#######Comparable 区间 - 区间运算符可以作用在 Comparable 类型上,返回闭区间和半闭区间。
位运算符
位取反运算符
- 位取反运算符( ~ )是对所有位的数字进行取反操作
位与运算符
- 位与运算符( & )可以对两个数的比特位进行合并。它会返回一个新的数,只有当这两 个数都是 1 的时候才能返回 1 。
位或运算符
- 位或运算符( | )可以对两个比特位进行比较,然后返回一个新的数,只要两个操作位任 意一个为 1 时,那么对应的位数就为 1 。
位异或运算符
- 位异或运算符,或者说“互斥或”( ^ )可以对两个数的比特位进行比较。它返回一个 新的数,当两个操作数的对应位不相同时,该数的对应位就为 1 。
位左移和右移运算符
- 位左移运算符( << )和位右移运算符( >> )可以把所有位数的数字向左或向右移动一 个确定的位数。
- 位左移和右移具有给整数乘以或除以二的效果。将一个数左移一位相当于把这个数翻
倍,将一个数右移一位相当于把这个数减半。
无符号整数的移位操作
- 已经存在的比特位按指定的位数进行左移和右移 任何移* 动超出整型存储边界的位都会被丢弃
- 用 0 来填充向左或向右移动后产生的空白位
有符号整数的移位操作
- 有符号整数使用它的第一位(所谓的符号位)来表示这个整数是正数还是负数。符号位为 0 表示为正 数,1 表示为负数
- 其余的位数(所谓的数值位)存储了实际的值。有符号正整数和无符号数的存储方式是一样的,都是 从 0 开始算起。
- 但是负数的存储方式略有不同。它存储的是 2 的 n 次方减去它的绝对值,这里的 n 为数值位的位 数。
补码表示的优点
- 首先,如果想给 -4 加个 -1 ,只需要将这两个数的全部八个比特位相加(包括符号 位),并且将计算结果中超出的部分丢弃。
- 其次,使用二进制补码可以使负数的位左移和右移操作得到跟正数同样的效果,即每向 左移一位就将自身的数值乘以 2 ,每向右移一位就将自身的数值除以 2 。要达到此目 的,对有符号整数的右移有一个额外的规则:当对正整数进行位右移操作时,遵循与无 符号整数相同的规则,但是对于移位产生的空白位使用符号位进行填充,而不是 0 。
位运算符经典算法
两个数字交换
不借助临时变量,交换两个变量的值。
var a = 10
var b = 9
a = a ^ b
b = a ^ b
a = a ^ b
print("a = \(a), b = \(b)")
求无符号整数二进制中 1 的个数
思路:看一个八位整数 10 100 001 ,先判断最后一位是否为 1 ,而“与”操作可以达 到目的。可以把这个八位的数字与 00000001 进行“与”操作。如果结果为 1 ,则表示 当前八位数的最后一位为 1 ,否则为 0 。怎么判断第二位呢?向右移位,再延续前面的 判断即可。
func countOfones(num: UInt) -> UInt {
var count: UInt = 0
var temp = num
while temp != 0 {
count += temp & 1
temp = temp >> 1
}
return count
}
如果整数的二进制中有较多的 0 ,那么我们每一次右移一位做判断会很浪费,怎么改进 前面的算法呢?有没有办法让算法的复杂度只与“1”的个数有关?
运算符重载
类和结构体可以为现有的运算符提供自定义的实现,称为运算符重载。
extension Vector2D {
static func + (left: Vector2D, right: Vector2D) -> Vector2D {
return Vector2D(x: left.x + right.x, y: left.y + right.y)
}
}
extension Vector2D {
static prefix func - (vector: Vector2D) -> Vector2D {
return Vector2D(x: -vector.x, y: -vector.y)
}
}
extension Vector2D {
static func += (left: inout Vector2D, right: Vector2D) {
left = Vector2D(x: left.x + right.x, y: left.y + right.y)
}
}
一元运算符重载
- 类与结构体也能提供标准一元运算符的实现。
- 要实现前缀或者后缀运算符,需要在声明运算符函数的时候在 func 关键字之前指定 prefix 或者 postfix 限定符。
组合赋值运算符重载
- 组合赋值运算符将赋值运算符( = )与其它运算符进行结合。
- 在实现的时候,需要把运算符的左参数设置成 inout 类型,因为这个参数的值会在运算 符函数内直接被修改。
等价运算符重载
- 自定义类和结构体不接收等价运算符的默认实现,也就是所谓的“等于”运算符( == ) 和“不等于”运算符( != )。
等价运算符重载 - 要使用等价运算符来检查你自己类型的等价,需要和其他中缀运算符一样提供一个“等 于”运算符,并且遵循标准库的 Equatable 协议
等价运算符重载
只拥有遵循 Equatable 协议存储属性的结构体
Swift 为以下自定义类型提供等价运算符合成实现:
只拥有遵循 Equatable 协议关联类型的枚举 没有关联类型的枚举