Swift 进阶(二)函数、可选项

646 阅读10分钟

函数

函数的定义

有返回值的函数

形参默认是let,也只能是let

func sum(v1: Int, v2: Int) -> Int { 
    return v1 + v2 
}

无返回值的函数

本质返回值的就是一个空元组

// 三种写法相同
func sayHello() -> Void {
    
}

func sayHello() -> () {
    
}

func sayHello() {
    
}

隐式返回

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

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

sum(v1: 10, v2: 20)

返回元组,实现多返回值

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)
print(result.sum, result.difference, result.average)

函数的文档注释

可以通过一定格式书写注释,方便阅读

/// 求和【概述】
///
/// 将2个整数相加【更详细的描述】
///
/// - Parameter v1: 第1个整数
/// - Parameter v2: 第2个整数
/// - Returns: 2个整数的和
///
/// - Note:传入2个整数即可【批注】
///
func sum(v1: Int, v2: Int) -> Int {
    v1 + v2
}

-w592

详细参照Apple官方的api设计准则

参数标签(Argument Label)

可以修改参数标签

func gotoWork(at time: String) {

}

gotoWork(at: "8:00")

可以省略参数标签,为了阅读性一般不建议省略

func sum(_ value1: Int, _ value2: Int) -> Int {
	value1 + value2
}

sum(5, 5)

默认参数值(Default Parameter Value)

参数可以有默认值

func check(name: String = "nobody", age: Int, job: String = "none") {
    print("name=\(name), age=\(age), job=\(job)")
}

check(name: "Jack", age: 20, job: "Doctor")
check(name: "Jack", age: 20)
check(age: 20, job: "Doctor")
check(age: 20)

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

但是在省略参数标签时,需要特别注意,避免出错

// 这里的middle不可以省略参数标签
func test(_ first: Int = 10, middle: Int, _ last: Int = 30) { }
test(middle: 20)

可变参数(Variadic Parameter)

一个函数最多只能有一个可变参数

func sum(_ numbers: Int...) -> Int {
	numbers.count
}

sum(1, 2, 3, 4)

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

// 参数string不能省略标签
func get(_ number: Int..., string: String, _ other: String) { }
get(10, 20, string: "Jack", "Rose")

我们可以参考下Swift自带的print函数

-w828

print(1, 2, 3, 4, 5)
print(1, 2, 3, 4, 5, separator: " ", terminator: "\n")

输入输出参数(In-Out Parameter)

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

func swapValues(_ v1: inout Int, _ v2: inout Int) {
	let tmp = v1
	v1 = v2
	v2 = tmp
}

var num1 = 10
var num2 = 20
swapValues(&num1, &num2)

官方自带swap的交换函数就是使用的inout

-w674

可以利用元组来进行参数交换

func swapValues(_ v1: inout Int, _ v2: inout Int) {
	(v1, v2) = (v2, v1)
}

var num1 = 10
var num2 = 20
swapValues(&num1, &num2)

可变参数不能标记为inout

-w708

inout参数不能有默认值

-w704

inout参数只能传入可以被多次赋值的

常量只能在定义的时候赋值一次,所以下面会报错

-w712

inout参数的本质是地址传递

我们新建个项目,通过反汇编来观察其本质

-w671

leaq表示的就是地址传递,可以看出在调用函数之前先将两个变量的地址放到了寄存器中

-w1119

函数重载(Function Overload)

函数重载的规则

  • 函数名相同
  • 参数个数不同 || 参数类型不同 || 参数标签不同
func sum(value1: Int, value2: Int) -> Int { }

// 参数个数不同
func sum(_ value1: Int, _ value2: Int, _ value3: Int) -> Int { }

// 参数标签不同
func sum(_ a: Int, _ b: Int) -> Int { }

// 参数类型不同
func sum(_ a: Double, _ b: Double) -> Int { }

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

-w711

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

// 不建议的写法
func sum(_ value1: Int, _ value2: Int, _ value3: Int = 5) -> Int { v1 + v2 + v3 }
func sum(_ value1: Int, _ value2: Int) -> Int { v1 + v2 }

// 会调用第二个函数
sum(10, 2)

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

-w723

内联函数(Inline Function)

如果开启了编译器优化(Release模式默认会开启优化),编译器会自动将某些函数变成内联函数

-w829

我们分别来观察下更改Debug模式下的优化选项,编译器做了什么

1.我们新建一个项目,项目代码如下

-w551

2.然后我们先通过反汇编观察没有被优化时的编译器做了什么

-w1059

可以看到会调用test函数,然后test函数里面再执行打印

-w1051

3.现在我们开启Debug模型下的优化选项,然后运行程序

-w619

发现print打印直接就在main函数里执行了,没有了test函数的调用过程

相当于print函数直接放到了main函数中,编译器会将函数调用展开成函数体

-w1061

哪些函数不会被内联

  • 函数体比较长
  • 包含递归调用
  • 包含动态派发(运行时的多态调用)

我们可以使用@inline关键字,来主动控制编译器是否做进行优化

@inline(nerver):永远不会被内联,即使开启了编译器优化

@inline(nerver) func test() {}

@inline(__alaways):开启编译器优化后,即使代码很长,也会被内联(递归调用和动态派发除外)

@inline(__alaways) func test() {}

Release模式下,编译器已经开启优化,会自动决定哪些函数需要内联,因此没必要使用@inline

函数类型(Function Type)

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

// () -> Void 或 () -> ()
func test() {} 

// (Int, Int) -> Int
func sum(a: Int, b: Int) -> Int {} 

// 定义变量
var fn: (Int, Int) -> Int = sum
fn(5, 3) // 调用时不需要参数标签

函数类型作为函数参数

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

func difference(v1: Int, v2: Int) -> Int {
	v1 - v2
}

func printResult(_ mathFn: (Int, Int) -> Int, _ a: Int, _ b: Int) {
	mathFn(a, b)
}

printResult(sum, 5, 2)
printResult(difference, 5, 2)

函数类型作为函数返回值

func next(_ input: Int) -> Int {
	input + 1
}

func previous(_ input: Int) -> Int {
	input - 1
}

func forward(_ forward: Bool) -> (Int) -> Int {
	forward ? next : previous
}

forward(true)(3)
forward(false)(3)

返回值是函数类型的函数叫做高阶函数

typealias

用来给类型起别名

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

typealias Date = (year: String, mouth: String, day: String)
func getDate(_ date: Date) {
    print(date.day)
    print(date.0)
}

getDate(("2011", "9", "10"))


typealias IntFn = (Int, Int) -> Int

func difference(v1: Int, v2: Int) -> Int {
    v1 - v2
}

let fn: IntFn = difference
fn(20, 10)

func setFn(_ fn: IntFn) { }
setFn(difference)

func getFn() -> IntFn { difference }

按照Swift标准库的定义,Void就是空元组()

-w314

嵌套函数

将函数定义在函数内部

func forward(_ forward: Bool) -> (Int) -> Int {
	func next(_ input: Int) -> Int {
		input + 1
	}
	
	func previous(_ input: Int) -> Int {
		input - 1
	}

	forward ? next : previous
}

forward(true)(3)
forward(false)(3)

可选项(Optional)

可选项,一般也叫可选类型,它允许将值设置为nil

在类型名称后面加个问号?来定义一个可选项

var name: String? = nil

如果可选类型定义的时候没有给定值,默认值就是nil

var age: Int?

等价于
var age: Int? = nil

如果可选类型定义的时候赋值了,那么就是一个Optional类型的值

var name: String? = "Jack" // Optional(Jack)

可选类型也可以作为函数返回值使用

var array = [1, 2, 3, 4]

func get(_ index: Int) -> Int? {
    if index < 0 || index >= array.count {
        return nil
    }
    
    return array[index]
}

强制解包(Forced Unwrapping)

可选项是对其他类型的一层包装,可以理解为一个盒子

  • 如果为nil,那么它就是个空盒子
  • 如果不为nil,那么盒子里装的是:被包装类型的数据
var age: Int?
age = 10
age = nil

可选关系的类型大致如下图

-w606

如果要从可选项中取出被包装的数据(将盒子里装的东西取出来),需要使用感叹号!进行强制解包

var age: Int? = 10
var ageInt = age!
ageInt += 10 // ageInt为Int类型

如果对值为nil的可选项(空盒子)进行强制解包,将会产生运行时错误

-w668

可选项绑定(Optional Binding)

我们可以判断可选项是否包含值

let number = Int("123") // number为Int?

if number != nil {
    print(number!)
}

还可以使用可选项绑定来判断可选项是否包含值

如果包含就自动解包,把值赋给一个临时的常量(let)或者变量(var),并返回true,否则返回false

if let number = Int("123") {
	print("字符串转换整数成功:\(number)")
	// number是强制解包之后的Int值
	// number作用域仅限于这个大括号
} else {
	print("字符串转换整数失败")
}

如果判断条件有多个,可以合并在一起,用逗号,来分隔开

if let first = Int("4") {
	if let second = Int("42") {
		if first < second && second < 100 {
			print("\(first) < \(second) < 100") 
		} 
	} 
}

等于

if let first = Int("4"),
let second = Int("42"),
	first < second && second < 100 {
		print("\(first) < \(second) < 100")
}

while循环中使用可选项绑定

let strs = ["10", "20", "abc", "-20", "30"]

var index = 0
var sum = 0
while let num = Int(strs[index]), 
		num > 0 {
		sum += num
		index += 1
}

空合并运算符(Nil-Coalescing Operator)

我们可以使用空合并运算符??来对前一个值是否有值进行判断,如果前一个值为nil,就会返回后一个值

-w860 -w871

详细用法如下

a ?? b

a是可选项
b是可选项或者不是可选项
ba的存储类型必须相同
如果a不为nil,就返回a
如果a为nil,就返回b
如果b不是可选项,返回a时会自动解包

结果的类型取决于??后面的值类型是什么

let a: Int? = 1
let b: Int = 2
let c = a ?? b // c是Int , 1 

let a: Int? = nil
let b: Int = 2
let c = a ?? b // c是Int , 2

多个??一起使用

let a: Int? = 1
let b: Int? = 2
let c = a ?? b ?? 3 

let a: Int? = nil
let b: Int? = 2
let c = a ?? b ?? 3
var a: Int??? = 10
var b: Int = 20
var c: Int? = 30

print(a ?? b) // Optional(Optional(10))
print(a ?? c) // Optional(Optional(10))

??if let配合使用

let a: Int? = nil
let b: Int? = 2
if let c = a ?? b {
	print(c)
}

if let c = a, let d = b {
	print(c)
	print(d)
}

guard语句

guard语句的条件为false时,就会执行大括号里面的代码

guard语句的条件为true时,就会跳过guard语句

guard语句适合用来“提前退出”

guard 条件 else {
    // do something....
    退出当前作用域
    // return
}

当使用guard语句进行可选项绑定时,绑定的常量(let)、变量(var)也能在外层作用域中使用

func login(_ info: [String : String]) {
	guard let username = info["username"] else {
		print("请输入用户名")
		return
	}
	
	guard let password = info["password"] else {
		print("请输入密码")
		return
	}
	
	// if username ....
	// if password ....
	print("用户名:\(username)", "密码:\(password)", "登录ing")
}

隐式解包(Implicitly Unwrapped Optional)

在某些情况下,可选项一旦被设定值之后,就会一直拥有值

在这种情况下,可以去掉检查,也不必每次访问的时候都进行解包,因为他能确定每次访问的时候都有值

可以在类型后面加个感叹号!定义一个隐式解包的可选项

let num1: Int! = 10
let num2: Int = num1

if num1 != nil {
    print(num1 + 6)
}

if let num3 = num1 {
    print(num3)
}

如果对空值的可选项进行隐式解包,也会报错

-w687

用隐式解包的可选项类型,大多数是希望别人要给定一个不为空的值,如果别人传的是个空值那就报错,目的就是制定你的规则,更多适用于做一个接口来接收参数;

更多还是建议不使用该类型

字符串插值

可选项在字符串插值或者直接打印时,编译器会发出警告

-w708

至少有三种方法消除警告

var age: Int? = 10

print("My age is \(age!)") // My age is 10
print("My age is \(String(describing: age))") // My age is Optional(10)
print("My age is \(age ?? 0)") // My age is 10

多重可选项

1.看下面几个可选类型,可以用以下图片来解析

var num1: Int? = 10
var num2: Int?? = num1
var num3: Int?? = 10

print(num2 == num3) // true

-w787

可使用lldb指令frame variable -R或者fr v -R查看区别

-w1124

2.看下面几个可选类型,可以用以下图片来解析

var num1: Int? = nil
var num2: Int?? = num1
var num3: Int?? = nil

print(num2 == num3) // false
print(num3 == num1) // false(因为类型不同)

(num2 ?? 1) ?? 2 // 2
(num3 ?? 1) ?? 2 // 1

-w784

不管是多少层可选项,一旦赋值为nil,就只有最外层一个大盒子

可使用lldb指令frame variable -R或者fr v -R查看区别

-w1126