目录
函数定义
理解难度
★★★☆☆
实用程度
★★★★★
函数(function)是一个独立的代码区块,用来完成特定任务。当你需要一个多次使用到的功能时,重复代码通常不是个好主意。函数帮助我们避免这样的问题。把这个功能写成一个函数可以简化代码。
- 函数声明: 告诉编译器函数的名字,返回类型及参数。
- 函数定义: 提供了函数的实体。
函数命名的方式与常数变数一样,但名称后面需要加上小括号( )(像是前面章节很常使用的print(),就是一个内建的函数。建立一个函数要使用func关键字,函数格式如下:
func 函数名称(参数: 参数类型) -> 返回值类型 {
内部执行的程式
return 返回值
}
Swift函数从 func 关键字开始,然后是函数名称,然后打开和关闭括号。你的函数的所有主体在请求函数时,应该运行的代码都放置在大括号内。
定义函数的时候,可以指定一个或多个输入参数和一个返回值类型。
每个函数都有一个函数名来描述它的功能。通过函数名以及对应类型的参数值来调用这个函数。函数的参数传递的顺序必须与参数列表相同。
函数的实参传递的顺序必须与形参列表相同,-> 后定义函数的返回值类型。
依据上述介绍,你可以为一个函数设置传入的参数与返回值,但也可以依据需求不设置参数与返回值,如下为一个最简单的函数格式:
func 函数名称() {
// 内部执行的程式
}
没有参数也没有返回值,以下是一个例子:
// 建立一个函数
func simpleOne() {
print("It is a simple function .")
}
// 呼叫函数
simpleOne()
// 打印:It is a simple function .
这样就会执行函数内的程式。这就是一个最简单的功能。
函数中应该写什么代码?
有三个时机点就应该创建自己的函数:
- 最常见的情况是,在不同的地方想要相同的功能。在这里使用函数意味着你可以只更改一段代码,所有使用同一个函数的任何地方都会更新。
- 函数也可用来分解代码。如果有一个很长函数,可能不容易调试现在的进程;但如果将其分解为三到四个较小的函数,那么就更容易追踪了。
- 最后一个原因是更进阶的。Swift允许我们从现有函数中构建新函数,称为函数嵌套。通过将进程拆分为多个小函数,函数组合使我们能够通过以各种方式组合这些小函数来构建大函数,有点像组合积木一样。
函数参数
理解难度
★★★★☆
实用程度
★★★★★
Swift允许你将值发送到函数,然后该函数可以在函数里面用于更改其行为方式。以这种方式发送到函数的值称为参数。
函数可以传入参数(parameter),定义函数的参数时,必须明确标注类型,然后就会将参数指派成一个常数,格式如下:
func 函数名称(参数指派给的常数: 类型标注) {
// 内部执行的程式
}
以下是一个例子:
// 建立函数 有一个类型为 Int 的参数
// 函数的功能是将带入的参数加一 并打印来
func addOne(数字: Int) {
// number 即为被指派参数的常数 只能在函数内部范围内使用
print(数字 + 1)
}
// 呼叫函数 传入整数 12
addOne(数字: 12)
这里的“数字”是由自己命名的。最好名称命名要清楚合理,至少得要自己看得懂。一看就明白才是好代码。
想想看,现在打印的结果是什么?
多重参数函数
函数如果有超过一个参数时,要依序将参数填入,并以逗号 , 隔开,以下例子:
// 建立有两个参数的函数 参数的类型分别为 String 及 Int
func hello(name: String, age: Int) {
print("\(name) is \(age) years old .")
}
// 呼叫函数
hello(name: "Jack", age: 25)
一个函数应该接受多少参数?
一个函数可以不带参数或带 20 个参数。这是一个没有“绝对”而难回答的东西。
但是一个函数需要许多参数时…也许六个或更多,这是非常主观的问题…你需要开始想想,这个函数是不是做了太多的工作了?复杂化了?
- 真的需要所有的参数吗?
- 是不是可以把这个函数拆分为参数少一点的小函数?
- 这些参数是不是可以以某种方式分组呢?
函数参数名称与标签
理解难度
★★☆☆☆
实用程度
★★☆☆☆
每个函数的参数的完整定义方式,都包含参数标签(argument label)及参数名称(parameter name)。
- 参数标签用于呼叫函数时,标注给函数的参数。
- 参数名称用于函数内部操作。
以下为一个有参数标签及参数名称的函数格式:
func 函数名称(参数标签1 参数名称1: 类型1,
参数标签2 参数名称2: 类型2) {
// 内部执行的程式
}
以下为一个例子:
func hello2(name n: String, age a: Int) {
// n 跟 a 为参数名称 在函数内部使用
print("\(n) is \(a) years old .")
}
// name 跟 age 为参数标签 呼叫函数时要标注在参数之前
hello2(name: "Jack", age: 25)
虽然在这里介绍这个标签的技巧,但是说实在的,何必找自己的麻烦呢!
省略参数标签
理解难度
★★☆☆☆
实用程度
★★★★☆
如果呼叫函数时想要省略参数标签(argument label),定义函数时只要使用下底线_来替代参数标签即可,以下为一个例子:
// 参数标签使用下底线 _ 替换
func hello3(_ name: String, _ age: Int) {
print("\(name) is \(age) years old .")
}
// 呼叫函数时 就可以不用写参数标签
hello3("Jack", 33)
这个技巧倒是实用多了。
或是在定义函数时省略参数标签,这样就可以将参数名称当作参数标签使用,如下:
// 使用函数的参数名称作为参数标签 这边就是 name 与 age
func hello4(name: String, age: Int) {
print("\(name) is \(age) years old .")
}
// 呼叫函数时 使用的参数标签即为参数名称
hello4(name: "Jack", age: 25)
Swift 会这样稍微复杂的设计函数参数名称,是为了让呼叫函数时,语意可以更明显,以下是一个例子:
func sayHello(to name: String, and name2: String) {
print("Hello \(name) and \(name2) !")
}
// 参数标签设为 to 跟 and
// 这行看起来就像个完整的英文句子 可以明显的知道这个函数要干嘛
sayHello(to: "Joe", and: "Amy")
总之,管他是标签还是名称,一个已经足够,何必用两个来混乱自己呢?不要这样做了。
预设参数值
理解难度
★★★☆☆
实用程度
★★★☆☆
可以对函数的参数设定一个预设值,如果未传入值时,则内部就使用这个预设值。就像这样:
func someFunction(number: Int = 12) {
print(number)
}
// 打印:6
someFunction(number: 6)
// 如果没有传入值 则会使用预设值 打印:12
someFunction()
- 一个函数只能有一个可变数量参数。
- 函数如果有预设值参数,也有可变数量参数时,必须把可变数量参数放在参数列表的最后。
输入输出参数
理解难度
★★★★★
实用程度
★☆☆☆☆
普通函数的参数使用范围都只在函数程式体内,如果想要一个可以修改参数的函数,并在呼叫函数结束后,这个修改仍然存在,则必须将参数设定为输入输出参数( In-Out Parameters )。使用方式为:
- 定义函数时,在参数类型前加上inout。此参数不能有预设值,也不能再设为可变数量参数。
- 当呼叫函数,传入的参数作为输入输出参数时,需要在参数前加上&。这个参数只能是一个变数,不能是常数、字面量(单纯的数值或字串)。
以下是个例子:
// 定义一个[有一个输入输出参数]的函数 参数类型前要加上 inout
func newNumber(number: inout Int) {
number *= 2
}
var n = 10
print(n) // 这时 n 为 10
// 传入的参数在函数结束后 改变仍然存在
newNumber(number: &n)
print(n) // 所以这时再打印 就会是 20
函数返回值
理解难度
★★★☆☆
实用程度
★★★★☆
函数除了可以传入参数,同时也可以返回值,使用方式是在函数( )后面接着一个减号跟大于组成的箭头 -> ,然后再接着写返回值的类型标注,函数内部要使用 return 语句后接着返回值来返回。使用 return 后会随即终止函数的动作,函数内部其后的程式都不会继续执行。
以下例子:
// 定义函数 有一个类型为 Int 的参数以及一个类型为 Int 的返回值
// 函数的功能是将带入的参数加十 并返回
func addTen(number: Int) -> Int {
let n = number + 10
// 使用 return 来返回值 这个返回值的类型要与上面标注的相同
return n
}
// 呼叫函数 传入整数 12 会返回 22
let newNumber = addTen(number: 12)
print(newNumber)
想想看,现在打印的结果是什么?
没有返回值函数
理解难度
★★☆☆☆
实用程度
★★★★☆
下面是 runoob(_😃 函数的另一个版本,这个函数一个网址参数,没有指定返回值类型,并直接输出 String 值,而不是返回它:
func 网址(site: String) {
print("米尔视觉创意:\(site)")
}
网址(site: "https://millva.com/APPS.html")
多重返回值函数
理解难度
★★★★☆
实用程度
★★☆☆☆
函数可以有不只一个的返回值,返回值超过一个时以一个元组(Tuple)返回,元组内包含着要回传的每个值的类型标注。在建立函数时也可以给返回元组的每个值加上名称。以下是一个例子:
// 定义函数 有一个类型为 Int 的参数, 返回两个类型为 Int 的值
func findNumbers(number: Int) -> (Int, Int) {
let n = number + 10
// 返回一个元组
return (number, n)
// 合并成一行直接返回也是可以
// return (number, number + 10)
}
// 呼叫函数 传入整数 12 会返回 (12, 22)
let numbers = findNumbers(number: 12)
// numbers 为一个元组 可以自 0 开始算 依序取得其内的值
// 打印:12 and 22
print("\(numbers.0) and \(numbers.1)")
// 定义另一个函数 将上面函数中返回的元组内的值加上名称
func findNumbers2(number: Int) -> (oldNumber: Int, newNumber: Int) {
let n = number + 10
return (number, n)
}
// 呼叫函数 传入整数 24 会返回 (24, 34)
let numbers2 = findNumbers2(number: 24)
// 这边即可使用定义函数时 返回元组内的值设定的名称
// 打印:24 and 34
print("\(numbers2.oldNumber) and \(numbers2.newNumber)")
可选元组返回类型
理解难度
★★★★☆
实用程度
★★☆☆☆
如果整个返回的元组可能会没有值(nil),可以将此返回元组设为可选元组类型,使用方式为在括号()后面接着一个问号?,像是(Int, String)?或是(String, Int, Int)?。以下是个例子:
// 定义函数 参数为一个类型为 [Int] 的阵列
// 返回值为 包含两个类型为 Int 的元组 或是 nil
func findNumbers3(arr: [Int]) -> (Int, Int)? {
// 检查传入的阵列 如果其内没有值的话 就直接返回 nil
if arr.isEmpty {
return nil
}
let n = arr[0] + 10
let n2 = arr[0] + 100
return (n, n2)
}
// 呼叫函数
// 因为返回的是一个可选类型 这边先做可选绑定的动作 确定有值后再打印来
if let numbers = findNumbers3(arr: [11,22,33]) {
print("\(numbers.0) and \(numbers.1)")
}
// 如果传入的阵列 内部没有值
if let numbers = findNumbers3(arr: []) {
print("这里不会被打印来 \(numbers.0)")
}
函数类型
理解难度
★★☆☆☆
实用程度
★★★★☆
函数也是一种类型,由函数的参数类型和返回值类型组成:
func someFunction(a: String, b: Int) -> Int {
// 函数内部程式
}
上述程式中的函数,这个函数类型就标注为(String, Int) -> Int,意思就是有两个类型依序为String跟Int的参数,会返回一个类型为 Int 的值。
以下是另一个例子,如果没有参数也没有返回值时,函数类型标注为( ) -> Void。
func hello5() {
print("Hello !")
}
这个函数类型也可以标注为() -> ()。实际上,没有返回值的函数,会返回一个特殊的值,叫做Void,也就是一个空的元组(tuple),没有任何元素,可以写成( )。
使用函数类型
理解难度
★★★★☆
实用程度
★☆☆☆☆
前面刚提过函数是一种类型,所以也可以将变数或常数声明为一个函数,然后可以指派一个适当的函数。
以下是一个例子,声明一个变数mathFunction,是一个类型为(Int) -> Int的函数,最后指派一个已经存在且类型相同的函数:
var mathFunction: (Int) -> Int = addTen
函数类型作为参数类型
理解难度
★★★★☆
实用程度
★☆☆☆☆
函数可以作为另一个函数的参数,以下是个例子:
// 定义一个将两个整数相加的函数
func addTwoInts(number1: Int, number2: Int) -> Int {
return number1 + number2
}
// 定义另一个函数,有三个参数依序为
// 类型为 (Int, Int) -> Int 的函数, Int, Int
func printMathResult(
_ mathFunction: (Int, Int) -> Int, _ a: Int, _ b: Int) {
print("Result: \(mathFunction(a, b))")
}
// 将一个函数 addTwoInts 传入函数 printMathResult
printMathResult(addTwoInts, 3, 5)
上述程式中,作为参数的函数只需要类型正确,不用了解其内的程式如何运作,这表示可以将 printMathResult 函数一部分的操作交给呼叫函数的人来实作,也是以一种类型安全(type-safe)的方式来保证传入函数的呼叫是正确的。
函数类型作为返回类型
理解难度
★★★☆☆
实用程度
★★★☆☆
函数可以作为另一个函数的返回值,使用方式为在返回箭头(->)后写一个完整的函数类型。
以下例子:
// 定义一个将传入的参数加一的函数
func stepForward(input: Int) -> Int {
return input + 1
}
// 定义一个将传入的参数减一的函数
func stepBackward(input: Int) -> Int {
return input - 1
}
// 建立一个参数为布林值的函数 会返回一个函数
// 根据布林值返回上述两个函数的其中一个
func chooseStepFunction(backwards: Bool) -> (Int) -> Int {
return backwards ? stepBackward : stepForward
}
// 声明一个整数常数
let number = 3
// 声明一个函数常数
let someFunction2 = chooseStepFunction(backwards: number > 0)
// 根据 chooseStepFunction 函数的内容
// 传入 true 时 会返回 stepBackward 函数
// 所以 someFunction2 会被指派为 stepBackward
someFunction2(10) // 返回 9
这边声明函数常数时没有标注类型,因为就如同其他变数或常数声明时,Swift 会根据指派的函数自动判断出类型。