# 函数(Functions) 本页包含内容: - 函数定义与调用 - 函数参数与返回值 - 函数参数名称 - 函数类型 - 函数嵌套 函数是用来完成特定任务的独立的代码块。你给一个函数起一个合适的名字,用来标示函数做什么,并且当函数需要执行的时候,这个名字会被“调用”。 Swift统一的函数语法足够灵活,可以用来表示任何函数,包括从最简单的没有参数名字的C风格函数,到复杂的带局部和外部参数名的Objective-C风格函数。参数可以提供默认值,以简化函数调用。参数也可以即当做传入参数,也当做传出参数,也就是说,一旦函数执行结束,传入的参数值可以被修改。 在Swift中,每个函数都有一种类型,包括函数的参数值类型和返回值类型。你可以把函数类型当做任何其他普通变量类型一样处理,这样就可以更简单地把函数当做别的函数的参数,也可以从其他函数中返回函数。函数的定义可以写在在其他函数定义中,这样可以在嵌套函数范围内实现功能封装。 ## 函数的定义与调用 当你定义一个函数时,你可以定义一个或多个有名字和类型的值,作为函数的输入(称为参数,parameters),也可以定义某种类型的值作为函数执行结束的输出(称为返回类型)。 每个函数有个函数名,用来描述函数执行的任务。要使用一个函数时,你用函数名“调用”,并传给它匹配的输入值(称作实参,arguments)。一个函数的实参必须与函数参数表里参数的顺序一致。 在下面例子中的函数叫做`"greetingForPerson"`,之所以叫这个名字是因为这个函数用一个人的名字当做输入,并返回给这个人的问候语。为了完成这个任务,你定义一个输入参数-一个叫做`personName`的`String`值,和一个包含给这个人问候语的`String`类型的返回值: func sayHello(personName: String) -> String { let greeting = "Hello, " + personName + "!" return greeting } 所有的这些信息汇总起来成为函数的定义,并以`func`作为前缀。指定函数返回类型时,用返回箭头`->`(一个连字符后跟一个右尖括号)后跟返回类型的名称的方式来表示。 该定义描述了函数做什么,它期望接收什么和执行结束时它返回的结果是什么。这样的定义使的函数可以在别的地方以一种清晰的方式被调用: println(sayHello("Anna")) // prints "Hello, Anna!" println(sayHello("Brian")) // prints "Hello, Brian! 调用`sayHello`函数时,在圆括号中传给它一个`String`类型的实参。因为这个函数返回一个`String`类型的值,`sayHello`可以被包含在`println`的调用中,用来输出这个函数的返回值,正如上面所示。 在`sayHello`的函数体中,先定义了一个新的名为`greeting`的`String`常量,同时赋值了给`personName`的一个简单问候消息。然后用`return`关键字把这个问候返回出去。一旦`return greeting`被调用,该函数结束它的执行并返回`greeting`的当前值。 你可以用不同的输入值多次调用`sayHello`。上面的例子展示的是用`"Anna"`和`"Brian"`调用的结果,该函数分别返回了不同的结果。 为了简化这个函数的定义,可以将问候消息的创建和返回写成一句: func sayHelloAgain(personName: String) -> String { return "Hello again, " + personName + "!" } println(sayHelloAgain("Anna")) // prints "Hello again, Anna! ## 函数参数与返回值 函数参数与返回值在Swift中极为灵活。你可以定义任何类型的函数,包括从只带一个未名参数的简单函数到复杂的带有表达性参数名和不同参数选项的复杂函数。 ### 多重输入参数 函数可以有多个输入参数,写在圆括号中,用逗号分隔。 下面这个函数用一个半开区间的开始点和结束点,计算出这个范围内包含多少数字: func halfOpenRangeLength(start: Int, end: Int) -> Int { return end - start } println(halfOpenRangeLength(1, 10)) // prints "9 ### 无参函数 函数可以没有参数。下面这个函数就是一个无参函数,当被调用时,它返回固定的`String`消息: func sayHelloWorld() -> String { return "hello, world" } println(sayHelloWorld()) // prints "hello, world 尽管这个函数没有参数,但是定义中在函数名后还是需要一对圆括号。当被调用时,也需要在函数名后写一对圆括号。 ### 无返回值函数 函数可以没有返回值。下面是`sayHello`函数的另一个版本,叫`waveGoodbye`,这个函数直接输出`String`值,而不是返回它: func sayGoodbye(personName: String) { println("Goodbye, \(personName)!") } sayGoodbye("Dave") // prints "Goodbye, Dave! 因为这个函数不需要返回值,所以这个函数的定义中没有返回箭头(->)和返回类型。 > 注意: > 严格上来说,虽然没有返回值被定义,`sayGoodbye`函数依然返回了值。没有定义返回类型的函数会返回特殊的值,叫`Void`。它其实是一个空的元组(tuple),没有任何元素,可以写成`()`。 被调用时,一个函数的返回值可以被忽略: func printAndCount(stringToPrint: String) -> Int { println(stringToPrint) return countElements(stringToPrint) } func printWithoutCounting(stringToPrint: String) { printAndCount(stringToPrint) } printAndCount("hello, world") // prints "hello, world" and returns a value of 12 printWithoutCounting("hello, world") // prints "hello, world" but does not return a value 第一个函数`printAndCount`,输出一个字符串并返回`Int`类型的字符数。第二个函数`printWithoutCounting`调用了第一个函数,但是忽略了它的返回值。当第二个函数被调用时,消息依然会由第一个函数输出,但是返回值不会被用到。 > 注意: > 返回值可以被忽略,但定义了有返回值的函数必须返回一个值,如果在函数定义底部没有返回任何值,这叫导致编译错误(compile-time error)。 ### 多重返回值函数 你可以用元组(tuple)类型让多个值作为一个复合值从函数中返回。 下面的这个例子中,`count`函数用来计算一个字符串中元音,辅音和其他字母的个数(基于美式英语的标准)。 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) } 你可以用`count`函数来处理任何一个字符串,返回的值将是一个包含三个`Int`型值的元组(tuple): let total = count("some arbitrary string!") println("\(total.vowels) vowels and \(total.consonants) consonants") // prints "6 vowels and 13 consonants 需要注意的是,元组的成员不需要在函数中返回时命名,因为它们的名字已经在函数返回类型有有了定义。 ## 函数参数名 以上所有的函数都给它们的参数定义了`参数名(parameter name)`: func someFunction(parameterName: Int) { // function body goes here, and can use parameterName // to refer to the argument value for that parameter } 但是,这些参数名仅在函数体中使用,不能在函数调用时使用。这种类型的参数名被称作`本地参数名(local parameter name)`,因为它们只能在函数体中使用。 ### 外部参数名 有时候,调用函数时,给每个参数命名是非常有用的,因为这些参数名可以指出各个实参的用途是什么。 如果你希望函数的使用者在调用函数时提供参数名字,那就需要给每个参数除了本地参数名外再定义一个`外部参数名`。外部参数名写在本地参数名之前,用空格分隔。 func someFunction(externalParameterName localParameterName: Int) { // function body goes here, and can use localParameterName // to refer to the argument value for that parameter } > 注意: > 如果你提供了外部参数名,那么函数在被调用时,必须使用外部参数名。 以下是个例子,这个函数使用一个`结合者(joiner)`把两个字符串联在一起: func join(s1: String, s2: String, joiner: String) -> String { return s1 + joiner + s2 } 当你调用这个函数时,这三个字符串的用途是不清楚的: join("hello", "world", ", ") // returns "hello, world 为了让这些字符串的用途更为明显,我们为`join`函数添加外部参数名: func join(string s1: String, toString s2: String, withJoiner joiner: String) -> String { return s1 + joiner + s2 } 在这个版本的`join`函数中,第一个参数有一个叫`string`的外部参数名和`s1`的本地参数名,第二个参数有一个叫`toString`的外部参数名和`s2`的本地参数名,第三个参数有一个叫`withJoiner`的外部参数名和`joiner`的本地参数名。 现在,你可以使用这些外部参数名以一种清晰地方式来调用函数了: join(string: "hello", toString: "world", withJoiner: ", ") // returns "hello, world 使用外部参数名让第二个版本的`join`函数的调用更为有表现力,更为通顺,同时还保持了函数体是可读的和有明确意图的。 > 注意: > 当其他人在第一次读你的代码,函数参数的意图显得不明显时,考虑使用外部参数名。如果函数参数名的意图是很明显的,那就不需要定义外部参数名了。 ### 简写外部参数名 如果你需要提供外部参数名,但是本地参数名已经定义好了,那么你不需要写两次这些参数名。相反,只写一次参数名,并用`井号(#)`作为前缀就可以了。这告诉Swift使用这个参数名作为本地和外部参数名。 下面这个例子定义了一个叫`containsCharacter`的函数,使用`井号(#)`的方式定义了外部参数名: func containsCharacter(#string: String, #characterToFind: Character) -> Bool { for character in string { if character == characterToFind { return true } } return false } 这样定义参数名,使得函数体更为可读,清晰,同时也可以以一个不含糊的方式被调用: let containsAVee = containsCharacter(string: "aardvark", characterToFind: "v") // containsAVee equals true, because "aardvark" contains a "v” ### 默认参数值 你可以在函数体中为每个参数定义`默认值`。当默认值被定义后,调用这个函数时可以略去这个参数。 > 注意: > 将带有默认值的参数放在函数参数表的最后。这样可以保证在函数调用时,非默认参数的顺序是一致的,同时使得相同的函数在不同情况下调用时显得更为清晰。 以下是另一个版本的`join`函数,其中`joiner`有了默认参数值: func join(string s1: String, toString s2: String, withJoiner joiner: String = " ") -> String { return s1 + joiner + s2 } 像第一个版本的`join`函数一样,如果`joiner`被幅值时,函数将使用这个字符串值来连接两个字符串: join(string: "hello", toString: "world", withJoiner: "-") // returns "hello-world 当这个函数被调用时,如果`joiner`的值没有被指定,函数会使用默认值(" "): join(string: "hello", toString: "world", withJoiner: "-") // returns "hello-world ### 默认值参数的外部参数名 在大多数情况下,给带默认值的参数起一个外部参数名是很有用的。这样可以保证当函数被调用且带默认值的参数被提供值时,实参的意图是明显的。 为了使定义外部参数名更加简单,当你未给带默认值的参数提供外部参数名时,Swift会自动提供外部名字。此时外部参数名与本地名字是一样的,就像你已经在本地参数名前写了`井号(#)`一样。 下面是`join`函数的另一个版本,这个版本中并没有为它的参数提供外部参数名,但是`joiner`参数依然有外部参数名: func join(s1: String, s2: String, joiner: String = " ") -> String { return s1 + joiner + s2 } 在这个例子中,Swift自动为`joiner`提供了外部参数名。因此,当函数调用时,外部参数名必须使用,这样使得参数的用途变得清晰。 func join(s1: String, s2: String, joiner: String = " ") -> String { return s1 + joiner + s2 } > 注意: > 你可以用用`下划线(_)`