运算符和表达式
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.

位异或运算符
- 位异或运算符,或者说"互斥或"(^)可以对两个数的比特位进行比较。他返回一个新的数,当两个操作数对应位不相同时,该数的对应位就为1.

位左移和右移运算符
- 位左移运算符(<<)和位右移运算符(>>)可以把所有位数的数字向左或向右移动一个确定的位数
- 位左移和右移具有给整数乘以或除以二的效果。将一个数左移一位相当于把这个数翻倍,将一个数右移一位相当于把这个数减半。
无符号整数的移位操作
- 已经存在的比特位按指定的位数进行左移和右移
- 任何移动超出整型存储边界的位都会被丢弃
- 用0来填充向左或向右移动后产生的空白位

有符号整数的移位操作
- 有符号整数使用它的第一位(所谓的符号位)来表示这个整数是正数还是负数。符号为0表示为正数,1表示为负数。
- 其余的位数(所谓的数值位)存储了实际的值。有符号正整数和无符号数的存储方式是一样的,都是从0开始算起。
- 但是负数的存储方式略有不同。它存储的是2的n次方减去它的绝对值,这里的n为数值位的位数。

补码表示的优点
- 首先,如果给-4加个-1,只需要将这两个数的全部八个比特位相加(包括符号位),并且将计算结果中超出的部分丢弃。

- 其次,使用二进制补码可以使负数的位左移和右移操作得到跟正数同样的效果,即每向左移一位就将自身的数值乘以2,每向右移一位将自身的数值除以2.要达到此目的,对有符号整数的右移有一个额外的规则:当对整数进行位右移操作时,遵循与无符号整数相同的规则,但是对于移位产生的空白位使用符号位进行填充,而不是0.

两个数字交换
var a = 10
var b = 8
a = a ^ b
b = a ^ b
a = a ^ b
print(a)
print(b)
求无符号整数二进制中1的个数
- 给定一个无符号整数(UInt)变量,求其二进制中"1"的个数,要求算法执行效率尽可能高
- 思路:看一个八位整数10100001,先判断最后一位是否为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"的个数有关?
- 思路:为了简化这个问题,我们考虑只有高位有1的情况。例如:11000000,如何跳过前面低位的6个0,而直接判断第七位的1?可以设计11000000和10111111 (也就是1100000 - 1)做"与"操作,消去最低位的1.如果得到的结果为0,表示已经找到或者消去里面最后一个1.如果不为0,表示消去了最低的1,但是二进制中还有其他的1,我们的计数器需要加1,然后继续以上操作。
计数器 count = 0
步骤一:整数不为0,说明二进制里肯定有1,count=1
11 000 000 & 10 111 111 = 10 000 000 (消去第七位的1)。
步骤二:结果不为0,说明二进制里还有1,count = 2
10 000 000 & 01 111 111 = 0(消去第八位的1)。
步骤三:结果为0,终止,返回count为2。
func countOfOnes2(num: UInt) -> UInt {
var count: UInt = 0
var temp = num
while temp != 0 {
count += 1
temp = temp & (temp - 1)
}
return count
}
print(countOfOnes2(num: 0x100010))
引申:如何判断一个整数为2的整数次幂
- 给定一个无符号整型(UInt)变量,判断是否为2的整数次幂。
- 思路:一个整数如果是2的整数次方,那么他的二进制表示中有且只有一位是1,而其他所有位都是0.根据前面的分析,把这个整数减去1后再和它自己做与运算,这个整数中唯一的1就变成0了,也就是得到的结果为0.
func isPowerOfTwo(num: UInt) -> Bool {
return (num & (num - 1)) == 0
}
print(isPowerOfTwo(num: 512))
缺失的数字
- 很多成对出现的正整数保存在磁盘文件中,注意成对的数字不一定是相邻的,如2,3,4,3,4,2.....,由于意外有一个数字消失了,如何尽快找到是哪个数字消失了?
- 思路:考虑"异或"操作的定义,当两个操作数的对应位不相同时,该数的对应位就是1.也就是说如果是相等的两个数"异或",得到的结果为0,而0与任何数字"异或",得到的是那个数字本身。所以我们考虑将所有的数字做"异或"操作,因为只有一个数字消失,那么其他两两出现的数字"异或"后为0,0与仅有的一个数字做"异或",就得到了消失的数字。
func findLostNum(nums:[UInt]) -> UInt {
var lostNum:UInt = 0
for num in nums {
lostNum = lostNum ^ num
}
return lostNum
}
print(findLostNum(nums: [1,2,3,4,3,2,1]))
缺失的数字2
- 如果有两个数字意外丢失了(丢失的不是相等的数字),该如何找到丢失的两个数字?
- 思路:设题目中这两个出现1次的数字分别为A和B,如果能将A,B分开到二哥数组中,那显然符合"异或"解法的关键点了。因此这个题目的关键点就是将A,B分开到二个数组中。由于A,B肯定是不相等的,因此在二进制上必定有一位是不同的。根据这一位是0还是1可以将A和B分开到A组和B组。而这个数组中其他数字要么属于A组,要么属于B组。再对A组和B组分别执行"异或"解法就可以得到A,B了。而要判断A,B在哪一位上不相同,只要根据"A异或B"的结果就知道了,这个结果在二进制上为1的位就说明在这一位上是不相同的。
func findTwoLostNums(nums: [UInt]) -> (UInt,UInt) {
var lostNum1: UInt = 0
var lostNum2:UInt = 0
var temp: UInt = 0
for num in nums {
temp = temp ^ num
}
var flag: UInt = 1;
while((flag & temp) == 0){
flag = flag << 1
}
for num in nums {
if (num & flag) == 0 {
lostNum1 = lostNum1 ^ num
}else {
lostNum2 = lostNum2 ^ num
}
}
return (lostNum1,lostNum2)
}
思考题-缺失的数字3
- 数组中,只有一个数出现一次,剩下都出现三次,找出出现一次的数字
运算符优先级和结合性
- 运算符的优先级使得一些运算符优先于其他运算符,高优先级的运算符会先被计算
- 结合性定义了具有相同优先级的运算符是如何结合(或关联)的一一是与左边结合为一组,还是与右边结合为一组。可以这样理解:"他们是与左边的表达式结合的"或者"他们是与右边的表达式结合的"。
运算符优先级 - 显式括号
- Swift语言中逻辑运算符 && 和 || 是左相关的,这意味着多个逻辑运算符组合的表达式会首先计算最左边的子表达式。
运算符重载
- 类和结构体可以为现有的运算符提供自定义的实现,称为运算符重载。
struct Vector2D {
var x=0.0, y=0.0
}
extension Vector2D {
static func + (left: Vector2D,right: Vector2D) -> Vector2D {
return Vector2D(x: left.x + right.x,y: left.y + right.y)
}
}
let vector = Vector2D(x: 3.0,y: 1.0)
let another = Vector2D(x: 2.0,y: 4.0)
let combinedVector = vector + anotherVector

一元运算符重载
- 类与结构体也能提供标准一元运算符的实现
- 要实现前缀或者后缀运算符,需要在声明运算符函数的时候在func关键字之前指定prefix或者postfix限定符。
extension Vector2D {
static prefix func - (vector: Vector2D) -> Vector2D {
return Vector2D(x: -vector.x,y: -vector.y)
}
}
let positive = Vector2D(x:3.0,y:4.0)
let negative = -positive
let alsoPositive = -negative
组合赋值运算符重载
- 组合赋值运算符将赋值运算符(=)与其他运算符进行结合
- 在实现的时候,需要把运算符的左参数设置成inout类型,因为这个参数的值会在运算符函数内直接被修改
extension Vector2D {
static func += (left: inout Vector2D,right:Vector2D){
left=left+right
}
}
var original=Vector2D(x:1.0,y:2.0)
let vectorToAdd=Vector2D(x:3.0,y:4.0)
original += vectorToAdd
等价运算符重载
- 自定义类和结构体不接收等价运算符的默认实现,也就是所谓的"等于"运算符(==)和"不等于"运算符(!=)
- 要使用等价运算符来检查你自己类型的等价,需要和其他中缀运算符一样提供一个"等于"运算符重载,并且遵循标准库的Equatable协议
extension Vector2D: Equatable {
static func == (left: Vector2D,right:Vector2D) -> Bool {
return (left.x == right.x) && (left.y == right.y)
}
}
- Swift为以下自定义类型提供等价运算符合成实现:
- 只拥有遵循Equatable协议存储属性的结构体
- 只拥有遵循Equatable协议关联类型的枚举
- 没有关联类型的枚举
struct Vector3D: Equatable {
var x=0.0,y=0.0,z=0.0
}
let twoThreeFour=Vector3D(x:2.0,y:3.0,z:4.0)
let anotherTwoThreeFour=Vector3D(x:2.0,y:3.0,z:4.0)
if twoThreeFour == anotherTwoThreeFour {
print("These two vectors are also equivalent")
}
自定义运算符
- 除了实现标准运算符,在Swift当中还可以声明和实现自定义运算符(custom operators)
- 新的运算符要在全局作用域内,使用operator关键字进行声明,同时还要指定prefix、infix或者postfix限定符。
prefix operator +++
extension Vector2D {
static prefix func +++ (vector: inout Vector2D) -> Vector2D {
vector += vector
return vector
}
}
var toBeDoubled = Vector2D(x:1.0,y:4.0)
let afterDoubling = +++toBeDoubled
自定义中缀运算符的优先级和结合性
- 自定义的中缀(infix)运算符也可以指定优先级和结合性
- 每一个自定义的中缀运算符都属于一个优先级组
- 优先级组指定了自定义中缀运算符和其他中缀运算符的关系
precedencegroup MyPrecedence {
associativity: left
lowerThan: AdditionPrecedence
}