IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> iOS开发——Swift中的函数盘点 -> 正文阅读

[移动开发]iOS开发——Swift中的函数盘点

前言:

Swift已经被越来越多的公司使用起来,因此Swift的学习也应该提上日程了。本篇就先探索Swift中的函数,主要包括以下几个方面:

  • Swift函数定义

  • Swift函数参数与返回值

  • Swift函数重载

  • 内敛函数优化

  • 函数类型、嵌套函数

一、Swift函数定义

函数的定义包含函数名、函数体、参数及返回值,定义了函数会做什么、接收什么以及返回什么。函数名前要加上 func 关键字修饰。如下为一个完整的函数定义事例:

func greet(person: String) -> String {
    let greeting = "Hello, " + person + "!"
    return greeting
}  

  • 函数名:greet

  • 参数:圆括号中(person: String)即为参数,person为参数名,String为类型

  • 返回值:使用一个 -> 来明确函数的返回值,在该事例中定义了一个 String类型的返回值

作为一个开发者,有一个学习的氛围跟一个交流圈子特别重要,这是一个我的iOS交流群:834688868,不管你是大牛还是小白都欢迎入驻 ,分享BAT,阿里面试题、面试经验,讨论技术, 大家一起交流学习成长!

如果你正在面试,或者正准备跳槽,不妨看看我精心总结的面试资料:https://gitee.com/Mcci7/i-oser 来获取一份详细的大厂面试资料 为你的跳槽加薪多一份保障

二、函数返回值与参数

2.1 函数返回值

从返回值的角度看,函数可以分为有返回值和无返回值两种。无返回值的函数可以有如下三种定义方式:

func testA() -> Void {
}

func testB() -> () {
}

func testC() {
}

let a = testA()
let b = testB()
let c = testC()  

打印 a、b、c 可以发现,三者的类型均为(),即空元组。在 Void 的定义处也可以发现,Swift中 Void 就是空元组。

[图片上传中…(image-1a4ab5-1637226038732-4)]

也就是说上面三种方式是等价的,都表示无返回值的情况,不过从代码简洁程度上来说,最后一种更方便使用。

还有一种函数有返回值的情况,如同第一节中所述的函数定义方式,即为一种返回值为String的函数。在Swift中,函数的返回值可以隐式返回,如果函数体中只有一句返回代码,则可以省略return关键字。如下代码所示,两种写法是等价的:

func testD() -> String {
    return "正常返回"
}
func testE() -> String {
    "隐式返回"
}  

Swift中还可以通过元组实现多个返回值的情况,如下所示:

func compute(a:Int, b: Int) -> (sum: Int, difference: Int) {
    return (a+b, a-b);
}  

compute函数返回一个元组,包含了求和与求差,实现了返回多个值的情况。

2.2 函数参数

与OC不同的是,Swift中函数的参数是let修饰的,参数值是不支持修改的。如下图所示,可以证明。

[图片上传中…(image-9dbe10-1637226038734-20)]

2.2.1 函数标签

Swift的函数参数除了形参外,还包含一个参数标签。形参在函数内部使用,使得函数体中使用没有歧义,而函数标签用于在函数调用时使用,其目的是增加可读性。函数标签是可以省略的,使用_表示即可,需要注意的是,_与不设置函数标签是不一样的,如下图所示:

[图片上传中…(image-68984b-1637226038734-19)]

图片

当使用_时,调用函数不会显示函数标签,而不设置函数标签会把形参作为函数标签。

2.2.2 函数默认参数值

Swift可以给函数参数设置默认值,设置了默认值的参数,在函数调用时可以不传参

[图片上传中…(image-339f1f-1637226038734-17)]

由于参数 a 有了默认值 8,所以在调用时只传参 b 就可以。同样的,如果参数均有默认值,则在调用函数时,都可以不传值。

图片

如图所示,由于两个参数均有默认值,在调用时都不传值,就像调用了一个无参函数一样。

Swift中设置函数参数默认值可以不按照顺序,因为Swift中有函数标签,不会造成歧义。而在C++中,则必须要按照从右往左的顺序依次设置,两者对比如下:

[图片上传中…(image-3f5b28-1637226038734-15)]

[图片上传中…(image-cd1ebe-1637226038734-14)]

下面一张图是C++的调用,没有按照顺序设置默认值,直接报错缺失b的默认值,而Swift中则不会。但是,如果Swift函数参数都隐藏了函数标签,则无法识别是给哪个参数,只能按照从右往左的方向赋值,这样就会照成报错,如下图所示:

[图片上传中…(image-c7063e-1637226038734-13)]

在调用函数时,直接报错缺失第二个参数。因此,在Swift中,如果省略了函数参数标签,要保证所有的函数参数都有值,或者都可以得到赋值。

2.2.3 可变参数

与OC的NSLog参数一样,Swift函数也提供了可变参数,其定义方式是 参数名:类型...,可以参照系统的print函数定义:

[图片上传中…(image-25b3a8-1637226038734-12)]

print函数的第一个参数即为可变参数,参数类型为Any,可以接受任意类型,输入时以,分割即可。

可变参数需要注意的一点是,在紧随其后的一个参数不能省略参数标签,如下图所示:

[图片上传中…(image-e7f40e-1637226038734-11)]

参数b也是一个Any类型,如果省略了参数标签,则在调用函数时就没有了标签区分,仅凭,编译器无法确定该将参数赋值给item还是b,因此会报错。

可变参数本质上是一个数组,可以在函数内部使用参数,查看其类型如下:

[图片上传中…(image-e5c4fd-1637226038732-3)]

可以看到 item 实际上是一个 Any 类型的数组。

2.2.4 inout修饰的参数

在OC和C中,我们可以通过指针传参,以达到在函数内部修改函数外部实参的值的目的。在Swift中,也提供了类似的方法,不过需要使用inout修饰一下参数,具体使用方式如下:

[图片上传中…(image-f594cb-1637226038734-10)]

number的值本来为10,经过inoutFunc函数调用,结果变为了20。那么 inout 是如何改变了外部实参的值的呢?有种说法是与OC一样,采用了指针传值的方式改变;还有说法是 inout 在底层是一个函数,将其修饰的函数内部的值通过这个函数重新赋值外部实参。

针对这两种说法,我们可以通过汇编来验证下,本次使用的是真机调试,因此使用的是ARM下的汇编。

将上图中12行22行的断点打开,并打开XCode的汇编调试 Debug -> Debug Workflow -> Always show Disassembly。运行工程,首先进入22行的断点:

[图片上传中…(image-311e93-1637226038732-2)]

图中红框处为 inoutFunc 函数的调用处,在上面28行可以发现一行代码 ldr x0, [sp, #0x10]

这句代码的意思是,将[sp, #0x10]的值赋值给 x0 寄存器,[sp, #0x10]表示 sp+#0x10的地址,也就是说 x0 寄存器现在存储的是一个地址,通过 register read x0 命令可知改地址为 x0 = 0x000000016dbf9a80

单步调试进入 inoutFunc 函数,得到如下代码:

[图片上传中…(image-22c0cf-1637226038733-9)]

执行到第4行,再次读取 x0 寄存器得到了相同的值x0 = 0x000000016dbf9a80,此时通过 x/4gx 读取内存地址0x000000016dbf9a80的值,得到结果如下:

[图片上传中…(image-497160-1637226038733-8)]

红框中的值 0x000000000000000a 换算成十进制正是 10。走到第6行汇编代码,将x0存储的地址所指向的内容存到x8寄存器,然后将值加10,就此完成对外部实参值的改变。在viewDidLoad中调用inoutFunc后并没有对于number的重新赋值,也证实了inout是通过地址传递改变外部实参的值。

使用inout需要注意两点:

  • 1、inout只能传入可以被多次赋值的,即不能传入常量和字面量

  • 2、inout不能修饰可变参数

三、函数重载

函数重载指的是函数名相同,但是参数名称不同 || 参数类型不同 || 参数个数不同 || 参数标签不同。需要注意的是,函数重载(overload)与函数重写(override)是两个概念,函数重写涉及到继承关系,而函数重载不涉及继承关系。另外,在OC中没有函数或方法的重载,只有重写。以下是几个函数重载的例子:

[图片上传中…(image-8fa73d-1637226038733-7)]

可以看到,四个函数的方法名称相同,但是参数不同,实际上并不会报错,这就是方法重载。

不过方法重载也有需要注意的地方:

  • 方法重载与函数返回值无关,即函数名及参数完全相同的情况下,如果返回值不同,不构成函数重载,编译器会报错。

[图片上传中…(image-bc4706-1637226038733-6)]

如图所示,在调用方法时,编译器不知道该调用哪个函数,因此会报二义性错误。

  • 方法重载与默认参数值的情况

[图片上传中…(image-ad1c00-1637226038733-5)]

从图中可以发现,由于第二个函数给参数c设置了默认值,在调用时形式上与第一个函数一样,不过编译器在此并不会报错,猜想是因为第二个函数还有一种test(a: , b: , c: )的调用形式。

四、inline内联函数

内联函数,其实是指开启了编译器内联优化后,编译器会将某些函数优化处理,该优化会将函数体抽离出来直接调用,而不会给这个函数再开辟栈空间。

func test() {
    print("test123")
}
test()  

如以上函数所示,调用test()时,需要为其开辟栈空间,而其内部只调用了一个print函数,所以在开启内联优化的情况下,可能会直接调用print函数。

开启内联优化的方式如下图:

[图片上传中…(image-2a6d5b-1637226038731-1)]

Debug模式下默认不开启优化,Release模式下默认是开启的。为了测试内联优化的现象,这里先将Debug模式开启优化,之后在test()调用处打断点,再运行工程会发现,直接打印了test123,然后在test函数内部打断点,进入汇编如下:

[图片上传中…(image-d8efac-1637226038731-0)]

全局搜索发现没有test函数的调用,而是直接调用了print函数。

不过内联优化,也不是对所有函数都会进行优化,以下几点不会优化:

  • 函数体代码比较多

  • 函数存在递归调用

  • 函数包含动态派发,例如类与子类的多态调用

内联函数还有内联参数控制@inline(never)@inline(__always)

  • 使用@inline(never)修饰,即使开启了编译器优化,也不会内联

  • 使用@inline(__always)修饰,开启编译器优化后,即使函数体代码很长也会内联,但是递归和动态派发依然不会优化

五、函数类型

每一个函数都可以符合一种函数类型,例如:

func test() {
    print("test123")
}

对应 () -> ()

func compute(a:Int = 8, b: Int = 9) -> Int {
    return a+b;
}

对应 (Int, Int) -> Int  

上述代码中,() -> ()(Int, Int) -> Int都表示一种函数类型。可以发现函数类型是不需要参数名的,直接标明参数类型即可。

函数类型也可以用作函数的参数和返回值,使用函数类型作为返回值的函数被称为高阶函数,例如:

// 函数类型作为参数
func testFunc(action:(Int) -> Int) {
    var result = action(2)
    print(result)
}

func action(a:Int) -> Int {
    return a
}
testFunc(action: action(a:))

// 函数类型作为返回值
func action(a:Int) -> Int {
    return a
}
func testFunc() -> (Int) -> Int {
    return action(a:)
}
let fu = testFunc()
print(fu(3))  

六、嵌套函数

Swift中,可以在函数内部定义函数,被称为嵌套函数,如下代码所示:

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

    return forward ? next : previous
}  

像上面这样在函数内部定义其他的函数,其目的是为了将函数内部的实现封装起来,外部只看到调用了 forward,而不需要知道其内部的实现逻辑,当然也不能直接调用内部的嵌套函数。

总结

相对于OC,Swift中主要增加了以下几点:

  • 参数标签

  • 函数重载

  • 嵌套函数

整体而言,个人感觉Swift的函数使用起来更加方便,参数标签使得代码可读性更强。以上即为本篇关于Swift函数的总结,如有不足之处,欢迎大家指正。


作者 | 奔跑的不将就

来源 | 掘金

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2021-11-24 08:03:55  更:2021-11-24 08:05:54 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 4:59:53-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码