赋值和算术运算符
基本概念
一元运算符,对一个目标进行操作。一元前缀运算符:
!b
,一元后缀运算符:b!
。二元运算符,对两个目标进行操作。如
a + b
,同时因为它们出现在两个目标之间,所以是中缀运算符。三元运算符,对三个目标进行操作。Swift 语言仅有一个三元运算符,即三元条件运算符
a ? b : c
。
Swift 支持 C 中大多数的标准运算符,同时也增加了一些能力,用来排除常见代码错误:
- 赋值符号(
=
)不会返回值,以防止它和等于符号(==
)产生误用。 - 算数符号(
+
,-
,*
,/
,%
等)可以检测并阻止值溢出,如存储类型放入大于允许范围的数字。
赋值运算符
=
:赋值运算符,将一个值赋值给另外一个值。如果赋值符号右侧是拥有多个值的元组,则元组的元素将会一次性拆分成常量和变量。
赋值运算符不会返回值
在 OC 中,赋值运算符在赋值成功后会返回 true
:
而 Swift 的赋值符号自身不会返回值:
算术运算符
标准运算符
+
、-
、*
、/
是标准算术运算符。
加法运算符也支持 String 的拼接:
|
|
- Swift 算术运算符默认不允许值溢出
余数运算符
|
|
余数:指整数除法中被除数未被除尽部分,且余数的取值范围为0到除数之间(不包括除数)的整数。
除数为负数时,也是用同样的方法进行计算:
|
|
被除数为负数时,负号会被忽略:
|
|
一元
- 一元减号运算符(
-
):直接放置在操作的值前边。数字值的正负号可以用前缀-
来切换。 - 一元加号运算符(
+
):直接返回操作的值,不会对其进行任何的修改。
溢出运算符
向一个整数赋值超过它容量的值时,Swift 会报错,而不是生成一个无效的数。
&+
溢出加法&-
溢出减法&*
溢出乘法
值溢出
无符号整型数值和有符号整型数值都可以向上溢出或向下溢出。
当发生上溢时,它们会从数值所能容纳的最大数变成最小数。
当发生下溢时,它们会从数值所能容纳的最小数变成最大数。
无符号整型数值,向上溢出:
无符号整型数值,向下溢出:
有符号整型数值,向下溢出:
Swift 算术运算符默认不允许值溢出:
可以使用溢出加法&+
:
合并空值运算符
如果 a 有值则返回 a,不会再判断 b;如果 a 没有值(nil
),则返回默认值 b:
|
|
其中,a 必须是一个可选类型,b 必须与 a 的储存类型相同。
合并空值运算符就是三元运算符,是三元运算符作用到 Optional 上的缩写版:
|
|
应用效果对比
1、使用强制解包,有崩溃风险,如果 num1 或 num2 为 nil 会崩:
|
|
2、使用 if 判断,代码量比较多:
|
|
3、使用合并控制运算符 ??
|
|
区间运算符
闭区间运算符
a...b
:闭区间运算符,定义了从 a 到 b 的一组范围,包含 a 和 b。a 的值不能大于 b。
|
|
半开区间运算符
a..<b
:半开区间运算符,定义了从 a 到 b 的一组范围,包含 a 不包含 b。a 不能大于 b,如果 a 与 b 相等,则返回空。
|
|
单侧区间
单侧区间是闭区间的另外一种形式,让区间朝一个方向尽可能的远。
a...
:区间左边大于等于 a。...b
:区间右边小于等于 b。
半开区间运算符也有单侧形式:
..<b
:区间右边小于b。
举例1:从索引2到数组最后一个元素
|
|
举例2:从数组第一个元素开始到索引2:
|
|
举例3:从数组第一个元素开始到索引2前一个元素:
|
|
举例4:单侧区间不仅用于下标,还可以在上下文中使用
|
|
字符串索引区间
区间运算符在字符串中的应用:删除指定范围的字符
|
|
倒序索引
|
|
Comparable 区间
区间运算符作用在 Comparable 类型上,返回闭区间和半闭区间。
|
|
位运算符
位取反运算符
~
位取反运算符,是对所有位的数字进行取反操作。
位与运算符
&
位与运算符,可以对两个数的比特位进行比较,返回一个新的数,当两个比特位都是 1 时返回 1。
位或运算符
|
位或运算符,可以对两个数的比特位进行比较,返回一个新的数,当两个比特位任意一个为 1 时返回 1。
位异或运算符
^
为异或运算符,或者叫“互斥或”,可以对两个数的比特位进行比较,返回一个新的数,当两个比特位不相同 时返回 1。
位左移和右移运算符
<<
位左移运算符,可以把所有位数的数字向左移动一个确定的位数。>>
位右移运算符,可以把所有位数的数字向右移动一个确定的位数。
位左移和位右移具有将整数乘以或除以二的效果。将一个数左移一位相当于把这个数翻倍,将一个数右移一位相当于把这个数减半。
无符号整数的移位操作
实现过程:
- 已经存在的比特位按指定的位数进行左移和右移;
- 任何移动超出整型存储边界的位都会被丢弃;
- 用 0 来填充向左或向右移动后产生的空白位。
有符号整数的移位操作
符号位:有符号整数用第一位表示正数还是负数。符号位为 0 表示正数,1 表示负数。
数值位:除第一位外其余的位,存储了实际的值。
有符号正整数和无符号数的存储方式是一样的,都是从 0 开始算起。
但是负数的存储方式略有不同,它存储的是 2 的 n 次方减去它的绝对值,这里的 n 为数值位的位数。
-4 的存储位 128 - 4 = 124。
补码表示的优点
对于算式 -4 + -1
,先将两个数的全部八个比特位相加(包括符号位),再将计算结果中超出的部分丢弃。
-4 的存储位 2^8 - 4
,-1 的存储位 2^8 - 1
,-5 的存储位 2^8 - 5
。-4 + -1 = 2^8 - 4 + 2^8 - 1
,将计算结果中超出的部分丢弃(去掉一个2^8)。
即 -4 + -1 = 2^8 - 4 - 1 = 2^8 - 5
,等于 -5。
使用二进制补码可以使负数的位左移和右移操作,得到跟正数同样的效果,即每向左移一位就将自身的数值乘以 2,每向右移一位就将自身的数值除以 2。
要达到此目的,对有符号整数的右移有一个额外的规则:当对正整数进行位右移操作时,遵循与无符号整数相同的规则,但是对于移位产生的空白位使用符号位进行填充,而不是 0。
位运算符经典算法
两个数字交换
|
|
求无符号整数二进制中1的个数
- 给定一个无符号整型(UInt)变量,求其二进制表示中 “1” 的个数,要求算法的执行效率尽可能的高。
思路:看一个八位整数 10 100 001,先判断最后一位是否为 1,而“与”操作可以达到目的。可以把这个八位的数字与 00 000 001 进行“与”操作。如果结果为1,则表示当前八位数的最后一位为1,否则为0。怎么判断第二位呢?向右移位,在延续前面的判断即可。
|
|
思考:如果整数的二进制中有较多的 0,那么我们每一次右移一位做判断会很浪费,怎么改进呢?有没有办法让算法的复杂度只与“1”的个数有关?
思路:为了简化这个问题,先考虑只有高位有“1”的情况。例如:11 000 000,如何跳过前面低位的 6 个 0,而直接判断第七位的 1?我们可以设计 11 000 000 和 10 111 111(也就是 11 000 000 - 1)做“与”操作,消去最低位的 1。如果得到的结果为 0,说明已经找到(消去)里面最后一个 1。如果不为 0,那么说明消去了最低位的 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。
|
|
如何判断一个整数为2的整数次幂
- 给定一个无符号整型(UInt)变量,判断是否为 2 的整数次幂。
思路:一个整数如果是 2 的整数次方,那么它的二进制表示中有且只有一位是 1,其它所有位都是 0。根据前面的分析,把这个整数减去 1 后再和它自己做“与”运算,这个整数中唯一的 1 就变成 0 了,也就是得到的结尾为 0。
|
|
缺失的数字
- 很多成对出现的正整数保存在磁盘文件中,注意成对的数字不一定是相邻的,如 2,3,4,3,4,2……,由于意外有一个数字消失了,如何尽快找到是哪个数字消失了?
思路:考虑“异或”操作的定义,当两个操作数的对应位不同时,该数的对应位就为 1。也就是说如果是 相等的两个数“异或”,得到的结果为 0,而 0 与任何数字“异或”,得到的是那个数字本身。所以可以将所的数字做“异或”操作,因为只有一个数字消失,那么其它两两出现的数字“异或”后为 0,0与仅有的一个数字做“异或”,就得到了消失的数字是哪个。
|
|
缺失的数字2
- 很多成对出现的正整数保存在磁盘文件中,注意成对的数字不一定是相邻的,如 2,3,4,3,4,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 的位都说明 A,B 在这一位上是不相同的。
|
|
缺失的数字3
- 数组中,只有一个数出现一次,剩下的都出现三次,找出出现一次的数字。
思路:考虑到三次异或后还是数字本身,所以不能像例2那样使用异或。根据位运算的思想,出现三次的数字每个比特位也出现了三次,如果将出现的次数相加的话,每一个比特位都可以被3整除。只出现一次的数字的每一个比特位出现的次数都是1,不能被3整除。
|
|
运算符的优先升级和结核性
运算符的优先级使得一些运算符优先于其他运算符,高优先级的运算符会先被计算。
结合性定义了具有相同优先级的运算符是如何结合(或关联)的——与左边结合为一组,还是与右边结合为一组。
|
|
2 + 3 % 4 * 5
等价于 2 + ((3 % 4) * 5)
。
运算符优先级 - 显示括号
Swift 语言中逻辑运算符 &&
和 ||
是做相关的,意味着多个逻辑运算符组合的表达式会首先计算最左边的子表达式。
|
|
等价于
|
|
运算符重载
类和结构体可以为现有的运算符提供自定义的实现,称为运算符重载。
|
|
一元运算符重载
类与结构体也能提供标准一元运算符的实现。要实现前缀或者后缀运算符,需要在声明运算符函数的时候在 func 关键字之前指定 prefix 或者 postfix 限定符。
|
|
组合赋值运算符重载
组合赋值运算符将赋值运算符(=
)与其它运算符进行结合。
|
|
因为 left
的值会在运算符函数内直接被修改,所以需要把 left
设置成 inout
类型。
等价运算符重载
自定义类和结构体不接收等价运算符的默认实现,也就是所谓的“等于”运算符(==
)和“不等于”运算符(!=
)。要使用等价运算符,需要提供一个“等于”运算符(类似中缀运算符+
),并且遵循标准库 Equaltable 协议。
|
|
swift 为一下自定义类型提供等价运算符合成实现:
- 只拥有遵循 Equaltable 协议存储属性的结构体
- 只拥有遵循 Equaltable 协议关联类型的枚举
- 没有关联类型的枚举
自定义运算符
除了实现标准运算符,在 Swift 中还可以声明和实现自定义运算符。自定义的运算符如果想在全局作用域内,可以使用 operator
关键字进行声明,同时还要指定 prefix
、infix
或者 postfix
限定符。
|
|
自定义中缀运算符的优先级和结合性
自定义的中缀(infix
)运算符也可以指定优先级和结合性,每一个自定义的中缀运算符都属于一个优先级组,优先级组制定了自定义中缀运算符和其他中缀运算符的关系。
AdditionPrecedence
加法运算优先级组MultiplicationPrecedence
乘法运算优先级组
|
|
自定义中缀运算符 +-
|
|
自定义运算符 +-
属于加法运算符组,自定义运算符 *^ 属于乘法运算符组。因为乘法运算的优先级大于加法运算,所以运算符组 +-
的优先级小于运算符组 \*^
,即 \*^
先于 +-
运算:
|
|
自定义运算符组 MyPrecedence
,设置优先级低于加法运算组。即 *^
和 +-
的执行顺序按从左至右的顺序:
|
|