十五、赋值和算术运算符
运算符基本概念
- 一元运算符对一个目标进行操作。一元前缀运算符(如!b),一元后缀运算符(b!)
- 二元运算符对两个目标进行操作(比如a+b)同时因为它们出现在两个目标之间,所以是中缀
- 三元运算符操作三个目标。Swift语言也仅有一个三元运算符,三元条件运算符( a ? b : c)
Swift运算符的改进
- Swift在支持C中的大多数标准运算符的同时也增加了一些排除常见代码错误的能力
- 赋值符号(=)不会返回值,以防它被误用于等于符号(==)的意图上
- 算术符号(+,-,*,/,%以及其他)可以检测并阻止值溢出,以避免你在操作比存储类型允许的范围更大或者更小的数字时得到奇奇怪怪的结果
赋值运算符
- 赋值运算符将一个值赋给另外一个值
- 如果赋值符号右侧是拥有多个值的元组,它的元素将会一次性地拆分成常量或者变量
- Swift的赋值符号自身不会返回值
OC中常常有
if( self = [super init] )
,如果self初始化成功,则进入方法体内部;而Swift中=符号不会返回值
算术运算符 - 标准运算符
- 标准运算符+-*/
- 加法运算符同时也支持String的拼接
- Swift算术运算符默认不允许值溢出
算术运算符 - 余数运算符
- 余数运算符(a % b)可以求出多个b的倍数能够刚好放进a中并且返回剩下的值(就是我们所谓的余数)
- 当a是负数时也使用相同的方法来进行计算
- 当b为负数时它的正负号被忽略掉了。这意味着a%b与a%-b能够获得相同的答案,如 9 % 2 为 1,,9 % (-2)结果也为1
算术运算符 - 一元
- 数字值的正负号可以用前缀 - 来切换,我们称之为一元减号运算符
- 一元减号运算符(-)直接在要进行操作的值前边设置,不加任何空格
- 一元加好运算符(+)直接返回它操作的值,不会对其进行任何的修改
十六、处理算术结果溢出
溢出运算符
- 在默认情况下,当将一个整数赋给超过它容量的值时,Swift会报错而不是生成一个无效的数,给我们操作过大或者过小的数的时候提供了额外的安全性
- 同时提供三个算术溢出运算符来让系统支持整数溢出运算
- 溢出加法(&+)
- 溢出减法(&-)
- 溢出乘法(&*)
值溢出
- 数值可以出现向上溢出或向下溢出
let a: UInt8 = UInt8.max
print(a) // 255
//let a1 = a + 1 // 直接用+会报错,提示超出范围
let a1 = a &+ 1
print(a1) // 结果为0
let b = UInt8.min
print(b) // 0
//let b1 = b - 1// 直接用-会报错,提示超出范围
let b1 = b &- 1
print(b1) // 结果为255
- 溢出也会发生在有符号整形数值上
- 对于无符号与有符号整形数值来说,当出现上溢时,它们会从数值所能容纳的最大数变成最小的数。同样的,当发生下溢时,它们会从所能容纳的最小数变成最大的数。
let a: Int8 = Int8.max
print(a) // 127
//let a1 = a + 1 // 直接用+会报错,提示超出范围
let a1 = a &+ 1
print(a1) // 结果为-128
let b = Int8.min
print(b) // -128
//let b1 = b - 1// 直接用-会报错,提示超出范围
let b1 = b &- 1
print(b1) // 结果为127
溢出时的计算,实际会从符号位(负数为1,正数为0)借位,比如Int8,它的最大值为127,最小值为-128,127表示后7位全1(2的8次方减1),符号位为0,即011111111,加上1,则变成100000000,刚好是-128,而-128减去1,正好又变成011111111,即127
注意:除非非常清晰明了值溢出会带来什么样的结果,否则,最好尽量使用标准的算符运算符,这样可以帮助我们发现错误
十七、为了Optional:合并空值运算符
合并空值运算符(空合运算符)
- 合并空值运算符(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的值相等,那返回的区间将是空的
单侧区间
- 闭区间有另外一种形式来让区间朝一个方向尽可能的远,这种区间叫做单侧区间,如
5...
何...5
- 半开区间运算符同样可以有单侧形式,只需要写它最终的值,如
..<5
- 比如说,一个包含数组所有元素的区间,从索引3到数组的结束。在这种情况下,你可以省略区间运算符一侧的值
let array = [1,2,3,4,5,6,7,8,9]
print("左开区间")
for value in array[...5] {
print(value)
}
print("右开区间")
for value in array[5...] {
print(value)
}
print("半开区间")
for value in array[..<5] {
print(value)
}
/*
左开区间
1
2
3
4
5
6
右开区间
6
7
8
9
半开区间
1
2
3
4
5
*/
- 单侧区间可以在其它上下文中使用,不仅仅是下标
- 不能遍历省略了第一个值的单侧区间,因为遍历根本不知道该从哪里开始。你可以遍历省略了最终值的单侧区间
let range = ...5 //PartialRangeThrough<Int>
print(range.contains(7)) //false
print(range.contains(4)) //true
print(range.contains(-1)) //true
字符串索引区间
- 字符串范围也可以使用区间运算符
var string = "abcdefghij"
let range = string.index(string.endIndex, offsetBy: -6)...
string.removeSubrange(range)
print(string)//abcd
倒序索引
- 通过reversed()方法,我们可以将一个正序循环变成逆序循环
for i in (0..<10).reversed(){
print(i)
}
Comparable区间
- 区间运算符可以作用在Comparable类型上,返回闭区间和半闭区间
var string = "abcDefghij"
let interval = "a"..."z"
for c in string{
if interval.contains(String(c)) == false{
print("\(c)不是小写字母")
}
}
//D不是小写字母
十九、位运算符
位取反运算符
- 位取反运算符(~)是对所有位进行取反操作(1变成0,0变成1)
let a : UInt8 = 0x5// 00000101
let b = ~a // 11111010
print(b) // 转为10进制即250
位与运算符
- 位与运算符(&)可以对两个数的比特位进行合并。它会返回一个新的数,只有当这两个数都是1的时候才能返回1
let a : UInt8 = 0x5// 00000101
var b: UInt8 = ~a // 11111010
var c = a & b
print(c) // 0 a和b互为反,所以与的结果为0
b = 0x11 // 00010001
c = a & b // 00000001
print(c) // 1
位或运算符
- 位或运算符(|)可以对两个比特位进行比较,然后返回一个新的数,只要两个操作位任意一个为1时,那么对应的位数就为1
let a : UInt8 = 0x5// 00000101
var b: UInt8 = ~a // 11111010
var c = a | b
print(c) // 255 a和b互为反,所以或就是全1
b = 0x11 // 00010001
c = a | b // 00010101
print(c) // 21
位异或运算符
- 位异或运算符,或者说“互斥或”(^)可以对两个数的比特位进行比较。它返回一个新的数,当两个操作数的对应位不相同时,该数的对应位就为1。(相同为0,不同为1)
let a : UInt8 = 0x5// 00000101
var b: UInt8 = ~a // 11111010
var c = a ^ b
print(c) // 255 a和b互为反,所以异或就是全1
b = 0x11 // 00010001
c = a ^ b // 00010100
print(c) // 20
异或也叫半加运算,其运算法则相当于不带进位的二进制加法
位左移和右移运算符
- 位左移运算符(<<)和位右移运算符(>>)可以把所有位数的数字向左或向右移动一个确定的位数
- 位左移和右移具有给整数乘以或除以二的效果。将一个数左移一位相当于把这个数翻倍,将一个数右移一位相当于把这个数减半
let a : UInt8 = 0x5// 00000101
let b = a << 2 // 00010100
let c = a >> 2 // 00000001
print(b) // 20
print(c) // 1
无符号整数的移位操作
- 已经存在的比特位按指定的位数进行左移和右移
- 任何移动超出整形存储边界的位都会被丢弃
- 用0来填充向左或向右移动后产生的空白位
有符号整数的移位操作
- 有符号整数使用它的第一位(所谓的符号位)来表示这个整数是正数还是负数。符号位为0表示为正数,1表示为负数
- 其余的位数(所谓的数值位)存储了实际的值。有符号正整数和无符号数的存储方式是一样的,都是从0开始算起
- 但是负数的存储方式略有不同。它存储的是2的n次方减去它的绝对值,这里的n为数值位的位数(补码)
补码表示的优点
- 首先,如果想给-4加个-1,只需要将这两个数的全部八个比特位相加(包括符号位),并且将计算结果中超出的部分丢弃
- 其次,使用二进制补码可以使负数的位左移和右移操作得到跟正数同样的效果,即每向左移一位就将自身的数值乘以2,每向右移一位就将自身的数值除以2。要达到此目的,对有符号整数的右移有一个额外的规则:当对整数进行位右移操作时,遵循与无符号整数相同的规则,但是对于移位产生的空白位使用符号位进行填充,而不是0
// 10000000 - 01110101 = 00001011
let a : Int8 = -0x75// 10001011
let b = a << 2 // 00101100
let c = a >> 2 // 11100010,实际存储的数值为00011110,加上符号即-30
print(b) // 44
print(c) // -30
二十、位运算经典算法
两个数字交换
- 不借助临时变量,交换两个变量的值
var a = 10
var b = 20
a = a ^ b
b = a ^ b
a = a ^ b
print(a) // 20
print(b) // 10
求无符号整数二进制中1的个数
- 给定一个无符号整形(UInt)变量,求其二进制表示中“1”的个数,要求算法的执行效率尽可能的高
func countOfOnes(num: UInt) -> UInt{
var count: UInt = 0
var temp = num
while temp != 0{
count += temp & 1
temp >>= 1
}
return count
}
print(countOfOnes(num: 54))// 00110110,打印为 4
- 如果整数的二进制中有较多的0,那么我们每一次右移一位做判断会很浪费,怎么改进前面的算法呢?有没有算法让算法的复杂度只与“1”的个数有关?
- 思路:为了简化这个问题,我们考虑只有高位又1的情况,例如11000000,如何跳过前面低位的6个0,而直接去判断第七位的1?我们可以设计11000000和10111111(也就是11000000 - 1)做“与”操作,消去最低位的1.如果得到的结果为0,说明我们已经找到或者消去里面最后一个1。如果得到的结果为0,说明我们已经找到或者消去里面最后一个1。如果不为0,那么说明我们消去了最低位的1,但是二进制中还有其他的1,我们的计数器需要加1,然后继续上面的操作。
func countOfOnes(num: UInt) -> UInt{
var count: UInt = 0
var temp = num
while temp != 0{
count += 1
temp &= temp - 1
}
return count
}
print(countOfOnes(num: 54))// 4
如何判断一个整数为2的整数次幂
- 给定一个无符号整形(UInt)变量,判断是否为2的整数次幂
- 思路:一个整数如果是2的整数次方,那么它的二进制表示中有且只有一位是1,而其它所有位都是0,根据前面的分析,把这个整数减去1后再和它自己做与运算,这个整数中唯一的1就变成0了,也就是得到的结果为0
func isPowerOfTwo(num: UInt) -> Bool{
return (num & (num - 1)) == 0
}
print(isPowerOfTwo(num:512))//true
缺失的数字
- 很多成对出现的正整数保存在磁盘文件中,注意成对的数字不一定是相邻的,如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 ^= num
}
return lostNum
}
print(findLostNum(nums: [1,2,3,4,3,2,1])) // 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在这一位上是不相同的。
func findLostNums(nums: [UInt]) -> (UInt,UInt){
var lostNum1: UInt = 0
var lostNum2: UInt = 0
var temp: UInt = 0
//计算两个数的异或结果
for num in nums {
temp ^= num
}
//找到第一个为1的位
var flag: UInt = 1
while (flag & temp) == 0 {
flag <<= 1
}
//找到两个丢失的数字
for num in nums {
if (num & flag) == 0{
lostNum1 ^= num
} else {
lostNum2 ^= num
}
}
return (lostNum1,lostNum2)
}
print(findLostNums(nums: [1,2,3,4,5,3,2,1])) // (4, 5)
二十一、运算符的优先级和结合性
基本概念
- 运算符的优先级使得一些运算符优先于其它运算符,高优先级的运算符会先计算
- 结合性定义了具有相同优先级的运算符是如何结合(或关联)的--是与左边结合为一组,还是与右边结合为一组。可以这样理解:“它们是与左边的表达式结合的”或者“它们是与右边的表达式结合的”
编程规范
- 最好使用小括号来显式的指定优先级,这样代码易于阅读
- Swift语言中逻辑运算符&&和||是左相关的,这意味着多个逻辑运算符组合的表达式会首先计算最左边的字表达式
二十二、运算符重载
运算符重载
- 类和结构体可以为现有的运算符提供自定义的实现,称为运算符重载
struct Vector2D{
var x = 0.0
var 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 v1 = Vector2D(x: 3,y:1)
let v2 = Vector2D(x: 5,y:8)
let combinedVector = v1+v2
print(combinedVector) // Vector2D(x: 8.0, y: 9.0)
一元运算符重载
- 类与结构体也能提供标准一元运算符的实现
- 要实现前缀或者后缀运算符,需要在声明运算符函数的时候在func关键字之前指定
prefix
或者postfix
限定符
struct Vector2D{
var x = 0.0
var y = 0.0
}
extension Vector2D{
static prefix func - (vector: Vector2D) -> Vector2D{
return Vector2D(x: -vector.x,y: -vector.y)
}
}
let positive = Vector2D(x: 3,y:1)
let negative = -positive
print(negative) // Vector2D(x: -3.0, y: -1.0)
组合赋值运算符重载
- 组合赋值运算符将赋值运算符(=)与其它运算符进行结合
- 在实现的时候,需要把运算符的左参数设置成
inout
类型,因为这个参数的值
struct Vector2D{
var x = 0.0
var y = 0.0
}
extension Vector2D{
static func +(left: Vector2D,right: Vector2D) -> Vector2D {
return Vector2D(x: left.x + right.x,y:left.y + right.y)
}
static func +=(left: inout Vector2D,right: Vector2D) {
left = left + right
}
static func -(left: Vector2D,right: Vector2D) -> Vector2D {
return Vector2D(x: left.x - right.x,y:left.y - right.y)
}
static func -=(left: inout Vector2D,right: Vector2D) {
left = left - right
}
}
var original = Vector2D(x: 3,y:1)
let vectorToAdd = Vector2D(x: 13,y:15)
original += vectorToAdd
print(original) // Vector2D(x: 16.0, y: 16.0)
等价运算符重载
- 自定义类和结构体不接受等价运算符的默认实现,也就是所谓的“等于”运算符(==)和“不等于”运算符(!=)
- 要使用等价运算符来检查你自己类型的等价,需要和其他中缀运算符一样提供一个“等于”运算符重载,并且遵循标准库的
Equatable
协议
struct Vector2D{
var x = 0.0
var y = 0.0
}
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
var y = 0.0
var z = 0.0
}
let v1 = Vector3D(x:2,y: 3,z: 4)
let v2 = Vector3D(x:2,y: 3,z: 4)
if (v1 == v2){
print("这两个向量是相等的")//这两个向量是相等的
}
二十三、自定义运算符
- 除了实现标准运算符,在Swift中还可以声明和实现自定义运算符(custom operators)
- 新的运算符要在全局作用域内,使用
operator
关键字进行声明,同时还要指定prefix
、infix
或者postfix
限定符
struct Vector2D{
var x = 0.0
var y = 0.0
}
// 必须先声明在全局作用域
prefix operator +++
prefix operator ---
extension Vector2D{
static func +(left: Vector2D,right: Vector2D) -> Vector2D {
return Vector2D(x: left.x + right.x,y:left.y + right.y)
}
static func +=(left: inout Vector2D,right: Vector2D) {
left = left + right
}
static func -(left: Vector2D,right: Vector2D) -> Vector2D {
return Vector2D(x: left.x - right.x,y:left.y - right.y)
}
static func -=(left: inout Vector2D,right: Vector2D) {
left = left - right
}
// 实现自增,每个元素加1
static prefix func +++ (vector: inout Vector2D) -> Vector2D{
vector += Vector2D(x: 1,y: 1)
return vector;
}
// 实现自减,实现中每个元素减1
static prefix func --- (vector: inout Vector2D) -> Vector2D{
vector -= Vector2D(x: 1,y: 1)
return vector
}
}
自定义中缀运算符的优先级和结合性
- 自定义中缀(infix)运算符也可以指定优先级和结合性
- 每一个自定义的中缀运算符都属于一个优先级组
- 优先级组指定了中缀运算符和其他中缀运算符的关系
优先级的定义如下:
/*
定义自己的优先级组
associativity表示结合性
lowerThan表示优先级在哪个优先级之下
higherThan表示优先级在哪个优先级之上
*/
precedencegroup MyPrecedence{
associativity: left
lowerThan: MultiplicationPrecedence
higherThan: AdditionPrecedence
}
自定义中缀运算符的案例如下:
// 定义一个+-运算符,优先级是加法组
infix operator +-: AdditionPrecedence
extension Vector2D{
static func +-(left: Vector2D,right: Vector2D) -> Vector2D{
return Vector2D(x: left.x + right.x,y: left.y - right.y)
}
}
// 定义一个乘平方运算符,优先级是乘法组 乘法组优先级高于加法组
infix operator *^: MultiplicationPrecedence
extension Vector2D{
static func *^(left: Vector2D,right: Vector2D) -> Vector2D{
return Vector2D(x: left.x * right.x,y: left.y * left.y + right.y * right.y)
}
}
let firstVector = Vector2D(x: 1,y: 2)
let secondVector = Vector2D(x: 3,y: 7)
let plusMinusVector = firstVector +- secondVector
let thirdVector = Vector2D(x: 2,y: 2)
let vector = firstVector +- secondVector *^ thirdVector
print(plusMinusVector)//Vector2D(x: 4.0, y: -5.0)
print(vector)//Vector2D(x: 7.0, y: -51.0)
若使用一个自定义的优先级(低于加法优先级),代码如下:
precedencegroup MyPrecedence{
associativity: left
lowerThan: AdditionPrecedence
}
// 定义一个+-运算符,优先级是加法组
infix operator +-: AdditionPrecedence
extension Vector2D{
static func +-(left: Vector2D,right: Vector2D) -> Vector2D{
return Vector2D(x: left.x + right.x,y: left.y - right.y)
}
}
// 定义一个乘平方运算符,优先级是自定义的低于加法组的优先级
infix operator *^: MyPrecedence
extension Vector2D{
static func *^(left: Vector2D,right: Vector2D) -> Vector2D{
return Vector2D(x: left.x * right.x,y: left.y * left.y + right.y * right.y)
}
}
let firstVector = Vector2D(x: 1,y: 2)
let secondVector = Vector2D(x: 3,y: 7)
let plusMinusVector = firstVector +- secondVector
let thirdVector = Vector2D(x: 2,y: 2)
let vector = firstVector +- secondVector *^ thirdVector
print(plusMinusVector)//Vector2D(x: 4.0, y: -5.0)
print(vector)//Vector2D(x: 8.0, y: 29.0)
二十四、流程控制-循环
for-in循环
- 使用for-in循环来遍历序列,比如一个范围的数字,数组中的元素或者字符串中的字符
for i in 0...3{
print(i)
}
for c in "你好啊"{
print(c)
}
let set = Set([1,3,5])
for i in set{
print(i)
}
let array = [1,3,5]
for i in array{
print(i)
}
- 当字典遍历时,每一个元素都返回一个(key,value)元组,你可以在for-in循环体中使用显式命令常量来分解(key,value)元组成员
let dictionay = ["a":1,"b":2,"c":3]
for tuple in dictionay{
print(tuple.0)
print(tuple.1)
}
for (key,value) in dictionay{
print(key)
print(value)
}
for (_, value) in dictionay{
print(value)
}
- 如果不需要序列的每一个值,你可以使用下划线来取代遍历名以忽略值
let dictionay = ["a":1,"b":2,"c":3]
for (_, value) in dictionay{
print(value)
}
var addition = 0
for _ in 1...10{
addition += 1
}
for-in 分段区间
- 使用
stride(from:to:by:)
函数来跳过不想要的标记(开区间) - 闭区间也同样适用,使用
stride(from:through:by:)
即可
let interval = 5
for mark in stride(from: 0, to: 50, by: interval){
print(mark)
}
print("--------------")
for mark in stride(from: 0, through: 50, by: interval){
print(mark)
}
/*
0
5
10
15
20
25
30
35
40
45
--------------
0
5
10
15
20
25
30
35
40
45
50
*/
while循环
- 直接用
while
循环 -
repeat-while
循环(和OC中的do-while
一样)
var count = 0
repeat {
print(count)
count += 1
} while count < 5
二十五、流程控制-(switch-case)
switch
- switch语句会将一个值与多个可能的模式匹配。然后基于第一个成功匹配的模式来执行合适的代码块
- switch语句一定得是全面的。就是说,给定类型里每一个值都得被考虑到并且匹配到一个switch的case。如果无法提供一个switch-case所有可能的值,你可以定义一个默认匹配所有的case来匹配所有未明确出来的值。这个匹配所有的情况用关键字
default
标记,并且必须在所有case的最后出现。
在OC中,switch语句如果不全面,仍然可以运行
没有隐式贯穿
- 相比C和OC里的switch语句来说,Swift中的switch语句不会默认从匹配case的末尾贯穿到下一个case里
- 相反,整个switch语句会在匹配到第一个switch的case执行完毕之后退出,不再需要显式的break语句
- 如果想贯穿到下一个case,在此case语句的末尾使用
fallthrough
执行体
- 每一个case的函数体必须包含至少一个可执行的语句
- 在一个switch的case中匹配多个值可以用逗号分割,并且可以写成多行
let c: Character = "a"
switch c{
case "a":
fallthrough
case "A":
print("the letter is A")
default:
print("the letter is not A")
}
print("-----------")
switch c{
case "a","A":
print("the letter is A")
default:
print("the letter is not A")
}
/*
the letter is A
-----------
the letter is A
*/
区间匹配
- switch的case的值可以在一个区间中匹配
let approximateCount = 62
let countedThings = "moons orbiting Saturn"
var naturalCount: String
switch approximateCount{
case 0:
naturalCount = "no"
case 1..<5:
naturalCount = "a few"
case 5..<12:
naturalCount = "several"
case 12..<100:
naturalCount = "dozens of"
case 100..<1000:
naturalCount = "hundreds of"
default:
naturalCount = "many"
}
print("There are \(naturalCount) \(countedThings).")
//There are dozens of moons orbiting Saturn.
元组匹配
- 可以使用元组来在一个switch语句中测试多个值
- 使用下划线(_)来表明匹配所有可能的值
let point = (1,1)
switch point{
case (0,0):
print("origin")
case (_,0):
print("x-axis")
case (0,_):
print("y-axis")
case (-2...2,-2...2):
print("inside a 2 side box")
default:
print("outside of the box")
}
//inside a 2 side box
值绑定
- switch的case可以将匹配到的值临时绑定为一个常量或者变量,来给case的函数体使用
- 如果使用var关键字,临时的变量就会以合适的值来创建并初始化。对这个变量的任何改变都只会在case的函数体内有效
let point = (1,1)
switch point{
case (let x,0):
print("on the x-axis with an x value of \(x)")
case (0,let y):
print("on the y-axis with an y value of \(y)")
case (let x,let y):
print("point is {\(x),\(y)}")
}
//point is {1,1}
where子句
- switch-case可以使用where子句来检查是否符合特定的约束
let point = (1,-1)
switch point{
case let (x,y) where x == y:
print("on the line y=x")
case let (x, y) where x == -y:
print("on the line y=-x")
case let (x, y):
print("point is {\(x),\(y)}")
}
//on the line y=-x
符合匹配
- 多种情况共享同一个函数体的多个情况可以在case后写多个模式来复合,在每个模式之间用逗号分割。
- 如果任何一个模式匹配了,那么这个情况都会被认为是匹配的。
- 如果模式太长,可以把它们写成多行
let c = Character("m")
switch c{
case "a","e","i","o","u":
print("元音字母")
case let cc where "bcdfghjklmnpqrstvwxyz".contains(cc):
print("辅音字母")
default:
print("既不是元音,也不是辅音")
}
二十六、流程控制-控制转移
- continue
- break
- fallthrough
- return
- throw
continue
- continue语句告诉循环停止正在做的事情并且再次从头开始循环的下一次遍历。它是说“我不再继续当前的循环遍历了”而不是离开整个的循环。
break
- break语句会立即结束整个控制流语句。当你想要提前结束switch或者循环语句或者其他情况时可以在switch语句或者循环语句中使用break语句。
- 当在循环语句中使用时,break会立即结束循环的执行,并且转移控制到循环结束花括号(})后的第一行代码上。当前遍历循环里的其他代码都不会被执行,并且余下的遍历循环也不会开始了。
- 当在switch语句里使用时,break导致switch语句立即结束它的执行,并且转移控制到switch语句结束花括号(})之后的第一行代码上
fallthough
- 如果确实需要C或者OC风格的贯穿行为,可以选择在switch中case的末尾加上fallthough关键字
语句标签
- 可以用语句标签来给循环语句或者条件语句作标记。在一个条件语句中,你可以使用一个语句标签配合break语句来结束被标记的语句。在循环语句中,你可以使用语句标签来配合break或者continue语句来结束或者继续执行被标记的语句。
var number = 10
whileLoop: while number > 0 {
switch number{
case 9:
print("9")
case 10:
var sum = 0
for index in 0...10{
sum += index
if index == 9 {
print(sum)
// 从这里直接退出了外层的循环
break whileLoop
}
}
default:
break
}
number -= 1
}
二十七、使用guard来改善条件判断
guard
guard语句,类似于if语句,基于布尔值表达式来执行语句。使用guard语句来要求一个条件必须是真才能执行guard之后的语句。与if语句不同,guard语句综上有一个else子句--else子句中的代码会在条件不为真的时候执行。
黄金大道原则:当编写条件语句的时候,左边的代码间距应该是一个“黄金”或者“快乐”的大道。这是说,不要嵌套if语句。多个return语句是OK的。这样可以避免圈复杂度(Cyclomatic Complexity),并且让代码更加容易阅读。因为你的方法的重要部分没有嵌套在分支上,你可以很清楚地找到相关的代码。
Early Exit原则
func isIPAddress(ipAddr: String) -> (Int,String){
let compoments = ipAddr.split(separator: ".")
guard compoments.count == 4 else {
return (100,"ip只能有4部分")
}
guard let first = Int(compoments[0]),first >= 0 && first <= 255 else {
return (1,"第一个数不对")
}
guard let second = Int(compoments[1]),second >= 0 && second <= 255 else {
return (1,"第二个数不对")
}
guard let third = Int(compoments[2]),third >= 0 && third <= 255 else {
return (1,"第三个数不对")
}
guard let fourth = Int(compoments[3]),fourth >= 0 && fourth <= 255 else {
return (1,"第四个数不对")
}
return (0,"")
}
检查API的可用性
- Swift拥有内置的对API可用性的检查功能,它能够确保你不会悲剧地使用了对部属目标不可用的API
- 你可以在if或者guard语句中使用一个可用性条件来由条件地执行代码,基于在运行时你想用的哪个API是可用的
if #available(platform name version, *)
if #available(iOS 13.0,macOS 12.1, *){
// 使用iOS13.0,macOS 12.1后的API
} else {
// fall back到早期的iOS、macOS的API
}
二十八、模式和模式匹配
模式
模式代表单个值或者复合值的结构
例如,元组(1,2)的结构是由逗号分割的,包含两个元素的列表。因为模式代表一种值的结构,而不是特定的某个值,你可以利用模式来匹配各种各样的值。比如,(x,y)可以匹配元组(1,2),以及任何含两个元素的元组。除了利用模式匹配一个值以外,你可以从复合值中提取出部分或全部值,然后分别把各个部分的值和一个常量或变量绑定起来。
-
Swift中的模式分为两类:一种能成功匹配任何类型的值,另一种在运行时匹配某个特定值时可能会失败。
- 第一类模式用于解构简单变量、常量和可选绑定中的值。此类模式包括通配符模式、标识符模式,以及包含前两种模式的值绑定模式和元组模式。你可以为这类模式指定一个类型标注,从而限制它们只能匹配某种特定类型的值。
- 第二类模式用于全模式匹配,这种情况下你试图匹配的值在运行时可能不存在。此类模式包括枚举用例模式、可选模式、表达式模式和类型转换模式。你在switch语句的case标签中,do语句的catch子句中,或者在if、while、guard和for-in语句的case条件句中使用此类模式。
模式分类
- 通配符模式(Wildcard Pattern)
- 标识符模式(Identifier Pattern)
- 值绑定模式(Value-Binding Pattern)
- 元组模式(Tuple Pattern)
- 枚举用例模式(Enumeration Case Pattern)
- 可选项模式(Optional Pattern)
- 类型转换模式(Type-Casting Pattern)
- 表达式模式(Expression Pattern)
通配符模式(Wildcard Pattern)
- 通配符模式由一个下划线(_)构成,用于匹配并忽略任何值。当你想忽略被匹配的值时可使用该模式
for _ in 1...3{
}
标识符模式(Identifier Pattern)
- 标识符模式匹配任何值,并将匹配的值和一个变量或常量绑定起来
let someValue = 42
值绑定模式(Value-Binding Pattern)
- 值绑定模式把匹配到的值绑定给一个变量或者常量。把匹配到的值绑定给常量时,用关键字let,绑定给变量时,用关键字var。
let point = (3,2)
switch point{
// 将point中的元素绑定到x和y
case let (x,y):
print("The point is at (\(x),\(y))")//The point is at (3,2)
}
var optionalValue: String? = "abc"
guard let optionalValue else {
print("value is nil")
throw NSError(domain: "ttt", code: 10034, userInfo: nil)
}
print("value is not nil")
元组模式(Tuple Pattern)
- 元组模式是由逗号分割的,具有零个或多个模式的列表,并由一对圆括号括起来。元组模式匹配相应元组类型的值。
- 可以使用类型标注去限制一个元组模式能匹配哪种元组类型。例如,在常量声明
let (x,y): (Int, Int) = (1,2)
中的元组模式(x,y): (Int, Int)
只能匹配两个元素都是Int类型的元组 - 当元组模式被用于for-in语句或者变量和常量声明时,它仅可以包含通配符模式、标识符模式、可选模式或者其他包含这些模式的元组模式
let points = [(0,0),(1,0),(1,1),(2,0),(2,1)]
for (x,y) in points where y == 0{
print("\(x) and \(y)")
}
/*
0 and 0
1 and 0
2 and 0
*/
枚举用例模式(Enumeration Case Pattern)
- 枚举用例模式匹配现有的某个枚举类型的某个用例。枚举用例模式出现在switch语句中的case标签中,以及if、while、guard和for-in语句的case条件中。
可选项模式(Optional Pattern)
- 可选项模式匹配Optional<Wrapped>枚举在some(Wrapped)中包装的值。
- 可选项模式为for-in语句提供了一种迭代数组的简便方式,只为数组中非nil的元素执行循环体
let intOptional: Int? = 42
// 用可选项模式的枚举来匹配
if case .some(let x) = intOptional {
print(x) // 42
}
// 用可选项模式匹配 只会匹配到非nil的值
if case let x? = intOptional{
print(x) // 42
}
let arrayOfOptionalInts: [Int?] = [nil,2,3,nil,5]
// 只会匹配到非nil的值
for case let number? in arrayOfOptionalInts{
print("found a \(number)")
}
/*
42
42
found a 2
found a 3
found a 5
*/
类型转换模式(Type-Casting Pattern)
- 有两种类型转换模式,is模式和as模式。is模式只出现在switch语句中的case标签中。is模式和as模式形式如下:
- is 类型
- 模式 as 类型
- is模式仅当一个值的类型在运行时和is模式右边的指定类型一致,或者是其子类的情况下,才会匹配这个值。is模式和is运算符有相似表现,它们都进行类型转换,但是is模式没有返回类型。
- as模式仅当一个值的类型在运行时和as模式右边的指定类型一致,或者是其子类的情况下,才会匹配这个值。如果匹配成功,被匹配的值的类型被转换成as模式右边指定的类型
protocol Animal{
var name: String {get}
}
struct Dog: Animal{
var name: String{
"dog"
}
var runSpeed: Int
}
struct Bird: Animal{
var name: String{
"bird"
}
var flightHeight: Int
}
struct Fish: Animal{
var name: String{
"fish"
}
var depth: Int
}
let animals: [Any] = [Dog(runSpeed: 55), Bird(flightHeight: 2000), Fish(depth: 100)]
for animal in animals{
switch animal{
case let dog as Dog:
print("\(dog.name) can run \(dog.runSpeed)")
case let fish as Fish:
print("\(fish.name) can dive depth \(fish.depth)")
case is Bird:
print("bird can fly")
default:
print("unknown animal")
}
}
/*
dog can run 55
bird can fly
fish can dive depth 100
*/
表达式模式(Expression Pattern)
- 表达式模式代表表达式的值。表达式模式只出现在switch语句中的case标签中。
- 表达式模式代表的表达式会使用Swift标准库中的
~=
运算符与输入表达式的值进行比较。如果~=
运算符返回true,则匹配成功。默认情况下,~=
运算符使用==
来比较两个相同类型的值。它也可以将一个整形数值与一个Range实例中的一段整数区间做匹配。
let point = (1,2)
switch point{
case (0,0):
print("origin")
case (-2...2,-2...2):
print("in the square")//in the square
default:
print("The point is at (\(point.0),\(point.1)).")
}
- 可以重载
~=
运算符来提供自定义的表达式匹配行为
func ~= (pattern: String,value: Int) -> Bool{
pattern == "\(value)"
}
let point = (0,0)
switch point{
case ("0","0"):
print("origin") // origin
default:
print("The point is at (\(point.0),\(point.1)).")
}
- 自定义类型默认也是无法进行表达式模式匹配的,也需要重载
~=
运算符
struct Employee{
var salary: Float
}
let e = Employee(salary: 33000)
func ~=(lhs: Range<Float>,rhs: Employee) -> Bool{
lhs.contains(rhs.salary)
}
switch e {
case 0.0..<1000:
print("艰难度日")
case 1000..<5000:
print("勉强温饱")
case 5000..<10000:
print("日子不错了")
case 10000..<50000:
print("中产阶级") // 中产阶级
default:
print("不敢想象")
}