swift学习笔记 ② —— 函数

Swift学习笔记 - 文集

语法篇

一、函数

函数定义

Swift 定义函数使用关键字 func,function的简写。-> 后定义函数的返回值类型。

语法

func funcname(形参) -> returntype
{
   Statement1
   Statement2
   ……
   Statement N
   return parameters
}

例如下面我们定义了一个函数名为 getName 的函数,形参的数据类型为 String,返回值也为 String

func getName(name: String) -> String {
    return ("my name is \(name)")
}
print(getName(name: "Mars")) //打印 my name is Mars

无参数的函数

我们可以创建不带参数的函数。

func sitename() -> String {
    return "swift学习笔记"
}
print(sitename()) // 打印 swift学习笔记

无返回值的函数

无返回值的函数可以有以下几种写法:

  • 返回值类型用 Void 代替:
func sayHello() -> Void {
    print("Hello")
}
  • 返回值类型用空元祖 () 代替。元组可以作为函数返回值,后面会讲到:
func sayBayBay() -> () {
    print("BayBay")
}
  • 直接省略返回值类型:
func sayThanks(){
    print("Thanks")
}

函数的隐式返回

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

func sum(a1: Int, a2: Int) -> Int {
    a1 + a2
}
sum(a1: 10, a2: 20) // 30

例子中的函数体是一个表达式 a1 + a2,没有写 return,调用函数也会得到表达式的结果。

元组作为函数返回值

用元组(tuple)类型让多个值作为一个复合值从函数中返回。例如下面求两个数的和跟差:

func calculate(num1: Int, num2: Int) -> (sum: Int, difference: Int) {
    let sum = num1 + num2
    let difference = num1 - num2
    return (sum, difference)
}
let result = calculate(num1: 30, num2: 10)
print(result.sum) // 打印 40
print(result.difference) // 打印 20

例子中 calculate 函数是用来求两个数的和跟差,我们将函数返回值定义为元组类型(元组Tuple类型我们在swift学习笔记①一文中有介绍,不了解的童鞋可以移步阅读)。我们可以通过获取元组中的值就能分别得到我们想要的结果。

二、函数参数标签

函数的参数都有一个外部参数名和一个局部参数名。

局部参数名

局部参数名在函数的实现内部使用。

func sample(number: Int) {
   println(number)
}
sample(number: 1)
sample(number: 2)
sample(number: 3)

例子中 number 为局部参数名,只能在函数体内使用。

外部参数名

我们可以在局部参数名前指定外部参数名,中间用空格分隔,外部参数名用于在函数调用时传递给函数的参数。例如:

func goToWork(at time: String) {
    print("work time is \(time)")
}
goToWork(at: "9:00")

例子中 at 就是外部参数名,在函数调用时使用。

也可以使用 _来省略函数的参数标签:

func sum(_ a1: Int, _ a2: Int) -> Int {
    a1 + a2
}
sum(10, 20)

函数默认参数值

Swift中函数允许有默认参数值,在定义函数时设置默认参数值,当调用函数时可以不用传入参数:

func check(name: String = "nobody", age: Int, job: String = "none") {
    print("name is \(name), age is \(age), job is \(job)")
}
check(name: "Mars", age: 18, job: "student")
check(name: "Tom", age: 20)
check(age: 20, job: "teacher")
check(age: 10)

分别会打印输出:

name is Mars, age is 18, job is student
name is Tom, age is 20, job is none
name is nobody, age is 20, job is teacher
name is nobody, age is 10, job is none

需要注意的是,在c++语言中有一种限制:函数的默认参数值必须从右向左设置,也就是说假如上面的例子中job参数没有设置默认参数值的话,age参数是不能设置默认参数值的。而在Swift中,由于引入了参数标签,所以这种限制也不存在。

可变参数

Swift中函数可以设置可变参数,可变参数可以接受零个或多个值。
可变参数通过在变量类型名后面加入 ... 的方式来定义。

func sum2(numbers: Int...) -> Int {
    var result = 0
    for number in numbers {
        result += number
    }
    return result
}
sum2(numbers: 10, 20, 30, 40) // 100
  • 一个函数只能有1个可变参数
  • 紧跟在可变参数后面的参数不能省略参数标签
func test(_ numbers: Int..., str: String, _ other: String) {}
test(10, 20, 30, str: "Mars", "Hello")

例子中,numbers是可变参数,并且省略了参数标签,所以str参数就不能省略参数标签。最后一个参数other可以省略参数标签。

输入输出参数

Swift中,可以用 inout 定义一个输入输出参数,用来在函数内部修改外部变量的值。可变参数不能用 inout
首先我们来看下面的例子:

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

我们想将变量 numberadd函数内部进行加 1 操作,但是由于swift中函数的参数默认是 let 类型,所以我们无法对 num 常量进行加 1 操作,程序也会报错。另外将变量 number 传入add函数,也仅是将变量 number 的值传递进去,也无法修改变量 number 的值。

下面我们用 inout 来修改一下这个函数:

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

利用 inout 关键字以后,我们就可以在函数内部对变量 number 的值进行修改,需要注意的是,在调用函数传递参数时,需要再参数前面加上 &。因为inout 在此处的作用本质是地址传递

利用 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)
print(num1) // 打印输出20
print(num2) // 打印输出10

我们也可以利用元组将上面的例子简化:

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

当然,Swift中已经提供了 swap(a: &T, b: &T)函数用来交换两个变量的值。

三、重载函数

Swift中支持重载函数。重载函数常用来实现功能类似而所处理的数据类型不同的问题。
重载函数的规则是:

  • 函数名相同
  • 参数个数不同 || 参数类型不同 || 参数标签不同
    例如同时下面的几个函数,编译器并不会报错,调用方法也会执行对应的方法实现:
// 参数个数不同
func sum(a1: Int, a2: Int) -> Int {
    a1 + a2
}
func sum(a1: Int, a2: Int, a3: Int) -> Int {
    a1 + a2
}
// 参数类型不同
func sum(a1: Int, a2: Double) -> Double {
    Double(a1) + a2
}
func sum(a1: Double, a2: Int) -> Double {
    a1 + Double(a2)
}
// 参数标签不同
func sum(_ a1: Int, _ a2: Int) -> Int {
    a1 + a2
}
func sum(a: Int, b: Int) -> Int {
    a + b
}

需要注意的是:

  • 返回值类型与重载函数无关,例如同时声明下面的两个函数就会报错:
// 返回值类型不同
func sum(a1: Int, a2: Int) -> Int {
    a1 + a2
}
func sum(a1: Int, a2: Int) {
    a1 + a2
}
  • 默认参数值和重载函数一起使用产生二义性时,编译器不会报错,但是在c++语言中会报错
func sum(a1: Int, a2: Int) -> Int {
    a1 + a2
}
func sum(a1: Int, a2: Int, a3: Int = 10) -> Int {
    a1 + a2 + a3
}

例子中第二个方法虽然设置了三个参数,但是第三个参数有默认值,如果调用第二个方法时没有传入第三个参数,即 sum(a1: 10, a2: 20) ,系统不错报错,但是会执行第一个函数。

四、内联函数

内联函数是一种编程语言结构,用来建议编译器对一些特殊函数进行内联扩展;也就是说建议编译器将指定的函数体插入并取代每一处调用该函数的地方,从而节省了每次调用函数带来的额外时间开支。

简单来说,在Swift中,开启编译器优化后,会将一些简单的函数调用展开成函数体。

编译器优化可以在 Bulid Setting 中开启,Release 模式下是默认开启的。Debug 模式下需要手动开启:

swift学习笔记 ② —— 函数_第1张图片
编译器优化.png

我们开启Debug 模式下的编译器优化,然后来看下面一段代码:

swift学习笔记 ② —— 函数_第2张图片
内联函数.png

例子中,我们声明了 test 函数,内部只是一个简单的打印,然后我们调用 test 函数,并且打入断点。运行项目发现,程序直接打印了 123456 并没有进入断点。这就说明,编译器并没有调用 test 函数,而是直接将 test 函数的函数体展开调用。下面我们将断点打在 test 函数内部 print 处,然后进入 汇编语言查看一下编译器底层:

swift学习笔记 ② —— 函数_第3张图片
汇编语言.png

可以看到整个 main 函数的汇编语言,断点停在 23 行,红色标注区域代码就是编译器在为 print 打印函数做的一些准备工作。第 34 行代码,也就是黄色标注区域就是我们要执行的 print 打印函数。也就说,编译器直接将 print 打印函数放在了 main 函数里面,虽然我们是写在 test 函数内部。那么例子中的几行代码就相当于下面的代码:

import Foundation

print("123456")

通过简单的测试证明了确实可以通过编译器优化将一些简单的函数优化为内联函数,以节省调用函数带来的额外时间开支。需要注意的是,一下几种函数即时在开启了编译器优化的情况下,也不会被优化成内联函数:

  • 函数体较长
  • 函数内包含递归调用
  • 函数内包含动态派发

不过可以使用 @inline 关键字来自定义哪些函数可以被内联:

// 永远不会被内联(开启了编译器优化也不会被内联)
@inline(never) func test() {
    print("123456")
}
// 开启编译器优化后,即使函数体很长,也会被内联
// 包含递归调用、包含动态派发除外
@inline(__always) func test() {
    print("123456")
}

五、函数类型

Swift 中每个函数都有种特定的函数类型,由函数的参数类型和返回类型组成。例如:

func test() {} // 函数类型为 () -> Void
func sum(a: Int, b:Int) -> Int {
      a + b
} // 函数类型为 (Int, Int) -> Int

函数类型定义变量

我们可以利用函数类型,定义一个类型为函数的常量或变量,并将适当的函数赋值给它:

var add: (Int, Int) -> Int = sum

可以用变量add 来调用被赋值的函数了:

add(2, 3) // 5

而且,使用时不需要参数标签。

函数类型作为参数类型

我们可以将函数作为参数传递给另外一个参数:

func sum(a: Int, b: Int) -> Int {
    return a + b
}
var add: (Int, Int) -> Int = sum
print("输出结果: \(add(40, 89))")

func another(add: (Int, Int) -> Int, a: Int, b: Int) {
    print("输出结果: \(add(a, b))")
}
another(add: sum, a: 10, b: 20)

以上程序执行输出结果为:

输出结果: 129
输出结果: 30

函数类型作为返回类型

我们也可以将函数类型作为一个函数的返回值类型:

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

func forward(_ judge: Bool) -> (Int) -> Int {
    judge ? next : previous
}
forward(true)(5) // 6 等价于 next(5)
forward(false)(5) // 4 等价于 previous(5)

例子中,next 函数和 previous函数均为(Int) -> Int 类型,我们将 forward 函数的返回值设置为(Int) -> Int 类型,就可以在forward 函数内部返回next 函数和 previous函数。

六、函数嵌套

函数嵌套指的是函数内定义一个新的函数,外部的函数可以调用函数内定义的函数。

func test(_ a: Int) -> () -> Int {
    var result = 0
    
    func next() -> Int {
        result -= a
       return result
    }

    return next
}
let nex = test(10)
print(nex()) // 打印结果为 -10

更多技术知识请扫码关注微信公众号

iOS进阶

swift学习笔记 ② —— 函数_第4张图片
iOS进阶.jpg

你可能感兴趣的:(swift学习笔记 ② —— 函数)