09-函数

使用Go语言进行模块化编程,必须了解的知识主要包括几个重要的数据类型以一些模块化编程的技巧。

在Go语言中函数是一等公民,函数类型也是一等的数据类型。这是函数式编程的重要特征,Go语言在语言层面支持函数式编程。

函数的用途:

  1. 封装代码、分割功能、解耦逻辑
  2. 作为普通函数值,在其他函数之间传递、赋值变量、做类型判断和转换(就像切片或字典)

函数值可以成为被随意传播的独立逻辑组件(功能模块)

对于函数类型来说,它是一种对一组输入、输出进行模板化的重要工具,它比接口类型更加轻巧、灵活,它的值也借此变成了可被热替换的逻辑组件。

package main

import "fmt"

type Printer func(contents string) (n int, err error)       // 声明函数类型

func printToStd(contents string) (bytesNum int, err error) {    // 实现了函数类型Printer
    return fmt.Println(contents)
}

func main() {
    var p Printer
    p = printToStd
    p("something")
}

函数的签名包括:函数的参数列表和函数的结果列表,它定义了用来鉴别不同函数的特征,也定义了与函数的交互方式。

函数签名中元素顺序及类型一致,那么就是同一个函数,或是实现了同一个函数类型的函数

编写高阶函数

高阶函数满足的条件:

  1. 接收其他函数作为参数传入
  2. 把其他的函数作为结果返回

只要满足其中一个条件就是高阶函数,高阶函数也是函数式编程中重要的概念和特征

高阶函数可用于实现闭包。

需求:编写calculate函数来实现两个整数间的加减乘除运算,希望两个整数和具体的操作都由该函数的调用方给出。

例子:

接收其他的函数作为参数传入。

// 第一步:声明叫operator的函数类型
type operator func(x,y int) int

// 第二步:编写calculate函数
func  calculate(x int,y int, op operator)(int, error){
    if op == nil {      // 卫述语句,判断操作是否合法
        return 0 errors.New("invalid operation!")
    }
    return op(x,y),nil
}

// 实现operator,只要函数的签名与operator类型一致就是它的实现
op := func(x, y int) int {
    return x + y
}

func  main(){
    x,y = 12,23
    result,err := calculate(x,y,op))
}

卫述语句:被用来检查关键的先决条件的合法性,并在检查未通过的情况下立即终止当前代码块的执行的语句。Go语言中if语句常被作为卫述语句。

例子:

把其他的函数作为结果返回。

// 第一步:声明叫operator和calculateFunc的函数类型
type operator func(x,y int) int
type calculateFunc func(x int,y int)(int,err)

// 第二步:编写genCalculator函数
func genCalculator(op operate) calculateFunc{
    return func(x int,y int)(int, error){
        if op == nil{       // 卫述语句
            return 0, errors.New("invalid operation!")
        }
        return op(x,y),nil
    }
}

// 实现operator
op := func(x, y int) int {
    return x + y
}

func main(){
    x,y = 56,78
    add := genCalculator(op)
    result,err = add(x,y)
}

闭包

在一个函数中存在对外来标识符(既不代表当前函数的任何参数或结果,也不是函数内部声明的,而是直接从外边拿来的)的引用,也称为自由变量。这里的函数称为闭包函数,就是因为引用了自由变量,而呈现出了一种”不确定“的状态(开放状态)。

闭包体现的是由”不确定“变为”确定“的一个过程。对于Go这样的静态类型语言,在定义的闭包函数的时候,最多只能知道自由变量的类型。

例子:

func genCalculator(op operate) calculateFunc {
    return func(x int, y int) (int, error) {
        if op == nil {
            return 0, errors.New("invalid operation")
        }
        return op(x, y), nil
    }
}

genCalculator只做了一件事,定义一个匿名的calculateFunc类型的函数,并把它作为结果返回。这个匿名函数就是一个闭包函数。它使用的变量op既不代表它的任何参数或结果,也不是它自己声明的,而是定义它的genCalculator函数的参数,所以是一个自由变量(这个自由变量不是在定义闭包函数的时候确定的,而是在genCalculator函数被调用的时候确定的)。

只有给定了该函数的参数op,才知道闭包函数可用于声明运算

编译到if op == nill语句时,Go编译器上天寻找op所代表的东西,发现op代表的是genCalculator函数的参数,然后把这两者联系起来,此时自由变量op被捕获,这个闭包函数的状态由”不确定“变为”确定“。

闭包的用途:表面上是延迟实现了一部分程序逻辑或功能,实际上是在动态地生成那部分程序逻辑

传入函数的参数值

package main

import "fmt"

func main() {
    array1 := [3]string{"a", "b", "c"}
    fmt.Printf("The array: %v\n", array1)
    array2 := modifyArray(array1)
    fmt.Printf("The modified array: %v\n", array2)  // The modified array: [a x c]
    fmt.Printf("The original array: %v\n", array1)  // The original array: [a b c]
}

func modifyArray(a [3]string) [3]string {
    a[1] = "x"
    return a
}

原数组没有因为modifyArray()函数而改变,因为所有传给函数的参数值都会被复制,函数在其内部使用的并不是参数值的原值,而是它的副本。

数组是值类型,所以每次复制都会拷贝它,以及它的所有元素值。

对于引用类型(切片、字典、通道),复制值的时候,只会拷贝它本身,并不会拷贝它引用的底层数据。以切片为例,只是拷贝了它指向底层数组中某一个元素的指针,以及它的长度值和容量值,底层数组并不会被拷贝。

上次修改: 25 November 2019