go语言函数声明
每一次函数在调用时都必须按照声明顺序为所有参数提供实参(参数值),在函数调用时,Go语言没有默认参数值,也没有任何方法可以通过参数名指定形参,因此形参和返回值的变量名对于函数调用者而言没有意义。
在函数中,实参通过值传递的方式进行传递,因此函数的形参是实参的拷贝,对形参进行修改不会影响实参,但是,如果实参包括引用类型,如指针、slice(切片)、map、function、channel 等类型,实参可能会由于函数的间接引用被修改。
调用函数
函数在定义后,可以通过调用的方式,让当前代码跳转到被调用的函数中进行执行,调用前的函数局部变量都会被保存起来不会丢失,被调用的函数运行结束后,恢复到调用函数的下一行继续执行代码,之前的局部变量也能继续访问。
函数内的局部变量只能在函数体中使用,函数调用结束后,这些局部变量都会被释放并且失效。
go语言函数变量
在Go语言中,函数也是一种类型,可以和其他类型一样保存在变量中
func main() {
var f func(int, string) (int, bool)
f = fire
i, b := f(11, "22")
fmt.Println(i, b)
}
func fire(a int, b string) (int, bool) {
fmt.Println(a, b)
return a, true
}
go语言匿名函数
- 匿名函数用作回调函数
下面的代码实现对切片的遍历操作,遍历中访问每个元素的操作使用匿名函数来实现,用户传入不同的匿名函数体可以实现对元素不同的遍历操作,代码如下:
func main() {
visit([]int{11, 22, 33, 44}, func(v int) {
v *= 10
fmt.Println(v)
})
}
func visit(s []int, f func(v int)) {
for _, v := range s {
f(v)
}
}
匿名函数作为回调函数的设计在Go语言的系统包中也比较常见,strings 包中就有类似的设计,代码如下:
func TrimFunc(s string, f func(rune) bool) string {
return TrimRightFunc(TrimLeftFunc(s, f), f)
}
使用匿名函数实现操作封装
下面这段代码将匿名函数作为 map 的键值,通过命令行参数动态调用匿名函数,代码如下:
func main() {
cPtr := flag.String("command", "", "这是一个命令")
flag.Parse()
m := map[string]func() {
"cn": func() {
fmt.Println("中国命令")
},
"en": func() {
fmt.Println("英国命令")
},
"han": func() {
fmt.Println("韩国命令")
},
}
f, ok := m[*cPtr]
if ok {
f()
}else{
fmt.Println("命令没找到")
}
}
go语言函数类型实现接口
函数和其他类型一样都属于“一等公民”,其他类型能够实现接口,函数也可以,本节将对结构体与函数实现接口的过程进行对比。
首先给出本节完整的代码:
type Invoker interface {
Call(any)
}
type Struct struct {}
func (s *Struct) Call(p any) {
fmt.Println("from struct", p)
}
type FuncCaller func(any)
func (f FuncCaller) Call(p any) {
f(p)
}
func main() {
var i Invoker
i = new(Struct)
i.Call("abc")
i = FuncCaller(func(p any) {
fmt.Println("from func", p)
})
i.Call(55)
}
运行结果:
from struct abc
from func 55
HTTP包中的例子
HTTP 包中包含有 Handler 接口定义,代码如下:
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
Handler 用于定义每个 HTTP 的请求和响应的处理过程。
同时,也可以使用处理函数实现接口,定义如下:
type HandlerFunc func(ResponseWriter, *Request)
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}
要使用闭包实现默认的 HTTP 请求处理,可以使用 http.HandleFunc() 函数,函数定义如下:
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
DefaultServeMux.HandleFunc(pattern, handler)
}
而 DefaultServeMux 是 ServeMux 结构,拥有 HandleFunc() 方法,定义如下:
func (mux *ServeMux) HandleFunc(pattern string, handler func
(ResponseWriter, *Request)) {
mux.Handle(pattern, HandlerFunc(handler))
}
上面代码将外部传入的函数 handler() 转为 HandlerFunc 类型,HandlerFunc 类型实现了 Handler 的 ServeHTTP 方法,底层可以同时使用各种类型来实现 Handler 接口进行处理。
函数类型类型也可以实现接口,并通过调用接口方法来实现调用函数本身,真是真实牛叉啊。
函数的声明不能直接实现接口,需要将函数定义为类型后,使用类型实现接口,当类型方法被调用时,还需要调用函数本体。
go语言闭包
go语言中的闭包是引用了自由变量的函数,被引用的自由变量和函数一同存在,即使已经离开了自由变量的环境也不会被释放或者删除,在闭包中可以继续使用这个自由变量,因此,简单的说:
函数+引用环境=闭包
同一个函数与不同的引用环境组合,可以形成不同的实例:
一个函数类型就像结构体一样,可以被实例化,函数本身不存储任何信息,只有与引用环境结合后形成的闭包才具有"记忆性",函数是编译期静态的概念,而闭包是运行期动态的概念。
在闭包内部修改引用的变量
闭包对它作用域上部的变量可以进行修改,修改引用的变量会对变量进行实际修改,通过下面的例子来理解:
func main() {
str := "hello world"
foo := func() {
str = "123456"
}
foo()
fmt.Println(str) // 123456
}
在匿名函数中并没有定义str,str被定义在匿名函数之前,此时,str就被引用到了匿名函数中形成了闭包。
闭包的记忆效应
被捕获到闭包中的变量让闭包本身拥有了记忆效应,闭包中的逻辑可以修改闭包捕获的变量,变量会跟随闭包生命期一直存在,闭包本身就如同变量一样拥有了记忆效应。
累加器的实现:
func main() {
accumelator1 := Accumulate(1)
fmt.Println(accumelator1())
fmt.Println(accumelator1())
fmt.Printf("accumelator1: %p\n", accumelator1)
accumelator2 := Accumulate(10)
fmt.Println(accumelator2())
fmt.Printf("accumelator2: %p\n", accumelator2)
}
// Accumulate 提供一个值,每次调用函数会指定对值进行累加
func Accumulate(value int) func() int {
return func() int {
value++
return value
}
}
- 累加器生成函数,这个函数输入一个初始值,调用时返回一个为初始值创建的闭包函数
- 返回一个闭包函数,每次返回会创建一个新的函数实例
- 对引用Accumulate参数变量进行累加,注意value不是匿名函数定义的,而是被匿名函数引用,所以形成了闭包
- 将修改后的值通过闭包的返回值返回
- 对比输出的日志发现,accumulator1和accumulator2输出的函数地址不同,因此它们是两个不同的闭包实例
每调用一次 accumulator 都会自动对引用的变量进行累加。
闭包实现生成器
闭包的记忆效应被用于实现类似于设计模式中工厂模式的生成器,下面的例子展示了创建一个玩家生成器的过程。
玩家生成器的实现:
func main() {
// 创建一个玩家生成器
player := PlayerGen("德玛")
fmt.Println(player())
}
// PlayerGen 创建一个玩家生成器,输入名称,输出生成器
func PlayerGen(name string) func() (string, int) {
hp := 150
// 返回创建的闭包
return func() (string, int) {
// 将变量引用到闭包中
return name, hp
}
}
go语言可变参数
可变参数类型
可变参数是指函数传入的参数个数是可变的,为了做到这点,首先需要将函数定义为可以接受可变参数的类型:
func myfunc(args ...int) {
for _, arg := range args {
fmt.Println(arg)
}
}
形如…type格式的类型只能作为函数的参数类型存在,并且必须是最后一个参数,它是一个语法糖(syntactic sugar),即这种语法对语言的功能并没有影响,但是更方便程序员使用,通常来说,使用语法糖能够增加程序的可读性,从而减少程序出错的可能。
从内部实现机理上来说,类型...type本质上是一个数组切片,也就是[]type, 这也是为什么上面的参数 args 可以用 for 循环来获得每个传入的参数。
假如没有...type这样的语法糖,开发者将不得不这么写:
加入没有...type这样的语法糖,开发者将不得不这么写:
func myfunc2(args []int) {
for _, arg := range args {
fmt.Println(arg)
}
}
从函数的实现角度来看,这没有任何影响,该怎么写就怎么写,但从调用方来说,情形则完全不同:
myfunc2([]int{1, 3, 7, 13})
大家会发现,我们不得不加上[]int{}来构造一个数组切片实例,但是有了...type这个语法糖,我们就不用自己来处理了。
任意类型的可变参数
用interface{}传递任意类型数据是go语言的惯例用法,使用interface{}仍然是类型安全的,
func main() {
myPrint(11, "hello", false, math.Pi)
}
func myPrint(args ...any) {
for _, v := range args {
switch v.(type) {
case int:
fmt.Println(v, "is a int")
case string:
fmt.Println(v, "is a string")
case bool:
fmt.Println(v, "is a bool")
case float64:
fmt.Println(v, "is a float64")
}
}
}
遍历可变参数列表,获取每一个参数的值
func main() {
joinStrings("abc", "def", "jjj", "哈哈哈")
}
func joinStrings(ss ...string) {
var b bytes.Buffer
for _, s := range ss {
_, _ = b.WriteString(s)
}
fmt.Println(b.String())
}
获得可变参数类型-获得每一个参数的类型
当可变参数为 interface{} 类型时,可以传入任何类型的值,此时,如果需要获得变量的类型,可以通过 switch 获得变量的类型,下面的代码演示将一系列不同类型的值传入printTypeValue() 函数,该函数将分别为不同的参数打印它们的值和类型的详细描述。
打印类型及值:
func main() {
printTypeValue(15, true, "Mayanna", 3.14)
}
func printTypeValue(args ...any) {
// 字节缓冲,作为快速字符串连接
var b bytes.Buffer
for _, v := range args {
// 使用 fmt.Sprintf 配合%v动词,可以将 interface{} 格式的任意值转为字符串。
str := fmt.Sprintf("%v", v)
var vType string
switch v.(type) {
case int:
vType = "int"
case string:
vType = "string"
case float64:
vType = "float64"
default:
vType = "未知"
}
b.WriteString("value: ")
b.WriteString(str)
b.WriteString(" type: ")
b.WriteString(vType)
b.WriteByte('\n')
}
fmt.Println(b.String())
}
在多个可变参数函数中传递参数
func main() {
myPrint(11, 22, 33, 44)
}
func myPrint(args ...any) {
rawPrint(args...)
}
func rawPrint(args ...any) {
for _, v := range args {
fmt.Println(v)
}
}
可变参数使用...进行传递与切片间使用append连接是同一个特性。
标签:闭包,01,函数,int,fmt,Println,详解,func,go From: https://www.cnblogs.com/mayanan/p/16640385.html