[Golang] 函数

Go语言中的函数特性:

  • 函数本身可以作为值进行传递
  • 支持匿名函数和闭包(closure)
  • 函数可以满足接口

1.利用函数进行链式处理

利用函数实现对list中string类型的“去go”、“去空格”、“专为大写”

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
package main

import (
"fmt"
"strings"
)

func StringProcess(list []string, chain []func(string) string) {
// list and StringProcess chain
for index, str := range list {
result := str
for _, proc := range chain {
result = proc(result)
}
list[index] = result
}
}

func removePrefix(str string) string {
return strings.TrimPrefix(str, "go")
}

func main() {
list := []string{
"go scanner",
"go parser",
"go compiler",
"go printer",
"go formater",
}

chain := []func(string) string{
removePrefix,
strings.TrimSpace,
strings.ToUpper,
}

StringProcess(list, chain)
for _, str := range list {
fmt.Println(str)
}
}

函数StringProcess(list []string, chain []func(string string)中分别将string类型与函数类型作为参数,相对C这是船新的操作。

2.匿名函数

func(参数列表) (返回参数列表) {
body
}

2.1 在定义时调用匿名函数

1
2
3
func(data int) {
fmt.Println(data)
} (100)

2.2 将匿名函数用作回调函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package main

import "fmt"

func visit(list []int, f func(int2 int)) {
for _, v := range list {
f(v)
}
}

func main() {
visit([]int{1, 2, 3, 4}, func(v int) {
fmt.Println(v)
})
}

visit()函数将遍历过程封装,当要获取遍历期间的切片值时,只需要给visit()传入一个回调参数即可。

2.3使用匿名函数实现封装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package main

import (
"flag"
"fmt"
)

var skillParam = flag.String("skill", "", "skill to perform")

func main() {
flag.Parse()
var skill = map[string]func(){
"fire": func() {
fmt.Println("chicken fire")
},
"run": func() {
fmt.Println("solier run")
},
"fly": func() {
fmt.Println("angel fly")
},
}
if f, ok := skill[*skillParam]; ok {
f()
} else {
fmt.Println("skill not found")
}
}

这段代码将匿名函数作为map的value,通过命令行参数动态调用匿名函数。

2.4函数作为接口来使用

结构体实现接口

golang中的其他类型都可以实现接口,函数也可以,下文将分别对比结构体与函数实现接口的过程
以接口invoker为例:

type Invoker interface {
Call(interface{})
}

这个接口需要实现Call()方法,调用时会传入一个interface{}类型的变量,这种类型的变量表示任意类型的值。
以下为结构体进行接口的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package main

import "fmt"

type Invoker interface {
Call(interface{})
}

type Struct struct{}

func (s *Struct) Call(p interface{}) {
fmt.Println("from struct", p)
}
func main() {
var invoker Invoker
s := new(Struct)
s.Call("hello")
invoker = s //将struct s传入invoker的interface
invoker.Call("hello")
}

输出为:

from struct hello
from struct hello

重点在invoker = s中,由于s为Struct类型的指针,且已经对应实现了Call()方法,即已经实现了Invoker接口类型,当赋值时invoker接收了一个结构体作为值。

函数体实现接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package main

import "fmt"

//调用器接口
type Invoker interface {
Call(interface{})
}
//函数定义为类型
type FuncCaller func(interface{})

//实现invoker的
func (f FuncCaller) Call(p interface{}) {
f(p)
}

func main() {
var invoker Invoker

//将匿名函数转为FuncCaller类型,再赋值给接口
invoker = FuncCaller(func(v interface{}) {
fmt.Println("from function", v)
})

invoker.Call("hello")
}

func (f FuncCaller) Call(p interface{}) {}中的Call()方法将实现Invoker中的Call()方法(还未传递),但FuncCaller的Call()方法被调用与func(interface{})无关,还需要通过f(p)手动调用函数本体。

1
2
3
invoker = FuncCaller(func(v interface{}) {
fmt.Println("from function", v)
})

这段将匿名函数转换为FuncCaller类型(函数签名才能转换),此时FuncCaller类型实现了INVOKER的Call()方法,赋值给invoker接口是成功的。
函数与结构体实现接口不同:

  • 分别将函数与结构体定义为类型type
  • 接口传入的分别是函数和结构体
  • 中间部分体会需要加深

HTTP包中的例子

HTTP包中有包含Handler接口定义,代码如下:

1
2
3
type Handler interface{
ServeHTTP(ResponseWriter, *Request)
}
  1. Handler用于定义每个HTTP的请求和响应的处理过程,可以使用处理函数实现接口,如下:
1
2
3
4
5
type HandlerFunc func(ResponseWriter, *Request)

func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}
  1. 要使用闭包实现默认的HTTP请求处理,可以使用http.HandleFunc()函数,函数定义:
1
2
3
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
DefaultServeMux.HandleFunc(Pattern, handler)
}

DefaultServeMux是ServeMux结构,拥有HandleFunc()方法,定义如下:

1
2
3
func (mux *ServeMux) HandlerFunc(pattern string, handler func(ResponseWriter, *Request)) {
mux.Handle(pattern, HandlerFunc(handler))
}

上面代码将外部传入的函数handler()转为HandlerFunc类型,HandlerFunc类型实现了Handler的ServeHTTP方法,底层可以同时使用各种类型来实现Handler接口进行处理。

2.5函数闭包

闭包是引用了“自由变量”的函数,被引用的自由变量和函数一同存在,即使已经离开了自由变量的环境也不会被释放或者删除,在闭包中可以继续使用这个自由变量,简单的说:

函数+引用环境=闭包

函数是编译期静态的概念,闭包是运行期动态的概念。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main

import "fmt"

func char() func(x int32) string {
var str string = "hello, "
return func(x int32) string {
str += string(x)
return str
}
}

func main() {
f := char()
fmt.Println("1 ", f('a'))
fmt.Println("2 ", f('b'))
fmt.Println("3 ", f('c'))
}

运行结果:

1 hello, a
2 hello, ab
3 hello, abc

个人理解,闭包其实就是利用栈的原理,通过实例化内部函数来保存局部变量。

2.6 可变参数

func function(static variables, v ...T) (r) {}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package main

import (
"bytes"
"fmt"
)

func printTypeValue(slist ...interface{}) string {
var b bytes.Buffer
for _, s := range slist {
str := fmt.Sprintf("%v", s)
var typeString string
switch s.(type) {
case bool:
typeString = "bool"
case string:
typeString = "string"
case int:
typeString = "int"
}

b.WriteString("value: ")
b.WriteString(str)
b.WriteString(" type: ")
b.WriteString(typeString)
b.WriteString("\n")
}
return b.String()
}

func main() {
fmt.Println(printTypeValue(100, "str", true))
}

value: 100 type: int
value: str type: string
value: true type: bool