函数
函数的定义
有返回值的函数
形参默认是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
}
参数标签(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函数
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
可以利用元组来进行参数交换
func swapValues(_ v1: inout Int, _ v2: inout Int) {
(v1, v2) = (v2, v1)
}
var num1 = 10
var num2 = 20
swapValues(&num1, &num2)
可变参数不能标记为inout
inout
参数不能有默认值
inout
参数只能传入可以被多次赋值的
常量只能在定义的时候赋值一次,所以下面会报错
inout
参数的本质是地址传递
我们新建个项目,通过反汇编来观察其本质
leaq
表示的就是地址传递,可以看出在调用函数之前先将两个变量的地址放到了寄存器中
函数重载(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 { }
返回值类型和函数重载无关
默认参数值和函数重载一起使用产生二义性时,编译器并不会报错(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)
可变参数、省略参数标签、函数重载一起使用产生二义性时,编译器有可能会报错
内联函数(Inline Function)
如果开启了编译器优化(Release模式
默认会开启优化),编译器会自动将某些函数变成内联函数
我们分别来观察下更改Debug模式下的优化选项,编译器做了什么
1.我们新建一个项目,项目代码如下
2.然后我们先通过反汇编观察没有被优化时的编译器做了什么
可以看到会调用test函数
,然后test函数
里面再执行打印
3.现在我们开启Debug模型下的优化选项,然后运行程序
发现print
打印直接就在main函数
里执行了,没有了test函数
的调用过程
相当于print函数
直接放到了main函数
中,编译器会将函数调用展开成函数体
哪些函数不会被内联
- 函数体比较长
- 包含递归调用
- 包含动态派发(运行时的多态调用)
我们可以使用@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
就是空元组()
嵌套函数
将函数定义在函数内部
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
可选关系的类型大致如下图
如果要从可选项中取出被包装的数据(将盒子里装的东西取出来),需要使用感叹号!
进行强制解包
var age: Int? = 10
var ageInt = age!
ageInt += 10 // ageInt为Int类型
如果对值为nil
的可选项(空盒子)进行强制解包,将会产生运行时错误
可选项绑定(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
,就会返回后一个值
详细用法如下
a ?? b
a是可选项
b是可选项或者不是可选项
b跟a的存储类型必须相同
如果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)
}
如果对空值的可选项进行隐式解包,也会报错
用隐式解包的可选项类型,大多数是希望别人要给定一个不为空的值,如果别人传的是个空值那就报错,目的就是制定你的规则,更多适用于做一个接口来接收参数;
更多还是建议不使用该类型
字符串插值
可选项在字符串插值或者直接打印时,编译器会发出警告
至少有三种方法消除警告
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
可使用lldb
指令frame variable -R
或者fr v -R
查看区别
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
不管是多少层可选项,一旦赋值为nil
,就只有最外层一个大盒子
可使用lldb
指令frame variable -R
或者fr v -R
查看区别