注:本文为自己学习The Swift Programming Language的笔记,其中的例子为引用原书和其他博文或自己原创的。每个例子都会批注一些实践过程中的经验或思考总结。
1.基础
函数是执行某项特定任务的代码块,函数有函数名来区别函数的特定功能。Swift的函数十分灵活,能够表达从没有参数名的C语言风格函数到复杂如带有本地参数名和外部参数名两种参数名的参数的Objective-C风格函数。传入参数可以提供让函数基本运作的初始值,Swift支持in-out型参数,传入变量的值会随着函数内部运算更改。
函数的参数类型和返回值类型可以是任意Swift的数据类型,Swift支持内部函数即在函数内部定义函数。
2.函数定义和函数调用
函数定义首先需要func关键字,声明接下来要定义函数。函数名称除了保留关键字任意定义,建议清楚的反映该函数的功能。
函数参数在括号中给出,需要进行类型注释;函数的返回值类型在箭头符号后面给出。Swift函数可以没有参数、没有返回值,没有参数括号需要保留,没有返回值箭头符号一并省略。函数体在大括号中书写,需要返回值的函数使用return语句返回值。下面是一个标准的函数定义:
func sayHello(personName: String) -> String {
let greeting = "Hello, " + personName + "!"
return greeting
}
函数名称是sayHello,参数是叫做personName的String类型,返回值类型也是String。
调用函数需要在函数名后的括号中给出必要的参数,函数返回值将取代函数调用的位置返回给调用者。
println(sayHello("JCGuo"))
// prints "Hello, JCGuo!”
Swift的函数除了格式以外,这些基本性质及用法是和类C语言一致的。
3.函数参数和函数返回值
函数可以有多个参数传入,它们之间用逗号隔开,各自带有自己的类型注释;并且如前面所述函数也可以没有参数传入,也可以没有返回值。
然而函数也可以返回多个可以是不同类型的值,需要用到元组,多个返回值的类型用元组的形式给出。正如元组声明时可以为每个元素取名字一样,函数返回值元组也可以为每个返回值取一个名字,不仅增强了阅读还方便了调用返回元组的元素成员。例如下面的字符统计函数:
func count(string: String) -> (vowels: Int, consonants: Int, others: Int) {
var vowels = 0, consonants = 0, others = 0
for character in string {
switch String(character).lowercaseString {
case "a", "e", "i", "o", "u":
++vowels
case "b", "c", "d", "f", "g", "h", "j", "k", "l", "m",
"n", "p", "q", "r", "s", "t", "v", "w", "x", "y", "z":
++consonants
default:
++others
}
}
return (vowels, consonants, others)
}
返回值分别为三个Int类型元音字母个数、辅音字母个数及其他字符个数。在给出返回值名称的多返回值函数的调用时,若要访问元组的元素成员,需使用点运算符和返回值名称。
let total = count("some arbitrary string!")
println("\(total.vowels) vowels and \(total.consonants) consonants")
4.1函数参数名
只能在函数体内部引用的参数名叫做本地参数名,与之相对的还有外部参数名。
4.1外部参数名
有些时候我们为了指明调用函数时传入参数的目的,需要在调用函数时提供参数名称。调用时所提供的参数名叫做外部参数名,它起的作用就是给函数使用者一个清晰的调用目的,不一定和本地参数名相同。一般在函数声明时在本地参数名前面直接用空格隔开的时外部参数名,格式如下:
func someFunction(externalParameterName localParameterName: Int) {
// function body goes here, and can use localParameterName
// to refer to the argument value for that parameter
}
以字符串连接作为例子,用外部函数名标注连接的顺序:
func stringJoin(string s1: String, toString s2: String, withJoiner joiner: String)
-> String {
return s1 + joiner + s2
}
println(stringJoin(string : "Hello", toString : "Swift!", withJoiner : ", "))
在参数较多或者意图并不十分明显时使用外部参数名,提高可读性。
4.2简写外部参数名
Swift支持直接给参数名添加一个#号前缀,简写表示外部参数名可以和本地参数名一样。上例可以统一外部参数名和本地参数名,如下:
func stringJoin2(#string : String, #toString : String, #withJoiner : String)
-> String {
return string + withJoiner + toString
}
println(stringJoin2(string : "Hello", toString : "Swift!", withJoiner : ", "))
4.3参数默认值
在函数定义时可以给函数参数声明一个默认值,配合外部参数名指定传入参数可以实现参数传入的缺省:
func stringJoin3(string s1: String, toString s2: String = "World!", withJoiner joiner: String)
-> String {
return s1 + joiner + s2
}
println(stringJoin3(string : "Hello", withJoiner : ", "))
大多数情况下为了让有默认值的参数意图表明清晰,有必要为其添加外部变量名。Swift为了支持这个功能提供为有默认值但没有注明外部参数名的参数,自动补充外部函数名,补充的外部函数名就是本地参数名。在调用一个有默认值的参数的函数时必须显式提供该参数外部参数名:
func stringJoin4(string : String, toString : String = "World!", withJoiner: String)
-> String {
return string + withJoiner + toString
}
//println(stringJoin4("Hello", "Swift!", ", "))
对于stringJoin4这句print是错误的,调用时必须显式提供toString外部参数名。如果在定义函数时用下划线代替有默认值的参数的外部参数名,那么在调用时即可避免这个问题:
func stringJoin5(string : String, _ toString : String = "World!", withJoiner: String)
-> String {
return string + withJoiner + toString
}
println(stringJoin5("Hello","Swift!", ", "))
这句的调用是正确的。
4.4可变参数
Swift支持函数穿入参数为个数可变的,成为可变参数[Variadic Parameters]。可变参数支持同一种类型的零个或多个参数传入,在参数类型名后添加后缀三个点...表示该类型的参数时可变参数。为了避免参数列表的混乱,一个函数最多有一个可变参数,并且他只能是参数列表里的最后一个:
func arithmeticMean(numbers: Double...) -> Double {
var total: Double = 0
for number in numbers {
total += number
}
return total / Double(numbers.count)
}
4.5变量参数
Swift的参数默认都是常量,在函数体中修改参数的值会导致错误。有些时候我们需要改变参数的值,但又不想再单独申请一个变量来完成这项工作,这时候可以在参数前添加var关键字表示这个参数是变量。
变量参数的生命周期仅存在于当前这一次函数调用。变量参数常用于对某一个传入参数的小幅度修改。
4.6in-out参数
in-out型的参数在函数内部是可以改变的,并且随着内部操作会导致传入参数的值的改变。in-out型参数类似于C语言的传址调用参数,在函数定义时用inout关键字声明该参数时in-out型的;传入in-out型的参数的值必须是一个变量,在调用函数时要在变量名前添加前缀&符号,表示函数可能会修改该变量的值。用C语言来解释的话就是传入的是变量的指针而不是变量的值。
in-out型的参数不能有默认值,它也不能是可变参数,声明时也不需要添加var或let关键字(因为它本来就相当于是一个变量)。
func swapTwoInts(inout a: Int, inout b: Int) {
let temporaryA = a
a = b
b = temporaryA
}
var someInt = 3
var anotherInt = 107
swapTwoInts(&someInt, &anotherInt)
someInt或者anotherInt如果是常量会导致编译错误。
5.函数类型
每个函数都有参数类型和返回值类型,他们组合在一起就是函数的类型。例如前面stringJoin函数就是一个(String,String,String) -> String型的函数,而swapTwoInts函数就是一个(inout Int,inout Int) -> ()的函数(无返回值的函数返回值类型用空元组代替)。
5.1使用函数类型
Swift中函数类型也是一种类型,我们可以像使用其他类型一样使用函数类型。比如可以像用变量类型来定义一个变量一样,用函数类型来定义一个函数:
var stringOperation: (String, String, String) -> String = stringJoin
这样相当于声明了一个类型是(String,String,String) -> String的函数,它的内容和stringJoin一致,我们可以像使用stringJoin一样使用它。
当然我们也可以省略函数类型注释,交给Swift自动推断。
stringOperation也可以被重新赋值,但我发现一个问题[待解决]:
stringOperation = stringJoin2
println(stringOperation("Hello","Swift!", "? "))
//println(stringJoin2("Hello","Swift!", "? "))
我们把stringOperation赋值为stringJoin2,在调用它时却不需要给出外部参数名,编译运行输出结果均正确。而斜杠注释内容时编译错误的。
5.2函数类型作为参数类型
函数类型作为参数类型允许其他函数作为参数传入,以供在当前函数中调用:
func hasAnyMatches(list: Int[], condition: Int -> Bool) -> Bool {
for item in list {
if condition(item) {
return true
}
}
return false
}
func lessThanTen(number: Int) -> Bool {
return number < 10
}
var numbers = [20, 19, 7, 12]
println("It is \(hasAnyMatches(numbers, lessThanTen)).")
一个Int -> Bool类型的函数作为输入,在hasAnyMatches中使用。虽然这相当于直接在hasAnyMatches中调用lessThanTen函数,但我们可以在不改变hasAnyMatches这个函数的基础上仅通过改变传入的函数来改变匹配条件,这样的代码时模块化的、优美的。
5.3函数类型作为返回值类型
函数类型同样可以作为返回值返回给调用者,相当于构建一个函数。例子和嵌套函数一起给出
5.4嵌套函数
嵌套函数[nested function]就是在函数体内部构建的函数,作用域仅在这个函数体内部。以上所给出的函数都叫做全局函数[global function]。
下面的例子给出了使用嵌套函数返回一个函数类型的例子:
func makeIncrementer() -> (Int -> Int) {
func addOne(number : Int) -> Int {
return number + 1
}
return addOne
}
let incrementer = makeIncrementer()