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 mainimport ( "fmt" "strings" ) func StringProcess (list []string , chain []func (string ) string ) { 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 mainimport "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 mainimport ( "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 mainimport "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 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 mainimport "fmt" type Invoker interface { Call(interface {}) } type FuncCaller func (interface {}) func (f FuncCaller) Call (p interface {}) { f(p) } func main () { var invoker Invoker 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) }
Handler用于定义每个HTTP的请求和响应的处理过程,可以使用处理函数实现接口,如下:
1 2 3 4 5 type HandlerFunc func (ResponseWriter, *Request) func (f HandlerFunc) ServeHTTP (w ResponseWriter, r *Request) { f(w, r) }
要使用闭包实现默认的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 mainimport "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 mainimport ( "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