Swift — 函数(Functions)

在 Swift 中,函数是一等公民。这意味着函数不仅可以用来执行一系列指令,还可以作为参数传递给其他函数,作为返回值从其他函数中返回,甚至可以嵌套和捕获周围作用域中的变量。下面是 Swift 中函数的详细介绍和使用示例。

官方文档

定义和调用函数 #

基本定义和调用 #

你可以使用关键词 func 来定义一个函数:

func greet(name: String) -> String {
    return "Hello, \(name)!"
}

let greeting = greet(name: "Alice")
print(greeting)  // 输出: "Hello, Alice!"

这里 greet 函数接受一个参数 name,返回一个 String。你可以通过传入实际参数来调用该函数。

多个参数和参数标签 #

函数可以接受多个参数。可以使用参数标签使函数调用时更加清晰:

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

let result = sum(a: 5, b: 3)
print(result)  // 输出: 8

还可以定义外部参数标签,使调用更具可读性:

func join(string s1: String, with s2: String, separator: String) -> String {
    return s1 + separator + s2
}

let joinedString = join(string: "Hello", with: "World", separator: ", ")
print(joinedString)  // 输出: "Hello, World"

_ 下划线可以用来省略外部参数名。

井号 # 的使用:参数标签省略(不推荐) #

在 Swift 中,函数参数可以使用 # 井号前缀,这种做法称为自动外部参数名。在这种情况下,调用函数时会使用参数名作为标签,而无需在调用位置显式地写出参数标签(即外部参数)。这种特性使得代码更加简洁和可读。

假设有一个函数需要两个参数,可以将参数标签前缀加 #,如下所示:

func greet(#name: String, #age: Int) {
    print("Hello, \(name)! You are \(age) years old.")
}

greet(name: "Alice", age: 30)
// 输出: Hello, Alice! You are 30 years old.

在这个示例中,greet 函数的参数 nameage 都使用了 # 前缀。这意味着在调用函数时,可以直接使用参数名,使代码更具可读性。我们可以对比没有 # 情况的函数:

func sum(_ a: Int, _ b: Int) -> Int {
    return a + b
}

let result = sum(10, 20)
print(result)  // 输出: 30

在这个没有参数标签的函数调用中,读者无法通过参数本身直接知道每个参数的含义。实际上,Swift 5.3之后,参数标签使用 # 的行为已经发生了变化。参数标签省略和参数标签的默认行为有所不同。以下是 Swift 5.3 和以后版本中,函数参数标签的推荐和默认使用方式:

// 推荐的参数标签和参数名不同使用方式
func greet(person name: String, from hometown: String) {
    print("Hello, \(name)! Glad you could visit from \(hometown).")
}

greet(person: "Alice", from: "New York")

在 Swift 当前版本中,虽然早期版本允许直接使用 # 井号作为参数标签前缀,但在更现代的版本(Swift 5.3 以后),为了避免可能的混淆和清楚起见,通常情况下推荐的做法是显式地定义参数标签和参数名以确保代码的可读性和清晰度。

如果你仍在使用较早版本的 Swift 编译器,你可以继续使用 # 井号前缀,但更新的代码库中推荐使用参数标签。希望这能帮你理解函数参数标签的使用。

默认参数值 #

可以为参数指定默认值,这样调用函数时可以省略这些参数:

func greet(name: String = "Guest") -> String {
    return "Hello, \(name)!"
}

print(greet())  // 输出: "Hello, Guest!"
print(greet(name: "Bob"))  // 输出: "Hello, Bob!"

返回值和多重返回值 #

单一返回值 #

函数可以返回一个值,使用 -> 符号来指定返回类型:

func square(number: Int) -> Int {
    return number * number
}

let squaredValue = square(number: 4)
print(squaredValue)  // 输出: 16

多重返回值 #

可以使用元组返回多个值:

func minMax(array: [Int]) -> (min: Int, max: Int)? {
    guard let minElement = array.min(), let maxElement = array.max() else {
        return nil
    }
    return (minElement, maxElement)
}

if let bounds = minMax(array: [1, 2, 3, 4, 5]) {
    print("min: \(bounds.min), max: \(bounds.max)")
}

函数参数和返回函数 #

参数为函数 #

函数可以接受其他函数作为参数:

func applyOperation(_ a: Int, _ b: Int, operation: (Int, Int) -> Int) -> Int {
    return operation(a, b)
}

let result = applyOperation(4, 2, operation: { $0 * $1 })
print(result)  // 输出: 8

返回函数 #

函数也可以返回其他函数:

func makeIncrementer(amount: Int) -> (Int) -> Int {
    func incrementer(value: Int) -> Int {
        return value + amount
    }
    return incrementer
}

let incrementByFive = makeIncrementer(amount: 5)
let result = incrementByFive(10)
print(result)  // 输出: 15

内嵌函数和闭包 #

内嵌函数 #

内嵌函数可以捕获其外围函数中定义的变量和常量:

func outerFunction() -> () -> Int {
    var total = 0
    func innerFunction() -> Int {
        total += 1
        return total
    }
    return innerFunction
}

let incrementer = outerFunction()
print(incrementer())  // 输出: 1
print(incrementer())  // 输出: 2

闭包 #

闭包是可以在代码中传递和引用的自包含代码块。你可以使用 {} 来定义闭包:

let numbers = [1, 2, 3, 4, 5]
let doubledNumbers = numbers.map { $0 * 2 }
print(doubledNumbers)  // 输出: [2, 4, 6, 8, 10]

可变参数 #

函数可以接受可变数量的参数:

func sum(of numbers: Int...) -> Int {
    return numbers.reduce(0, +)
}

let result = sum(of: 1, 2, 3, 4, 5)
print(result)  // 输出: 15

输入输出参数和关键字 inout #

在 Swift 中,inout 关键字用于函数参数,表示该参数是一个传入和传出参数,也就是说,这个参数允许在函数内部被修改,并且这种修改会在函数返回后保留。换句话说,使用 inout 参数,你可以在函数内部修改传入的变量,并且这些修改会反映到函数的调用者。

func swapValues(_ a: inout Int, _ b: inout Int) {
    let temp = a
    a = b
    b = temp
}

var x = 3
var y = 5
swapValues(&x, &y)
print(x, y)  // 输出: 5 3

使用 inout 参数的场景 #

inout 参数最适用于需要在函数内直接修改调用者传入的变量的场景。比如,对数组进行排序或对变量进行交换等操作。

如何使用 inout 参数 #

  1. 使用 inout 关键字标记参数。
  2. 在调用函数时使用 & 操作符传递变量。

下面是一些使用 inout 参数的示例:

示例 1:交换两个变量的值 #

func swapTwoInts(_ a: inout Int, _ b: inout Int) {
    let temporaryA = a
    a = b
    b = temporaryA
}

var x = 5
var y = 10
print("Before swap: x = \(x), y = \(y)")  // 输出: Before swap: x = 5, y = 10

swapTwoInts(&x, &y)
print("After swap: x = \(x), y = \(y)")  // 输出: After swap: x = 10, y = 5

在这个例子中,swapTwoInts 函数接受两个 inout 参数。由于使用了 inout,函数内部对 ab 的修改会反映到调用者传入的 xy 上。

示例 2:对数组进行排序 #

你可以使用 inout 参数实现一个简单的排序函数(如冒泡排序)。

func bubbleSort(_ array: inout [Int]) {
    let n = array.count
    for i in 0..<n {
        for j in 1..<n-i {
            if array[j-1] > array[j] {
                let temp = array[j-1]
                array[j-1] = array[j]
                array[j] = temp
            }
        }
    }
}

var numbers = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5]
print("Before sort: \(numbers)")  // 输出: Before sort: [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5]

bubbleSort(&numbers)
print("After sort: \(numbers)")  // 输出: After sort: [1, 1, 2, 3, 3, 4, 5, 5, 5, 6, 9]

在这个例子中,bubbleSort 函数接受一个 inout 数组。数组中的元素在函数内部被排序,并且调用者变量 numbers 内容也会反映排序结果。

注意事项 #

  1. 声明和调用:

    • inout 参数在函数定义时使用 inout 关键字。
    • 在函数调用时,传入的变量前需要加上 & 符号。
  2. 传递变量:

    • 只能传递变量,不能传递常量、字面量或表达式。
    var a = 10
    var b = 20
    swapTwoInts(&a, &b)   // 合法
    // swapTwoInts(&10, &20) // 非法:不能传递字面量
    
  3. 值类型:

    • inout 参数主要用于值类型(如结构体和枚举)。引用类型(如类)的引用本身会传递给函数,因此无需使用 inout 进行修改。
  4. 避免混淆:

    • inout 参数在函数内被强制转为变量(var),意味着它的修改是直接的。例如,不能对一个 inout 变量同时多次传递。
    var value = 100
    
    // 多次传递同一个 `inout` 参数会引起编译错误
    // swapTwoInts(&value, &value) // 非法
    

小结 #

inout 参数用于让函数可以直接修改传入的变量。它通过 & 符号传递变量,并且修改在函数执行结束后会保留。这个特性能简化某些需要在函数内部修改参数的操作,增强代码的可读性和可维护性。在使用时需要注意它的限制和注意事项,以避免引起困扰。

总结 #

Swift 中的函数是功能强大且灵活的。你可以通过各种方式定义、调用和传递函数,还可以利用函数来构建简洁、高效、模块化的代码结构。希望以上内容能够帮助你全面理解并熟练掌握 Swift 中的函数使用。

闭包的函数体部分由关键字 in 引入。该关键字表示闭包的参数和返回值类型定义已经完成,闭包函数体即将开始。

本文共 2549 字,上次修改于 Dec 16, 2024