函数就像是小学数学课本中所讲的那种能够处理各种东西的机器一样。你知道我说的是什么:上面是一个漏斗,中间是一些齿轮和曲柄,下面的管子就会出来东西。函数就像这样的机器一样:你提供一些东西,然后由特定的机器进行处理,最后东西就生成出来了。
提供的东西是输入,出来的东西是输出。从技术的角度来看,函数的输入叫作参数,输出叫作结果。比如,下面这个简单的函数有两个Int值输入,然后将它们相加,最后输出相加的和:
func sum (x:Int, _ y:Int) -> Int { let result = x + y return result}
这里所用的语法是非常严格且定义良好的,如果理解不好你就没法用好Swift。下面来详细介绍;我将第1行分为几块来分别介绍:
func sum ① (x:Int, _ y:Int) ②③ -> Int { ④ let result = x + y ⑤ return result ⑥}
①声明从关键字func开始,后跟函数的名字,这里就是sum。调用函数时必须使用这个名字,所谓调用就是运行函数所包含的代码。
②函数名后面跟着的是其参数列表,至少要包含一对圆括号。如果函数接收参数(输入),那么它们就会列在圆括号中,中间用逗号隔开。每个参数都有严格的格式:参数名,一个冒号,然后是参数类型。这里的sum函数接收两个参数:一个是Int,其名字为x;另一个也是Int,其名字为y。
值得注意的是,名字x与y是随意起的,它们只对该函数起作用。它们与其他函数或是更高层次作用域中所使用的其他x与y是不同的。定义这些名字的目的在于让参数值能有自己的名字,这样就可以在函数体的代码块中引用它们了。实际上,参数声明是一种变量声明:我们声明变量x与y以便在该函数中使用它们。
③该函数声明的参数列表中第2个参数名前还有一个下划线(_)和一个空格。现在我不打算介绍这个下划线,只是这个示例需要用到它,因此相信我就行了。
④圆括号之后是一个箭头运算符→,后跟函数所返回的值类型。接下来是一对花括号,包围了函数体——实际代码。
⑤在花括号中(即函数体),作为参数名定义的变量进入生命周期,其类型是在参数列表中指定的。我们知道,只有当函数被调用并将值作为参数传递进去时,这些代码才会运行。
这里的参数叫作x与y,这样我们就可以安全地使用这些值,通过其名字来引用它们,我们知道这些值都会存在,并且是Int值,这是通过参数列表来指定的。不仅是程序员,编译器也可以确保这一点。
⑥如果函数有返回值,那么它必须要使用关键字return,后跟要返回的值。该值的类型要与之前声明的返回值类型(位于箭头运算符之后)相匹配。
这里返回了一个名为result的变量;它是通过将两个Int值相加创建出来的,因此也是个Int,这正是该函数所生成的结果。如果尝试返回一个String(return/"howdy/")或是省略掉return语句,那么编译器就会报错。
关键字return实际上做了两件事。它返回后面的值,同时又终止了函数的执行。return语句后面可以跟着多行代码,不过如果这些代码行永远都不会执行,那么编译器就会发出警告。
花括号之前的函数声明是一种契约,描述了什么样的值可以作为输入,产生的输出会是什么样子。根据该契约,函数可以接收一定量的参数,每个参数都有确定的类型,并会生成确定类型的结果。一切都要遵循这个契约。花括号中的函数体可以将参数作为局部变量。返回值要与声明的返回类型相匹配。
这个契约会应用到调用该函数的任何地方。如下代码调用了sum函数:
let z = sum(4,5)
重点关注等号右侧——sum(4,5),这是个函数调用。它是如何构建的呢?它使用了函数的名字、名字后跟一对圆括号;在圆括号里面是逗号分隔的传递给函数参数的值。从技术角度来说,这些值叫作实参。我这里使用了Int字面值,不过还可以使用Int变量;唯一的要求就是它要有正确的类型:
let x = 4let y = 5let z = sum(y,x)
在上述代码中,我故意使用了名字x与y,这两个变量值会作为参数传递给函数,而且还在调用中将它们的顺序有意弄反,从而强调这些名字与函数参数列表及函数体中的名字x与y没有任何关系。对于函数来说,名字并不重要,值才重要,它们的值叫作实参。
函数的返回值呢?该值会在函数调用发生时替换掉函数调用。上述代码就是这样的,其结果是9。因此,最后一行就像我说过的:
let z = 9
程序员与编译器都知道该函数会返回什么类型的值,还知道在什么地方调用该函数是合法的,什么地方调用是不合法的。作为变量z声明的初始化来调用该函数是没问题的,这相当于将9作为声明的初始化部分:在这两种情况下,结果都是Int,因此z也会声明为Int。不过像下面这样写就不合法了:
let z = sum(4,5) + /"howdy/" // compile error
由于sum返回一个Int,这相当于将Int加到一个String上。在默认情况下,Swift中不允许这么做。
忽略函数调用的返回值也是可以的:
sum(4,5)
上述代码是合法的;它既不会导致编译错误,也不会造成运行错误。这么做有点无聊,因为我们历尽辛苦使得sum函数能够实现4与5相加的结果,但却没有用到这个结果,而是将其丢弃掉了。不过,很多时候我们都会忽略掉函数调用的返回值;特别是除了返回值之外,函数还会做一些其他事情(从技术上来说叫作副作用),而调用函数的目的只是让函数能够做一些其他事情。
如果在使用Int的地方调用sum,而且sum的参数都是Int值,那是不是就表示你可以在sum调用中再调用sum呢?当然了!这么做是完全合法的,也是合情合理的:
let z = sum(4,sum(5,6))
这样写代码存在着一个争议,那就是你可能被自己搞晕,而且会导致调试变得困难。不过从技术上来说,这么做是正常的。
2.1.1 Void返回类型与参数
下面回到函数声明。关于函数参数与返回类型,存在以下两种情况能够让我们更加简洁地表示函数声明。
无返回类型的函数
没有规定说函数必须要有返回值。函数声明可以没有返回值。在这种情况下,有3种声明方式:可以返回Void,可以返回(),还可以完全省略箭头运算符与返回类型。如下声明都是合法的:
func say1(s:String) -> Void { print(s) }func say2(s:String) -> { print(s) }func say3(s:String) { print(s) }
如果函数没有返回值,那么函数体就无须包含return语句。如果包含了return语句,那么其目的就纯粹是在该处终止函数的执行。
return语句通常只会包含return。不过,Void(无返回值的函数的返回类型)是Swift中的一个类型。从技术上来说,无返回值的函数实际上返回的就是该类型的值,可以表示为字面值()。(第3章将会介绍字面值()的含义。)因此,函数声明return()是合法的;无论是否声明,()就是函数所返回的。写成return()或return;
(后面加上一个分号)有助于消除歧义,否则Swift可能认为函数会返回下一行的内容。
如果函数无返回值,那么调用它纯粹就是为了函数的副作用;其调用结果无法作为更大的表达式的一部分,这样函数中代码的执行就是函数要做的唯一一件事,返回的()值会被忽略。不过,也可以将捕获的值赋给Void类型的变量;比如:
let pointless : Void = say1(/"howdy/")
无参数的函数
没有规定说函数必须要有参数。如果没有参数,那么函数声明中的参数列表就可以完全为空。不过,省略参数列表圆括号本身是不可以的!圆括号需要出现在函数声明中,位于函数名之后:
func greet1 -> String { return /"howdy/" }
显然,函数可以既没有返回值,也没有参数;如下代码的含义是一样的:
func greet1 -> Void { print(/"howdy/") }func greet2 -> { print(/"howdy/") }func greet3 { print(/"howdy/") }
就像不能省略函数声明中的圆括号(参数列表)一样,你也不能省略函数调用中的圆括号。如果函数没有参数,那么这些圆括号就是空的,但必须要有。比如:
greet1
注意上述代码中的圆括号!
2.1.2 函数签名
如果省略函数声明中的参数名,那就完全可以通过输入与输出的类型来描述一个函数了,比如,下面这个表达式:
(Int, Int) -> Int
事实上,这在Swift中是合法的表达式。它是函数签名。在该示例中,它表示的是sum函数的签名。当然,还可能有其他函数也接收两个Int参数并返回一个Int,这就是要点。该签名描述了所有接收这些类型、具有这些数量的参数,并返回该类型结果的函数。实际上,函数的签名是其类型——函数的类型。稍后将会看到,函数拥有类型是非常重要的。
函数的签名必须要包含参数列表(无参数名)与返回类型,即便其中一样或两者都为空亦如此;因此,不接收参数且无返回值的函数签名可以有4种等价的表示方式,包括Void->Void与()->()。