一、流程控制
-
1.1、if 语句
let age = 5 if age > 10 { print("\(age) 大于 10 ") } else if age > 3 { print("\(age) 大于 3 ") } else { print("\(age) 小于 3 ") }
提示:
- if 后面的条件可以省略小括号,而条件后面的
花括号
不可以省略 -
if 后面的类型只能是 bool 类型
- if 后面的条件可以省略小括号,而条件后面的
-
1.2、while 语句(先判断后执行)
var num = 5 while num > 0 { num -= 1 print("num=\(num)") }
打印结果:num=4、num=3、num=2、num=1、num=0
-
1.3、repeat while 语句(先执行后判断)
var num = -1 repeat{ print("num=\(num)") }while num > 0
打印结果:num=-1,
提示:repeat-while 相当于 OC 里面的 do-while
-
1.4、for 语句
-
闭区间运算符:
a...b
;a<= 取值 <=b
let names = ["小1","小2","小3","小4"] for i in 0...3 { print("name=\(names[i])") }
提示:上面的
0...3
可以替换为let range = 0...3
let a = 0 var b = 3 let names = ["小1","小2","小3","小4"] for i in a...b { print("name=\(names[i])") }
提示:区间可以用常量或者变量来表示的
for _ in 0...3 { print("😆") }
打印了 四次 😆
-
for 循环里面的 i 默认是 let,有需要的时候我们可以声明为 var,如下
for var i in 1...3 { i += 2 print("i=\(i)") }
打印结果:3、4、5
-
半开区间运算符:
a..<b
;a <= 取值 < b
for i in 1..<3 { print("i=\(i)") }
打印结果:1、2
-
-
1.5、for - 区间运算符用在数组上
-
例一
let names = ["小1","小2","小3","小4"] for name in names[0...3] { print("name=\(name)") }
打印结果:小1、小2、小3、小4
-
例二:单侧区间:让区间朝一个方向尽可能的远
let names = ["小1","小2","小3","小4"] for name in names[1...] { print("name=\(name)") }
打印结果:小2、小3、小4
let names = ["小1","小2","小3","小4"] for name in names[...2] { print("name=\(name)") }
打印结果:小1、小2、小3
let names = ["小1","小2","小3","小4"] for name in names[..<2] { print("name=\(name)") }
打印结果:小1、小2
let range = ...5 print(range.contains(8)) print(range.contains(3)) print(range.contains(-1))
打印结果:false、true、true
提示: let range = ...5 代表 range的范围从无穷小到 5,包含5
-
-
1.6、区间类型(重点)
let range1:ClosedRange<Int> = 1...3 let range2:Range<Int> = 1..<3 let range3:PartialRangeThrough<Int> = ...5
-
字符、字符串也能进行区间运算,但默认不能在
for-in
中,因为 ClosedRange:不可数的闭区间let stringRange1 = "cc"..."ff" // ClosedRange<String> stringRange1.contains("cb") // false stringRange1.contains("dz") // true stringRange1.contains("fg") // false let stringRange2 = "a"..."f" // ClosedRange<String> stringRange2.contains("d") // true stringRange2.contains("n") // false
提示:比较的是ascii,第1位比较第1位,第2位比较第2位,如果第1位大于第1位就没必要比较第二位了,依次类推
-
0到~
包含了所有可能要用到的 ASCII 字符let characterRange:ClosedRange<Character> = "\0"..."~" characterRange.contains("b") // true
-
-
1.8、带间隔的区间值
let hours = 12 let hourInterval = 2 // 从tickMark 的取值:从 4 开始,累加2,不超过 12 for tickMark in stride(from: 4, to: hours, by: hourInterval) { print("tickMark=\(tickMark)") }
打印结果:4、6、8、10
-
1.9、switch 语句
-
案例一:switch的基本使用
var num = 10 switch num { case 1: print("值是1") break case 2: print("值是2") break case 3: print("值是3") break default: print("没有匹配") break }
提示:case 和 default 后面不能写大括号
{}
,默认可以不写 break,并不会贯穿到后面的条件 -
案例二:使用 fallthrough 可以实现贯穿效果
var num = 1 switch num { case 1: print("num 值是 1") fallthrough case 2: print("num 值是 2") case 3: print("num 值是 3") default: print("没有匹配") }
打印结果:num 值是 1、num 值是 2
-
switch使用注意点:
-
<1>、switch 必须能保证处理所有的情况
-
<2>、case 或者 default 后面至少有一条语句,如果不想有语句可以写 break
var num = 1 switch num { case 1: print("num 值是 1") case 2: break default: print("没有匹配") }
-
<3>、如果能保证处理所有的情况,也可以不必使用 default,如下
enum Answer {case right,wrong} let answer = Answer.right switch answer { case Answer.right: print("right") case Answer.wrong: print("wrong") }
提示:上面已经确定 answer 是 Answer 类型,因此可以省略 Answer
enum Answer {case right,wrong} let answer = Answer.right switch answer { case .right: print("right") case .wrong: print("wrong") }
-
-
复合条件:switch也支持Character、String 类型,如下例子
-
例一:String 类型
let name = "marry" switch name { case "Jack": print("Jack") case "marry": print("marry") default: print("找不到某人") }
-
例二:String 类型
let name = "marry" switch name { case "Jack","marry": print("Jack and marry") default: print("找不到某人") }
-
例三:Character 类型
let character = "A" switch character { case "a","A": print("字母 a或者A") default: print("找不到字母") }
-
-
匹配区间和元组匹配:
-
匹配区间
let count = 20 switch count { case 0: print("none") case 1..<5: print("a few") case 5..<100: print("several") case 100..<1000: print("hundreds of") default: print("many") }
-
元组匹配
let point = (1,1) switch point { case (0,0): print("the origin") case (_,0): print("on the x-axis") case (0,_): print("on the y-axis") case (-2...2,-2...2): print("inside the box") default: print("many") }
提示:可以使用
_
忽略某个值
-
-
值绑定:必要时,let 可以改为 var
let point = (2,0) 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,y): print("somehere else at (\(x),\(y))") }
-
-
1.10、where语句
let point = (2,-2) switch point { case let (x,y) where x == y: print("on the line x == y") case let (x,y) where x == -y: print("on the line x == -y") case let (x,y): print("(\(x),\(y)) is just some arbitrary point") }
例二:所有的正数相加
let numbers = [-1,2,3,-10,20] var sum = 0 for num in numbers where num > 0 { sum += num } print("最后正数的和=\(sum)")
提示:where 用来过滤 负数
-
1.11、标签语句
outer: for i in 1...4{ for k in 1...4 { if k == 3 { continue outer } if i == 3 { break outer } print("i=\(i) k=\(k)") } }
二、函数
-
2.1、函数的定义:是一组代码的组合,因此函数也被称为完成特殊功能的代码块。函数三要素:函数名、参数、返回值。
-
有返回值,有参数
func pi()->Double{ return 3.14 } print(pi()) func sum(num1:Int,num2:Int)->Int{ return num1 + num2 } print(sum(num1: 1, num2: 3))
提示:形参默认是 let,也只能是 let
-
无返回值,无参数;下面的几种 无返回值表达的意思是一样的
func sayHello()->Void{ print("Hello") } func sayHello()->(){ print("Hello") } func sayHello(){ print("Hello") }
-
-
2.2、函数的隐式返回
func sum(num1:Int,num2:Int) -> Int{ num1 + num2 } sum(num1: 1, num2: 3)
提示:如果整个函数的整体是一个单一的表达式,那么函数会隐式返回这个表达式
-
2.3、返回元组 和 实现多值返回
func calculate(v1: Int,v2:Int) -> (sum:Int,difference:Int,average:Int){ let sum = v1 + v2 return (sum,v1-v2,sum>>1) } let result = calculate(v1: 20, v2: 10) result.sum // 和 result.difference // 差 : 10 result.average // 二进制右移 是 平均值 15
提示:
sum>>1
代表值的二进制数 右移 求平均值 -
2.4、函数的文档注释 参考苹果的链接
/// 求和 【概述】 /// /// 将两个整数相加 【更详细的描述】 /// /// - Parameter v1: 第 1 个整数 /// - Parameter v2: 第 2 个整数 /// - Returns : 两个整数的和 /// /// - Note:传入两个整数即可 【批注】,有没有什么特殊的用法 /// func sum(_ v1:Int,_ v2:Int) -> Int{ return v1 + v2 } sum(10, 20)
效果如下:
-
2.5、参数标签
-
可以修改参数标签
func goToWork(at time:String){ print("this time is \(time)") } goToWork(at: "08:00")
打印结果:
this time is 08:00
-
可以使用下划线
_
省略参数标签func sum(_ v1:Int,_ v2:Int) -> Int{ return v1 + v2 } sum(10, 20)
-
-
2.6、默认参数值(Default Parameter Value)
-
参数可以有默认值
func check(name:String = "noPerson",age:Int,job:String = "iOS"){ print("名字=\(name) 年龄=\(age) 工作=\(job)") } check(name: "老大", age: 19, job: "iOS") check(name: "老二", age: 19) check(age: 10, job: "前端") check(age: 16)
提示:只要是有默认参数值的就可以不传参数
C++的默认参数值有个限制:必须从右往左设置。由于Swift拥有参数标签,因此并没有此类限制
-
但是在省略参数标签时,需要特别注意,避免出错,如下
这里的two
不可以省略参数标签func test(_ one:Int = 10,two:Int,_ three:Int = 20){ print("打印 one = \(one) two = \(two) three = \(three)") } test(two: 9)
-
-
2.7、可变参数(Variadic Parameter)
func sum(_ numbers: Int...) -> Int { var total = 0 for i in numbers { total += I } return total } sum(1,2,3)
提示:一个函数 最多只能有1个 可变参数;紧跟在可变参数后面的参数不能省略参数标签,如下
func test(_ numbers:Int...,string:String,_ other:String){ } test(1,2,3, string: "iOS", "Rose")
参数 string 标签不能省略
-
2.8、Swift 自带的 print 函数
/// - Parameters: /// - items: Zero or more items to print. /// - separator: A string to print between each item. The default is a single /// space (`" "`). /// - terminator: The string to print after all items have been printed. The /// default is a newline (`"\n"`). public func print(_ items: Any..., separator: String = " ", terminator: String = "\n")
分析: 第一个参数是要打印的值,第二个参数是:连接第一个参数值的 字符,第三个参数是:默认是
\n
换行,如下例子print(1,2,3,4,5) print(1,2,3, separator: "_") print(1,2,3, separator: "_", terminator: "") print("哈哈")
打印结果是: 最后一行不换行,因为
"\n"
被用""
取代了1 2 3 4 5 1_2_3 1_2_3哈哈
-
2.9、输入输出参数 (In-Out Parameter)
可以用 inout 定义一个输入输出函数:可以在函数内部修改外部实参的值func swapValues(_ v1:inout Int,_ v2:inout Int){ let tem = v1 v1 = v2 v2 = tem } var num1 = 10 var num2 = 20 swapValues(&num1, &num2) print(num1,num2)
打印结果:20 10
-
提示:下面的函数与上面的等价:传进去的是 内存地址,所以可以修改其外部参数的值
func swapValues(_ v1:inout Int,_ v2:inout Int){ (v1,v2) = (v2,v1) }
可变参数不能标记为 inout
输入输出参数不能有默认值
输入输出参数不能传入常量(let)、字面量作为实参。也就是可以被多次赋值的 变量
inout
参数的本质是地址传递 (引用传递)
-
-
2.10、函数重载 (Function Overload)
规则:函数名相同
参数个数不同 || 参数类型不同 || 参数标签不同func sum(v1:Int,v2:Int) -> Int{ return v1 + v2 }
参数个数不同
func sum(v1:Int,v2:Int,v3:Int) -> Int{ return v1 + v2 }
参数类型不同
func sum(v1:Int,v2:Double) -> Double{ return Double(v1) + v2 } func sum(v1:Double,v2:Int) -> Double{ return v1 + Double(v2) }
参数标签不同
func sum(_ v1:Int,_ v2:Int) -> Int{ return v1 + v2 } func sum(a:Int,b:Int) -> Int{ return a + b }
-
函数重载注意点一: 返回值类型 与 函数重载 无关
func sum(a:Int,b:Int) -> Int{ return a + b } func sum(a:Int,b:Int) { } sum(a: 1, b: 2)
-
函数重载注意点二:默认参数值和函数重载一起使用产生二义性时,编译器并不会报错(在 C++ 中会报错)
func sum(a:Int,b:Int) -> Int { return a + b } func sum(a:Int,b:Int,c:Int = 10) -> Int { return a + b + c } // 会调用 sum(a:Int,b:Int),这样不太好,还不如编译器报错 sum(a: 1, b: 2)
-
函数重载注意点三:可变参数,省略参数标签,函数重载一起使用产生二义性时,编译器有可能会报错
func sum(a:Int,b:Int) -> Int{ return a + b } func sum(_ a:Int,_ b:Int) -> Int { return a + b } func sum(_ numbers: Int...)->Int{ var total = 0 for i in numbers { total += I } return total }
调用
sum(1,2)
报错:Ambiguous use of 'sum'
-
-
2.11、内联函数(Inline function)
- 如果开启了编译器优化(Release模式默认会开启优化),编译器会默认将某些函数变成内联函数
- 将函数调用展开成函数体
不会被内联的函数:函数体比较长、包含递归调用、包含动态派发......
- 如果开启了编译器优化(Release模式默认会开启优化),编译器会默认将某些函数变成内联函数
-
2.12、函数类型:每一个函数都是有类型的,函数的类型由:形式参数类型,返回值类型组成
-
例子一:无参数无返回值
func test() {}
-
例子二:有参数有返回值
func test(a: Int, b: Int) -> Int { return a + b }
-
例子三:调用的时候不需要传参数标签
func test(_ a: Int ,_ b: Int) -> Int { return a + b } test(2,3) // 调用的时候不需要传参数标签
-
-
2.13、函数类型作为函数参数
func sum(a: Int, b: Int) -> Int{ return a + b } func difference(a:Int,b:Int) -> Int{ return a - b } func printResult(_ mathFn:(Int,Int)->Int,_ a:Int,_ b:Int){ print("result=\(mathFn(a,b))") } printResult(sum, 5, 2) // 打印结果:result=7 printResult(difference, 5, 2) // 打印结果:result=3
-
2.14、函数类型作为函数返回值
func next(_ input: Int) -> Int { return input + 1 } func previous(_ input: Int) -> Int { return input - 1 } func forward(_ forward: Bool) -> (Int) -> Int { return forward ? next:previous } print(forward(true)(3)) // 结果:4 print(forward(false)(3)) // 结果:2
提示:返回值是函数的函数,叫做高阶函数(Height-Order Function)
-
2.15、typealias: 用来给类型起别名
typealias Byte = Int8 typealias Short = Int16 typealias Long = Int64
如下:
typealias Date = (year:Int,month:Int,day:Int) func test(_ date:Date){ print(date.year,date.month,date.day) } test((2019,6,19))
例二:(重点👂👂👂👂👂👂👂👂👂👂)
typealias IntFn = (Int,Int) -> Int func difference(v1:Int,v2:Int)->Int{ return v1-v2 } let fn:IntFn = difference fn(20,10) func setFn(_ fn:IntFn){} setFn(difference) func getFn() -> IntFn{ return difference }
-
2.16、嵌套函数: 将函数定义在函数的内部
func forward(_ forward:Bool)->(Int)->Int{ func next(_ input:Int)->Int{ return input + 1 } func previous(_ input:Int)->Int{ return input - 1 } return forward ? next:previous } forward(true)(3) // 4 forward(false)(3) // 2
三、枚举
-
3.1、枚举的基本用法
enum Direction { case north case south case east case west } enum Direction { case north,south,east,west }
使用如下:
var dircetion = Direction.north dircetion = Direction.west dircetion = .south print("dircetion=\(dircetion)") let dir = Direction.north switch dir { case .north: print("north") case .south: print("south") case .east: print("east") case .west: print("west") }
-
3.2、关联值(Associated Values)
-
有时会将枚举的成员值跟其他类型的关联存储在一起,会非常有用
enum Score { case points(Int) case grade(Character) } var source = Score.points(96) source = .grade("A") switch source { case let .points(i): print(i,"points") case let .grade(i): print("grade",i) }
打印结果:grade A
-
下面代码必要的时候 let 可以改为 var
enum Date{ case digit(year:Int,month:Int,day:Int) case string(String) } var date = Date.digit(year:2019,month:6,day:21) date = .string("2019-6-21") switch date { case .digit(let year,let month,let day): print(year,month,day) case let .string(value): print(value) }
打印结果:2019-6-21
-
关联值举例
enum Password{ case number(Int,Int,Int,Int) case gesture(String) } var pwd = Password.number(3,5,7,8) pwd = .gesture("12368") switch pwd { case let .number(n1,n2,n3,n4): print("number is \(n1) \(n2) \(n3) \(n4)") case let .gesture(str): print("gesture is",str) }
打印结果:gesture is 12368
-
-
3.3、原始值 (Raw Values):枚举成员可以使用相同类型的默认值预先关联,这个默认值叫做:原始值
enum PlayingCards : Character{ case A = "a" case B = "b" case C = "c" case D = "d" } var suit = PlayingCards.A print(suit) // A print(suit.rawValue) // a print(PlayingCards.D.rawValue) // d enum Grade : String{ case perfect = "A" case great = "B" case good = "C" case bad = "D" } print(Grade.perfect.rawValue) // A print(Grade.great.rawValue) // B print(Grade.good.rawValue) // C print(Grade.bad.rawValue) // D
-
3.4、隐式原始值 (Implicitly Assignd RawValues):如果枚举的原始类型是Int、String 类型,Swift会自动分配原始值
-
String 类型
enum Direction { case north,south,east,west } print(Direction. north) // north print(Direction. north.rawValue) // north
等价于
enum Direction: String { case north = "north" case south = "south" case east = "east" case west = "west" }
-
Int 类型
enum Season:Int{ case spring,summer,autumn,winter } print(Season.spring.rawValue) // 0 print(Season.summer.rawValue) // 1 print(Season.autumn.rawValue) // 2 print(Season.winter.rawValue) // 3
-
Int 类型设置了默认原始值,设置了值之后 Swift会自动递增原始值
enum Season:Int{ case spring = 2,summer,autumn = 5,winter } print(Season.spring.rawValue) // 2 print(Season.summer.rawValue) // 3 print(Season.autumn.rawValue) // 5 print(Season.winter.rawValue) // 6
-
-
3.5、递归枚举 (Recursive Enumeration)
enmu ArithExpr{ case number(Int) indirect case sum(ArithExpr,ArithExpr) indirect case difference(ArithExpr,ArithExpr) } let five = ArithExpr.number(5) let four = ArithExpr.number(4) let two = ArithExpr.number(2) let sum = ArithExpr.sum(five,four) let difference = ArithExpr.difference(sum,two) func calculate(_ expr:ArithExpr) -> Int{ switch expr { case let .number(value): return value case let .sum(left,right): return calculate(left) + calculate(right) case let .difference(left,right): return calculate(left) - calculate(right) } } calculate(difference)
提示:使用
indirect
关键字修饰的枚举值表示这个枚举是可以递归的,即此枚举值中的相关值使用其枚举类型本身。 -
3.7、MemoryLayout: 获取数据类型占用的内存大小
enum Password{ case number(Int,Int,Int,Int) // 32 字节 case other // 1 个字节就搞定了 } MemoryLayout<Password>.stride // 40 分配占用的内存空间大小 MemoryLayout<Password>.size // 33 实际用到的空间大小 MemoryLayout<Password>.alignment // 8 对齐参数 var password = Password.number(2,3,4,5) password = .other MemoryLayout.stride(ofValue:password) // 40 MemoryLayout.size(ofValue:password) // 33 MemoryLayout.alignment(ofValue:password) // 8
- 提示1:stride:范围 与 size 的区别
size: 分配占用的内存空间大小
stride: 实际用到的空间大小
内存对齐是 8个字节 ,所以上面是 32+1 = 40 - 提示2:N个字节存储关联值(N取内存最大的关联值),比如number(Int,Int,Int,Int)就是 4*8=32 里面的元素是关联值,1个字节用来存储成员值(也就是case的个数)
- 提示1:stride:范围 与 size 的区别
-
3.8、枚举拓展
-
拓展一:枚举的关联值(3.2) 和 默认原始值(3.3) 的区别
分析:枚举的关联值 的值 是写到枚举的内存中的,而枚举的原始值是固定死的,没有写入枚举的内存中
举例如下:enum Season:Int{ case spring,summer,autumn,winter } MemoryLayout<Season>.stride // 1 MemoryLayout<Season>.size // 1 MemoryLayout<Season>.alignment // 1
-
拓展二:思考下面枚举变量的内存布局
enum TestNum { case test1,test2,test3,test4 } print( MemoryLayout<TestNum>.stride) // 1 分配占用的内存空间大小 print(MemoryLayout<TestNum>.size) // 1 实际用到的空间大小 print(MemoryLayout<TestNum>.alignment) // 1 对齐参数
-
提示1:直接查看内存布局
-
提示2:我们还可以在Debug -> Debug Workflow -> View Memory 手动输入内存地址来查看内存布局,如下
分析: 用来窥探Swift内存的小工具
var t = TestNum.test1 print( Mems.ptr(ofVal: &t)) t = .test2 t = .test3 t = .test4
- 上面是采取一个字节来存储枚举变量的数据,一个字节 00 用来存储 test1、一个字节 01 用来存储 test2、一个字节 02 用来存储 test3、一个字节 03 用来存储 test4,其实也就是来区分成员变量的值
- 按照上面的一个字节来区分的话,一个字节的范围是 0x00~0xFF,最大也就是 256,每一个case也就是一个成员变量,其实
case test1,test2,test3,test4
相当于四个case
-
-
拓展三:看下面一个复杂的枚举
enum TestNum { case test1(Int,Int,Int) case test2(Int,Int) case test3(Int) case test4(Bool) case test5 } print(MemoryLayout<TestNum>.size) // 25分配的内存大小 print(MemoryLayout<TestNum>.stride) // 32 实际使用的内存大小 print(MemoryLayout<TestNum>.alignment) // 8 内存对齐的字节数
分析:分配内存的规则:如果有多个case,那么必有一个字节存储来区分成员变量case,再看case关联值最多的那个有多少个关联值,N个字节存储关联值(N取内存最大的关联值),比如number(Int,Int,Int)就是 3*8=24 里面的元素是关联值,1个字节用来存储成员值(也就是case的个数),那么分配的内存是 32,实际用了 24+1= 25
-
拓展四:看下面一个有意的的枚举
enum TestNum { case test1(Int,Int,Bool,Bool) case test2(Int,Int) } print(MemoryLayout<TestNum>.size) // 实际使用的内存大小 18 print(MemoryLayout<TestNum>.stride) // 分配的内存大小 24 print(MemoryLayout<TestNum>.alignment) // 内存对齐的字节数 8
分析:8+8+最后一个Bool【1(第一个bool)+1(区别成员变量) 】= 17 这是编译器的进一步优化,它把case标志放在最后一个Bool字节里去了,相当于最后一个Bool,它的那个字节里面包含了case成员信息。因为Bool类型只有2种取值,true是1,false是0,只会用到1个二进制位,所以Bool的1个字节还是有多余空间的,如果是Int这些,就有可能要把它自己的8个字节都用完。所以无法拿来放case,比如0xFF FF FF FF FF FF FF FF,占满了8字节,而Bool,你的8个二进制位,不管存储true还是false,都只需要用到1位
-
拓展五:没有关联值
enum TestNum { case test1,test2,test3,test4 } print(MemoryLayout<TestNum>.size) // 实际使用的内存大小 1 print(MemoryLayout<TestNum>.stride) // 分配的内存大小 1 print(MemoryLayout<TestNum>.alignment) // 内存对齐的字节数 1
分析:这里的 1 仅仅是为了区分 成员变量使用的
-