Go学习(六)function

在这里插入图片描述
在Go里面,函数是一等公民,这个是啥意思呢。
关于一等公民看看维基百科的定义:

In programming language design, a first-class citizen (also type, object, entity, or value) in a given programming language is an entity which supports all the operations generally available to other entities. These operations typically include being passed as an argument, returned from a function, modified, and assigned to a variable.

大意是说,在编程语言中,所谓一等公民,是指支持所有操作的实体, 这些操作通常包括作为参数传递,从函数返回,修改并分配给变量等。所以在Go中函数作为一等公民,可以把函数赋值给变量,也可以把函数作为其它函数的参数或者返回值。下面开始介绍下Go中的函数。

接下来会从一下几个方面来介绍Go中的函数。

  1. 一个简单的函数定义
  2. 函数可变长的入参
  3. 函数可以有多个返回值
  4. 函数可以当做参数传入
  5. 函数可以当做返回值
  6. 匿名函数
  7. 闭包

一个简单的函数定义

package main

import "fmt"

func test(a int, b int) (c int) {
	return a + b
}

func main() {
	fmt.Println(test(1, 2))
}

一个非常的test函数的定义,两个int类型的参数,加上一个int类型的返回值,也别的语言没有太大的差别。

函数可变长的入参

package main

import "fmt"

func test(a int, b ...int) (c int) {
	var sum int = a
	for _, item := range b {
		sum = sum + item
	}
	return sum
}

func main() {
	fmt.Println(test(1, 2, 3))
}

上面test函数中,有一个可变长的参数b,可以接受0个或者多个的入参,可以通过类似遍历列表的方式读取每一个值。这里要注意,可变长度的入参,是要作为后一个参数的。

函数可以有多个返回值

上面的这些函数的特性,和别的编程语言差不多,那下面这个特性,就是在其他编程语言中比较少见了。Go函数支持有多个返回值。如下:

package main

import "fmt"

func test(a int, b int) (sum int, sub int) {
	return a + b, a - b
}

func main() {
	sum, sub := test(1, 2)
	fmt.Printf("sum : %v , sub : %v\n", sum, sub)
}

上面的一个test函数,在返回值里面,返回了和 以及 差 两个返回值。这个特性还是有用的。Go函数中,异常的处理是通过返回相应的error,所以这个时候一个函数的返回值一般会包含正常的返回值以及error,如下:

package main

import (
	"errors"
	"fmt"
)

func div(a int, b int) (result float32, err error) {
	if b == 0 {
		return 0, errors.New("invalid param , b can't be zero")
	}
	return float32(a) / float32(b), nil
}

func test(a int, b int) {
	result, err := div(a, b)
	if err == nil {
		fmt.Println(result)
	} else {
		fmt.Println("error")
	}
}

func main() {
	test(1, 2) // 0.5
	test(1, 0) // error
}

一个返回值result,作为正常的函数返回值,另外一个是error,作为异常返回。

函数可以当做参数传入

函数,和其他类型的数据一样,都可以当做变量传入到函数中,如下:

package main

import "fmt"

func add(a int, b int) (resp int) {
	return a + b
}

func sub(a int, b int) (resp int) {
	return a - b
}

func cal(a int, b int, f func(int, int) int) (resp int) {
	return f(a, b)
}

func main() {
	fmt.Println(cal(1, 2, add))
	fmt.Println(cal(1, 2, sub))
}

函数可以当做返回参数

函数,和其他类型的数据一样,被函数当做一种返回值,如下:

package main

import "fmt"

func add(a int, b int) (resp int) {
	return a + b
}

func sub(a int, b int) (resp int) {
	return a - b
}

func getFunc(funcType string) func(int, int) int {
	if "add" == funcType {
		return add
	} else if "sub" == funcType {
		return sub
	} else {
		return nil
	}
}

func main() {
	fmt.Println(getFunc("add")(1, 2))   // 3
	fmt.Println(getFunc("sub")(1, 2))   // -1
	fmt.Println(getFunc("other")(1, 2)) // panic: runtime error: invalid memory address or nil pointer dereference
}

函数作为返回值,我们在闭包的场景下,会看到更加有意义的实际用途。

匿名函数

匿名函数,顾名思义,就是一个没有名字的函数。我们把上面的例子,改为一个匿名函数的实现方式。

package main

import "fmt"

func getFunc(funcType string) func(int, int) int {
	if "add" == funcType {
		return func(a int, b int) (resp int) {
			return a + b
		}
	} else if "sub" == funcType {
		return func(a int, b int) (resp int) {
			return a - b
		}
	} else {
		return nil
	}
}

func main() {
	fmt.Println(getFunc("add")(1, 2))   // 3
	fmt.Println(getFunc("sub")(1, 2))   // -1
	fmt.Println(getFunc("other")(1, 2)) // panic: runtime error: invalid memory address or nil pointer dereference
}

使用匿名函数的好处,一方面是减少定义函数名字的过程,另外一个用处,会在闭包中体现。

闭包

什么是闭包?摘用Wikipedia上的一句定义:

a closure is a record storing a function together with an environment.
闭包是由函数和与其相关的引用环境组合而成的实体 。

因此闭包的核心就是:函数和环境。
再来看一个官方的解释:

Function literals are closures: they may refer to variables defined in a surrounding function. Those variables are then shared between the surrounding function and the function literal, and they survive as long as they are accessible.

翻译过来

函数字面量(匿名函数)是闭包:它们可以引用在周围函数中定义的变量。然后,这些变量在周围的函数和函数字面量之间共享,只要它们还可以访问,它们就会继续存在。

闭包是由函数及其相关引用环境组合而成的实体。闭包 = 函数 + 引用环境
简而言之,闭包,首先是一个函数,其次还有引用了一些上下文的信息。下面我们来看几个有意思的例子。

斐波那契生成器

package main

import "fmt"

func makeFibGen() func() int {
	f1 := 1
	f2 := 1
	return func() int {
		f2, f1 = (f1 + f2), f2
		return f1
	}
}

func main() {
	gen1 := makeFibGen()
	fmt.Println(gen1()) // 1
	fmt.Println(gen1()) // 2
	fmt.Println(gen1()) // 3

	gen2 := makeFibGen()
	fmt.Println(gen2()) // 1
	fmt.Println(gen2()) // 2
	fmt.Println(gen2()) // 3
}

在上面的这个例子中,我们在makeFibGen里定义了一个匿名函数,并且把这个匿名函数当做结果返回了出去。这里面比较特别的地方是,匿名函数引用了makeFibGen的两个局部的变量 f1,f2。但是当makeFibGen结束后,匿名函数还是可以正常访问到这两个局部的变量。

所以这里返回的就是一个闭包,对应的函数就是匿名的函数,上下文就是f1,f2这两个变量。而且每一次生成闭包的时候,对应的f1,f2会重新赋值,而且两个闭包的上下文的数据是隔离的。

包装函数

之前看到一个非常有意思的例子。平时我们想记录一个函数的调用时间。类似下面我们已经完成了的一个http请求的处理函数。

package main

import (
  "fmt"
  "net/http"
)

func main() {
  http.HandleFunc("/hello", hello)
  http.ListenAndServe(":3000", nil)
}

func hello(w http.ResponseWriter, r *http.Request) {
  fmt.Fprintln(w, "<h1>Hello!</h1>")
}

这个时候,如果我们想记录这个处理函数的执行时间,我们可以使用闭包的方式进行包装。

package main

import (
  "fmt"
  "net/http"
  "time"
)

func main() {
  http.HandleFunc("/hello", timed(hello))
  http.ListenAndServe(":3000", nil)
}

func timed(f func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) {
  return func(w http.ResponseWriter, r *http.Request) {
    start := time.Now()
    f(w, r)
    end := time.Now()
    fmt.Println("The request took", end.Sub(start))
  }
}

func hello(w http.ResponseWriter, r *http.Request) {
  fmt.Fprintln(w, "<h1>Hello!</h1>")
}

我们使用times函数,对hello函数进行包装,返回一个相同函数签名闭包函数。在这个闭包函数中,执行对应的业务函数。在执行业务函数的前后,分别做了额外的处理进行时间的记录。这个还是一个非常有意思的例子。

结语

今天学习了函数,后面继续继续努力。

参考文章

https://www.calhoun.io/5-useful-ways-to-use-closures-in-go/
https://juejin.cn/post/7140664403996868615
https://zhuanlan.zhihu.com/p/92634505

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值