swift-流程控制、函数

Posted by sunzhongliang on October 21, 2019

流程控制

if

if后面的条件可以省略小括号,条件只能是Bool类型

let age = 20
if age > 20 {
    print("20多岁了")
}
else if age < 10 {
    print("小孩子")
}
else {
    print("年轻人")
}

// 以下写法将不会编译通过(if 条件只能是Bool类型)
if age {

}

while

repeat-while相当于C语言的do while

// while 循环
var num = 5
while num > 0 {
    print("num is \(num)")
    num -= 1
}

// repeat while 循环
var num2 = -1
repeat {
    print("num is \(num2)")
} while num2 > 0

for

for循环使用区间运算符
闭区间运算符:比如0...3代表大于等于0小于等于3

let names = ["张三", "李四", "思聪", "尼古拉斯"]
for i in 0...3 {
    print(names[i])
}

// 也可以将区间运算符定义为常量
let names = ["张三", "李四", "思聪", "尼古拉斯"]
let range = 0...3
for i in range {
    print(names[i])
}

// 也可以区间运算符使用某一个变量\常量
let index = 0
let names = ["张三", "李四", "思聪", "尼古拉斯"]
for i in index...3 {
    print(names[i])
}

// i默认是let,也可以声明为var变量
for var i in 1...3 {
    i+=5
    print(i)
}

// 如果循环语句当中没有用到i,可以写成_ 表示忽略循环变量
for _ in 1...3 {
    print("循环了")
}

半开区间运算符:比如a..<b 代表 大于等于a小于b

// 代表 大于等于1小于3
for i in 1..<3 {
    print("循环了")
}

条件过滤
区间运算符后边可以用wherefilter关键字来控制运算符的逻辑

// 使用where完成条件过滤
for i in 0..<10 where i%2==0 {
    print(i) //输出结果为1-10之间的偶数
}

// 使用filter完成条件过滤
for i in (1...10).filter({ i in i % 2 == 0}) {
    print(i) // 输出结果为1-10之间的偶数
}

反向遍历
使用reverse关键字让遍历条件反转

for i in (0...10).reverse(){
    print(i)    // 打印结果为10-0
}

遍历元素以及索引
使用enumerate关键字可以获得遍历的索引以及遍历的元素

for (index, i) in (1...10).enumerate(){
    print(index)// 遍历索引
    print(i) // 遍历元素
}

区间运算符应用在数组上:

// 相当于OC当中的for (NSString *name in names)
let names = ["张三", "李四", "思聪", "尼古拉斯"]
for name in names[0...3] {
    print(name)
}

单侧区间:让区间向一个方向尽可能的远

// 下面这种写法代表从数组的第二个元素开始,一直到数组的最大长度开始遍历
let names = ["张三", "李四", "思聪", "尼古拉斯"]
for name in names[1...] {
    print(name)
}

// 下面这种写法代表从数组的第1个元素开始,一直到数组的第三个元素开始遍历
let names = ["张三", "李四", "思聪", "尼古拉斯"]
for name in names[...2] {
    print(name)
}

// 半开区间,小于数组的第三个元素
let names = ["张三", "李四", "思聪", "尼古拉斯"]
for name in names[..<2] {
    print(name)
}

// 无穷区间(代表负无穷到小于等于5)
let range = ...5
range.contains(-1) // true
range.contains(6)  // false

区间类型

区间类型分为三种
1.ClosedRange
2.Range
3.PartialRangeThrough

ClosedRange

当区间有开闭区间时,类型为ClosedRange
let range1: ClosedRange<Int> = 1...3

Range

当区间有半开区间时,类型为Range
let range2: Range<Int> = 1..<3

PartialRangeThrough

当区间只有单侧区间时,类型为PartialRangeThrough, 如:
let range3: PartialRangeThrough<Int> = ...5

字符、字符串也能使用区间运算符,但默认不能用在for-in中,如:

// 字符串使用区间运算符(这种写法表示取值范围为:cc,cd,ce,cf....fe,ff)
let stringRange1 = "cc...ff" // 类型为ClosedRange<String>
stringRange1.contains("cb") // false
stringRange1.contains("dz")  // true 

// 也可以这样写
let stringRange2 = "a...f"
stringRange2.contains("c")  // true
stringRange2.contains("z") // flase

// 声明区间类型为CloseRange<Character>, \0到~囊括了所有ASCII字符
let characterRange: CloseRange<Character> = "\0"..."~"
characterRange.contains("G") // true

带间隔的区间值

let hours = 11
let hourInterval = 2
// tickMark的取值:从4开始,累加2,不超过11
for tickMark in stride(from: 4, through: hours, by: hourInterval) {
    print(tickMark) // 输出 4 6 8 10
}

switch

case、default后面不能写大括号{}, 也可以不用写break(并不会贯穿到后面的条件)如:

var number = 1
switch number {
case 1:
    print("number = 1")
    break; // 也可以不用写break,不会贯穿到后面的条件执行
case 2:
    print("number = 2")
    break; // 也可以不用写break,不会贯穿到后面的条件执行
default:
    print("number is other")
    break; // 也可以不用写break,不会贯穿到后面的条件执行
}
// 打印结果:number = 1

fallthrough可以实现贯穿效果

var number = 1
switch number {
case 1:
    print("number = 1")
    fallthrough
case 2:
    print("number = 2")
default:
    print("number is other")
}
// 打印结果number = 1  number = 2

switch语句必须要保证能处理所有情况,没有保证能够处理所有情况就会编译失败: 处理方法:casedefault后面至少要有一条语句;如果不想做任何事情,加一个break即可

var number = 1
switch number {
case 1:
    print("number = 1")
case 2:
    print("number = 2")
default:
    break
}

如果能够保证处理所有情况(比如枚举值),就不必使用default,如:

enum answer {case wrong, right}
let result = answer.right
switch result {
case answer.wrong:
    print("你的答案错误了")
case answer.right:
    print("正确")
}

switch 也支持CharacterString类型, 如:

let name = "jack"
switch name {
case "jack":
    print("jack")
case "robin":
    print("robin")
default:
    break
}

// 也可以这样写,等同于贯穿执行
let name = "jack"
switch name {
case "jack", "robin":
    print("jack,robin")
default:
    break
}

区间匹配、元组匹配

switch 支持区间匹配:

let count = 62
switch count {
case 0:
    print("0")
case 1..<5:
    print("a few")
case 10..<100:
    print("dozens of")
case 100..<1000:
    print("hundreds of")
default:
    break
}

元组匹配

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 the box")
default:
    print("outside")
    break
}

值绑定

可以当某些条件满足时,可以用来接收另外一些值

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 a y value of \(y)")
case let(x, y):
    print("somewhere else at \(x), \(y)")
}
// 输出:on the x-axis with an x value of 2

where

当使用值绑定时,where可以用来指定一些条件

let point = (1, -1)
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("just some arbitrary point")
}
// 输出:on the line x==-y

where不仅仅可以用在switch里面,还可以用在for循环里面; 当使用where时会筛选符合条件的对象

var numbers = [10,20,-10,-20,30,-39]
var sum = 0
for num in numbers where num > 0 {
    sum += num
}
print(sum) // 输出:60

标签语句

标签语句:可以在某个条件下控制某个条件

outer: for i in 1...4 {
    for k in 1...4 {
        if k == 3 {
            // 当continue在内层循环下,默认情况下控制的就是内层循环
            // 外层循环定义标签,当执行内层循环到达条件k==3时,就控制外层循环
            continue outer
        }
        if i == 3 {
            break outer
        }
        print("i == \(i), k == \(k)")
    }
}
// 输出
i == 1, k == 1
i == 1, k == 2
i == 2, k == 1
i == 2, k == 2

函数

函数定义

函数使用func来定义

// 方法名:pi 参数:无 返回值类型:double
func pi() -> Double {
    return 3.14
}

// 方法名:sum 参数:两个int类型 返回值类型:int
func sum(v1: Int, v2: Int) -> Int {
    return v1+v2
}

// 调用方式
sum(v1: 10, v2: 20)

方法的形参默认是let类型,也只能是let类型

无返回值的方法定义:

func sayHello() ->Void {
    print("hello")
}
func sayHello2() ->() {
    print("hello")
}
func sayHello3() {
    print("hello")
}

隐式返回

如果整个函数是一个单一表达式,那么函数会隐式返回这个表达式

func sum(v1: Int, v2: Int) -> Int {
    v1 + v2
}
// 调用函数
sum(v1: 10, v2: 20)

返回值:元组

函数支持返回元组类型,可以实现返回多个值

// 定义函数返回元组,实现计算和、差、平均值
func calculate(v1: Int, v2: Int) -> (sum: Int, differenct: Int, average: Int) {
    let sum = v1 + v2
    return (sum, v1 - v2, sum >> 1)  // 参数相加的和,右移一位(平均值)
}

let result = calculate(v1: 10, v2: 20)
result.sum
result.differenct
result.average

参数标签

可以修改参数标签

func goToWork(at time: String) {
    // 使用参数时,用time
    print("在\(time)上班了")
}
// 调用方法时,用at
goToWork(at: "9:00")
// 输出:在9:00上班了

忽略参数标签
可以省略调用方法时的参数标签

func sum(_ v1: Int, _ v2: Int) -> Int {
    v1 + v2
}
sum(10, 20)

默认参数值

函数支持默认值

func check(name: String = "nobody", age: Int, job: String = "none") {
    print("name = \(name), age = \(age), job = \(job)")
}
check(name: "jack", age: 22, job: "docter")
check(name: "rose", age: 18)
check(age: 18, job: "batman")
check(age: 19)
// 输出:
name = jack, age = 22, job = docter
name = rose, age = 18, job = none
name = nobody, age = 18, job = batman
name = nobody, age = 19, job = none

C++的默认参数值有个限制:必须从右往左设置。由于Swift拥有参数标签,因此并没有此限制

可变参数

可变参数可用…来表示, 一个函数最多只有一个可变参数

func sum(_ numbers: Int...) ->Int {
    var total = 0
    for number in numbers {
        total += number
    }
    return total
}

sum(10,20,30,40)  // 100

紧跟在可变参数后面的参数不能省略参数标签

// 可变参数后面的参数不能省略参数标签
func test(_ numbers: Int..., string: String, _other: String) {
    
}
// 调用
test(10,20,30, string: "jack", _other: "Rose")

输入输出参数

inout定义一个输入输出参数:可以在函数内部修改外部实参的值

var number = 10
func add(_ num: inout Int) {
    num += 1
}

add(&number)

print(number) // 输出:11

可变参数不能使用输入输出参数
可变参数的本质是地址传递
可变参数只能传入可以被多次赋值的(比如外部实参不能使用let定义)

函数重载

  1. 函数名相同
  2. 参数个数不同 或者 参数类型不同 或者 参数标签不同
// sum函数有重载------(参数个数不同)
func sum(v1: Int, v2: Int) -> Int {
    v1 + v2
}
func sum(v1: Int, v2: Int, v3: Int) -> Int {
    v1 + v2 + v3
}

// sum2函数有重载-------(参数类型不同)
func sum2(v1: Int, v2: Double) -> Int {
    Double(v1) + v2
}
func sum2(v1: Double, v2: Int) -> Int {
    v1 + Double(v2)
}

// sum3函数有重载-------(参数标签不同)
func sum3(_ v1: Int, _ v2: Int) -> Int {
    v1 + v2
}
func sum3(a: Int, b: Int) -> Int {
    a + b
}

返回值类型与函数重载无关

默认参数值和函数重载一起使用产生二义性时,编译器并不会报错(C++中会报错)

// 函数名相同,参数个数相同,参数标签相同,参数类型相同
func add(v1: Int, v2: Int) -> Int {
    return v1 + v2
}
func add(v1: Int, v2: Int, v3: Int = 10) -> Int {
    return v1 + v2 + v3
}
// 会调用add(v1: Int, v2: Int)
add(v1: 10, v2: 20)

可变参数、省略参数标签、函数重载一起使用产生二义性时,编译器有可能会报错

函数类型

每一个函数都是有类型的,函数类型有形式参数类型返回值类型组成

func sum(a: Int, b: Int) -> Int {
    return a + b
}
// 定义变量
var fn: (Int, Int) -> Int = sum
print(fn(2,3))  // 输出:5

函数类型可以作为函数参数

func sum(a: Int, b: Int) -> Int {
    return a + b
}
func difference(v1: Int, v2: Int) -> Int {
    return v1 - v2
}
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

函数类型作为函数返回值

func next(_ input: Int) -> Int {
    return input + 1
}
func previous(_ input: Int) -> Int {
    return input - 1
}
// forward 函数的第一个->是返回值,后面的参数是一个函数代表接收一Int类型的参数返回Int类型
func forward(_ forward: Bool) -> (Int) -> Int {
    // 如果参数为true,就调用next方法,否则调用previous方法
    return forward ? next : previous
}
print(forward(true)(3))  // 4
print(forward(false)(3)) // 2

typealias

typealias 用来给类型起别名

typealias Byte = Int8
typealias Short = Int16
typealias Long = Int64

// 给元组起别名
typealias Date = (year: Int, month: Int, day: Int)
func test(_ date: Date) {
    print(date.0)
    print(date.year)
}
test((2011, 9, 11)) // 输出2011 2011

// 给方法起别名
typealias IntFn = (Int, Int) -> Int
func difference(v1: Int, v2: Int) -> Int {
    return v1 - v2
}
let fn: IntFn = difference
print(fn(20,10)) // 输出:10

嵌套函数

将函数定义在函数内部的称为嵌套函数

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
}

print(forward(true)(3)) // 4
print(forward(false)(3)) // 2

内联函数

如果开启了编译器优化(Release模式默认情况下会开启优化),编译器会自动将某些函数转为内联函数调用

  1. 内联函数其实就是将函数展开成函数体
  2. 内联函数解决函数调用的效率问题
  3. 函数之间调用,是内存地址之间的调用,当函数调用完毕之后还会返回原来函数执行的地址。函数调用有时间开销,内联函数就是为了解决这一问题。

内联函数可以解决程序中函数调用的效率问题,通过编译器的预处理,在调用内联函数的地方将内联函数内的语句copy到调用函数的地方,减少一些不必要的开销,但会导致主函数指令增多、函数体积增大等情况。

// 永远不会被内联,即使编译器开启了优化
@inline(never) func test() {
    print("test")
}

// 开启编译器优化后,即使代码很长,也会被内联(递归调用函数、动态派发函数除外)
@inline(__always) func test2() {
    print("test2")
}

以下几种函数不会自动被内联:
1.函数体比较长
2.包含递归调用
3.包含动态派发等等

内联函数只是我们向编译器提供的申请,编译器不一定采取inline形式调用函数.
内联函数不能承载大量的代码.如果内联函数的函数体过大,编译器会自动放弃内联.
内联函数内不允许使用循环语句或开关语句.

函数的注释

快捷键:option+command+shift

本文首次发布于 孙忠良 Blog, 作者 [@sunzhongliang] , 转载请保留原文链接.