函数

228 阅读13分钟

函数

我们将程序中反复执行的代码封装到一个代码块中,这个代码块模仿了数学中的函数,具有函数名、参数和返回值。

Swift中的函数很灵活,它可以独立存在,即全局函数;也可以存在于别的函数中,即函数嵌套;还可以存在于类、结构体和枚举中,即方法。

使用函数

使用函数首先需要定义函数,然后在合适的地方调用该函数,函数的语法格式如下:

func 函数名(参数列表) -> 返回值类型 {
语句组
return 返回值
}

在Swift中定义函数时,关键字是func,函数名需要符合标识符规范;多个参数列表之间可以用逗号(,)分隔,极端情况下可以没有参数。

在参数列表后使用箭头“->”指示返回值类型。返回值有单个值和多个值,多个值返回可以使用元组类型实现。如果函数没有返回值,则“->返回值类型”部分可以省略。对应地,如果函数有返回值,就需要在函数体最后使用return语句将计算的值返回;如果没有返回值,则函数体中可以省略return语句。

print("--函数--")

func rectangleArea(width:Double, height:Double) -> Double {
    let area = width * height
    return area
}

print("320 * 480的长方形的面积是:\(rectangleArea(320, height:480))")

传递参数

Swift中的函数很灵活,具体体现在传递参数有多种形式。

使用外部参数名

如果我们定义的函数有很多参数,它们又具有相同的数据类型,如果没有清晰的帮助说明,调用者很难知道参数的含义是什么。

为了提高程序的可读性,我们可以为函数中的参数提供一个外部参数名。以上面函数例子分析:

参数width和height是函数名的一部分,但是它们只能在函数的内部使用,称为局部参数名。还可以为每个参数提供一个可以在函数外部使用的名称,称为外部参数名。

修改如下:

func rectangleArea_(W width:Double, H height:Double) -> Double {
    let area = width * height
    return area
}

在局部参数名之前给一个外部参数名,用空格分隔。定义代码中的W和H就是外部参数名。

调用代码如下:

print("320 * 480的长方形的面积:\(rectangleArea_(W:320, H: 480))")

如果提供了外部参数名,那么在函数调用时,必须使用外部参数名,所以W和H不能省略。

参数默认值

在定义函数的时候可以为参数设置一个默认值,当调用函数的时候可以忽略该参数。

print("---参数默认值--")

func makecoffee(type : String = "卡布奇诺") -> String {
    return "制作一杯\(type)咖啡。"
}

let coffee1 = makecoffee("拿铁")
let coffee2 = makecoffee()
print(coffee1)
print(coffee2)

在参数列表中,默认值可以跟在参数的后面,通过等号赋值。

在调用的时候,如果调用者传递了参数,则是其传递过来的值,如果没有传递,则是这个默认值。

还可以为具有默认值的参数添加外部参数名,也可以使用下划线(_)指定外部参数名。

print("--为具有默认值的参数添加外部参数名--")

func CircleArea(R radius: Double = 30, _ pi: Double = 3.14) -> Double {
    let circleArea = radius * radius * pi
    return circleArea
}

print("圆面积:\(CircleArea(R : 50, 3.1415926))")

第一个参数有外部参数名R,而第二个参数的外部参数名是下划线(_),这样当调用该函数的时候,不需要提供第二个参数的外部参数名。

可变参数

Swift中函数的参数个数可以变化,它可以接受不确定数量的输入类型参数,它们具有相同的类型,有点像是传递一个数组。我们可以通过在参数类型名后面加入(...)的方式来指示这是可变参数。

print("--可变参数--")

func sum(numbers: Double...) -> Double {
    var total: Double = 0
    for number in numbers {
        total += number
    }
    return total
}

print(sum(100, 2, 30.0))
print(sum(30, 80))

参数的传递引用

之前提过,参数传递方式有两种:值类型和引用类型。值类型给函数传递的是参数的一个副本,这样在函数的调用过程中不会影响原始数据。引用类型是把本身数据传递过去,这样在函数的调用过程中会影响原始数据。

在众多数据类型中,只有类是引用类型,其他的数据类型如整型、浮点型、布尔型、字符串、元组、集合、枚举和结构体全部是值类型。

如果一定要将一个值类型参数作为引用传递,也是可以实现的,Swift提供的inout关键字就可以实现。

print("--参数的传递引用---")

func increment(inout value:Double, amount:Double = 1.0) {
    value += amount
}

var value : Double = 10.0

increment(&value)
print(value)//11.0

increment(&value, amount: 100.0)
print(value)//111.0

定义了increment函数,这个函数可以计算一个数值的增长,第一个参数value是需要增长的数值,它被设计为inout类型,inout标识的参数被称为输入输出参数,不能使用var或let标识。第二个参数amount是增长量,它的默认值是1.0。函数没有声明返回值类型,函数体中不需要return语句,事实上要返回的数据已经通过参数value传递回来,没有必要通过返回值返回了。

var value : Doubel = 10.0声明并初始化了Double类型变量value,由于在函数调用过程中需要修改它,因此不能声明为常量。

&value(在变量前面加&符号)是传递引用方式,它在定义函数时,参数标识与inout是相互对应的。

函数返回值

Swiftg中函数的返回值也是比较灵活的,形式主要有3种:无返回值、单一返回值和多返回值。

无返回值函数

有的函数只是为了处理某个过程,或者要返回的数据要通过inout类型参数传递出来,这时可以将函数设置为无返回值。所谓无返回结果,事实上是Void类型,即表示没有数据的类型。

无返回值函数的语法格式有如下3种形式:

  • func 函数名(参数列表) {
    语句组
    }

  • func 函数名(参数列表) -> () {
    语句组
    }

  • func 函数名(参数列表) ->Void {
    语句组
    }

无返回值函数不需要“return返回值”语句,第一个语法格式很彻底,参数列表后面没有箭头和类型,第二个语法格式->(),表示返回类型是空的元组。第三个语法格式->Void,表示返回类型是Void类型。

print("--无返回值函数--")

func increment_(inout value:Double, amount:Double = 1.0) {
    value += amount
}

func increment__(inout value:Double, amount:Double = 1.0) {
    value += amount
}

func increment___(inout value:Double, amount:Double = 1.0) {
    value += amount
}

多返回值函数

有时我们需要函数返回多个值,这可以通过两种方式来实现。一种是在函数定义的时候,将函数的多个参数声明为引用类型传递,这样当函数调用结束时,这些参数的值就变化了。另一种是将返回定义为元组类型。

print("--多返回值函数--")

func position(dt:Double, speed:(x:Int, y:Int)) -> (x:Int, y:Int) {
    var posx:Int = speed.x * Int(dt)
    var posy:Int = speed.y * Int(dt)
    
    return (posx, posy)
}

let move = position(60.0, speed: (10, -5))
print("物体位移:\(move.x), \(move.y)")//600, -300

函数类型

每个函数都有一个类型,使用函数的类型与使用其他数据类型一样,可以声明变量或常量,也可以作为其他函数的参数或者返回值。

有如下3个函数的定义:

print("--函数类型---")

//定义计算长方形年级函数
func rectangleArea1(width:Double, height:Double) -> Double {
    let area = width * height
    return area
}

//定义计算三角形面积函数
func triangleArea(bottom:Double, height:Double) -> Double {
    let area = 0.5 * bottom * height
    return area
}

func sayHello() {
    print("Hello, World!")
}

第一个函数和第二个函数,函数类型都是(Double, Double) -> Double,第三个函数的函数类型是() -> ()。

作为函数返回类型使用

我们可以把函数类型作为另一个函数的返回类型使用。

print("--作为函数返回类型使用--")

//定义计算长方形年级函数
func rectangleArea2(width:Double, height:Double) -> Double {
    let area = width * height
    return area
}

//定义计算三角形面积函数
func triangleArea1(bottom:Double, height:Double) -> Double {
    let area = 0.5 * bottom * height
    return area
}

func getArea(type : String) -> (Double, Double) -> Double {
    var returnFunction:(Double, Double) -> Double
    
    switch (type) {
    case "rect"://rect 表示长方形
        returnFunction = rectangleArea2
    case "tria" ://tria 表示三角形
        returnFunction = triangleArea1
    default:
        returnFunction = rectangleArea2
    }
    
    return returnFunction
}

//获得计算三角形面积函数
var area : (Double, Double) -> Double = getArea("tria")
print("底10 高15,三角形面积: \(area(10, 15))")


//获得计算长方形面积函数
area = getArea("rect")
print("宽10 高15,计算长方形面积: \(area(10, 15))")

作为参数类型使用

我们可以把函数类型作为另一个函数的参数类型使用。

print("---作为参数类型使用---")

//定义计算长方形面积函数
func rectangleArea3(width:Double, height:Double) -> Double {
    let area = width * height
    return area
}

//定义计算三角形面积函数
func triangleArea2(bottom:Double, height:Double) -> Double {
    let area = 0.5 * bottom * height
    return area
}

func getAreaByFunc(funcName : (Double, Double) -> Double, a: Double, b: Double) -> Double {
    var area = funcName(a,b)
    return area
}

//获得计算三角形面积函数
var result : Double = getAreaByFunc(triangleArea2, a: 10, b: 15)
print("底10 高15, 三角形面积: \(result)")

//获得计算长方形面积函数
result = getAreaByFunc(rectangleArea3, a: 10, b: 15)
print("宽10 高15, 计算长方形面积: \(result)")

函数重载

函数重载是指多个函数享有相同的名字但是函数类型必须不同的一组函数,它们互相构成重载关系。

提示:Swift的函数类型包括了参数列表类型和返回值类型,例如(Double, Double) -> Double参数类型是由两个Double类型参数列表和Double类型返回值构成的。也就是说,在Swift中函数名相同、参数列表不同或返回值类型不同的函数都可以构成重载。而在C++和Java等语言中,函数重载只与参数列表不同有关,与函数返回值无关。

print("--函数重载--")

func receive(i : Int) {
    print("接收一个Int类型数据 i = \(i)")
}

func receive(d : Double) {
    print("接收一个Double类型数据 d = \(d)")
}

func receive(x : Int, y : Int) {
    print("接收两个Int类型数据 x = \(x) y = \(y)")
}

func receive(i : Int) -> Int {
    print("接收一个Int类型数据 i = \(i), 返回值类型是Int")
    return i * i
}

let a1 : Int = receive(10)
let a2 : () = receive(10)
let a3 : Void = receive(10)
let a4 : () = receive(10.0)
let a5 : () = receive(10, y: 10)

运行结果:

--函数重载--
接收一个Int类型数据 i = 10, 返回值类型是Int
接收一个Int类型数据 i = 10
接收一个Int类型数据 i = 10
接收一个Double类型数据 d = 10.0
接收两个Int类型数据 x = 10 y = 10

嵌套函数

在此之前定义的函数都是全局函数,它们定义在全局作用域中,也可以把函数定义在另外的函数体中,称作嵌套函数。

func calculate(opr : String) -> (Int, Int) -> Int {
    //定义+函数
    func add(a:Int, b: Int) -> Int {
        return a + b
    }
    
    //定义-函数
    func sub(a: Int, b: Int) -> Int {
        return a - b
    }
    
    var result : (Int, Int) -> Int
    
    switch (opr) {
    case "+":
        result = add
    case "-":
        result = sub
    default:
        result = add
    }
    
    return result
    
}

let f1:(Int, Int) -> Int = calculate("+")
print("10 + 5 = \(f1(10, 5))")

let f2:(Int, Int) -> Int = calculate("-")
print("10 - 5 = \(f2(10, 5))")

上述代码定义了calculate函数,它的作用是根据运算符进行数学计算,参数opr是运算符,返回值是函数类型(Int, Int) -> Int。在calculate函数体内,定义了嵌套函数add和嵌套函数sub。

在函数嵌套中,默认情况下嵌套函数的作用域在外函数体内,但我们可以定义外函数的返回值类型为嵌套函数类型,从而将嵌套函数传递给外函数,被其他调用者使用。

泛型和泛型函数

泛型(generic)可以使我们在程序代码中定义一些可变的部分,在运行的时候指定。使用泛型可以最大限度地重用代码、保护类型的安全以及提高性能。在Swift集合类中,已经采用了泛型。

一个问题的思考

怎样定义一个函数来判断两个参数是否相等呢?

如果参数是Int类型,则函数定义如下:

print("---泛型和泛型函数--")

func isEqualsInt(a: Int, b: Int) -> Bool {
    return (a == b)
}

这个函数参数列表是两个Int类型,它只能比较两个Int类型参数是否相等。如果想比较两个Double类型是否相等,可以修改上面定义的函数如下:

func isEqualsDouble(a:Double, b:Double) -> Bool {
    return (a == b)
}

这个函数参数列表是两个Double类型,它只能比较两个Double类型参数是否相等。如果想比较两个String类型是否相等,可以修改上面定义的函数如下:

func isEqualsString(a:String, b:String) -> Bool {
    return (a == b)
}

以上分别对3种不同的类型进行了比较,定义了类似的3个函数。那么我们是否可以定义1个函数能够比较3种不同的类型呢?如果isEqualsInt、isEqualsDouble和isEqualsString这3个函数名字后面的Int、Double和String是可变的,那么这些可变部分是与参数类型关联的。

泛型函数

改造上面的函数,修改内容如下:

func isEquals<T>(a: T, b: T) -> Bool {
    return (a == b)
}

上面这个函数在编译时会有错误发生,这是因为并不是所有的类型都具有“可比性”,它们必须遵守Comparable协议实现类型。Comparable协议表示可比较的,在Swift中,基本数据类型以及字符串都是遵守Comparable协议的。

在函数名isEquals后面添加,参数的类型也被声明为T,T称为占位符,函数在每次调用时传入实际类型才能决定T所代表的类型。如果有多个不同类型,可以使用其他大写字母,一般情况下我们习惯于使用U字母,但是也可以使用其他的字母。多个占位符用逗号","分隔。例如:

func isEquals<T, U>(a: T, b: U) -> Bool {...}

修改代码如下:

func isEquals<T: Comparable>(a: T, b: T) -> Bool {
    return (a == b)
}

我们需要在T占位符后面添加冒号和协议类型,这种表示方式被称为泛型约束,它能够替换T的类型。

测试:

func isEquals<T: Comparable>(a: T, b: T) -> Bool {
    return (a == b)
}

let n1 = 200
let n2 = 100

print(isEquals(n1, b: n2))

let s1 = "ABC1"
let s2 = "ABC1"

print(isEquals(s1, b: s2))