在编程中,函数是指一段可以直接被另一段程序或代码引用的、可重复使用的、用来实现单一或相关联功能的代码段。目的是为了提高应用的模块性和代码的重复利用率。
相比较其他语言,Go 语言 在设计上对函数进行了优化和改进,使其使用起来更加便利。
Go 语言中函数有着以下特性:
1、Go 语言函数定义
2、函数变量
3、匿名函数
4、函数接口
5、闭包(closure)
6、可变参数
7、延迟执行语句(defer)
8、处理运行时发生的错误
9、宕机(panic)
10、宕机恢复(recover)
在 Go 语言 中,定义一个函数需要声明参数和函数名等。
Go 语言中,定义函数需要以 func 标识开头,后面紧接着函数名、参数列表、返回参数列表以及函数体,格式如下:
func 函数名(参数列表) (返回参数列表) {函数体
}
函数名的命名有以下约束:
参数列表中声明的每一个参数由变量名和参数类型组成。
示例代码如下:
func foo(a int, b string)
注意,参数列表中的变量以局部变量的方式存在。
可以是返回一个值类型,也可以是一个组合,即返回多个参数。
另外,当函数中声明了有返回值时,函数体中必须 使用 return
语句提供返回值。
函数体表示能够被重复调用的代码片段。
当参数列表中定义了多个参数,且参数类型相同时,代码如下:
func foo(a int, b int) int {}
上面代码中,变量 a、b 的类型均为整型 int,可以采用以下简写方式:
func foo(a , b int) int {}
统一定义一个 int 类型即可。
在 Go 语言中,返回值支持返回多个,常用场景下,多返回值的最后一个参数会返回函数执行中可能发生的错误,示例代码如下:
conn, err := connectToDatabase()
上面这段代码中,函数 connectToDatabase()
用来获取数据库连接,conn
表示数据库连接,err
用来接收获取过程中可能发生的错误。
如果函数返回值是统一类型,则用括号将多个返回值括起来,以逗号隔开,示例代码如下:
package mainimport "fmt"func say(name, content string) (string, string) {return name, content
}func main() {name, content := say("知其黑", "受其白")fmt.Println(name, content)
}
知其黑 受其白
注意:使用
return
语句返回多个值时,值的顺序需要与函数声明的返回值一致。
Go 语言支持对返回值进行命名,命名后代码的可读性更佳。
命名的返回值默认值为该类型的默认值,例如,若返回值为整型 int
,则默认值为 0;若为字符串 string
,则默认值为空字符串;布尔为 false
; 指针为 nil
等。
下面是代码示例:
func initValue() (a int, b int) {a = 1b = 2return
}
上面这段代码中,函数声明中将返回值变量命名为 a、b,然后在函数体中分别对 a 、 b 进行赋值, 然后使用 return 语句进行返回。
注意: 当函数使用命名进行返回时,可以在 return 语句中不填返回值列表,当然填写也是可以的,上面的代码与下面的代码执行效果相同:
func initValue() (a int, b int) {a = 1return a, 2
}
函数在定义以后,要如何调用呢?
函数的调用格式如下:
返回值列表 = 函数名(参数列表)
下面代码演示如何调用函数:
package mainimport "fmt"func initValue() (a int, b int) {a = 1return a, 2
}func main() {// 调用 initValue 函数a, b := initValue()fmt.Println(a, b)
}
building...
running...
1 2
PS: 函数内定义的局部变量只能作用在函数体中,函数执行结束后,这些变量都会被释放掉,无法再次访问。
在 Go 语言中,函数也是一种类型,同样可以和其他类型(如 int 、float 、string 等)一样被保存到变量中。
示例代码如下:
package mainimport "fmt"func sayHello() {fmt.Println("hello , 知其黑,受其 ...")
}func main() {// 声明一个函数类型的变量,注意类型为 func()var f func()// 将函数名赋值给变量 ff = sayHello// 通过变量 f 直接调用函数f()
}
running...
hello , 知其黑,受其 ...
在 Go 语言 中,匿名函数是没有名字的函数,只有函数体。
匿名函数经常以变量的形式被传递。
大部分场景下,匿名函数经常被使用于实现函数回调、闭包等。
匿名函数的定义格式如下:
func(参数列表) (返回参数列表) {函数体
}
从上面可以看出来,匿名函数的定义格式其实就是没有函数名的普通函数定义格式。
可以定义完匿名函数后,立即调用它,例如:
package mainimport "fmt"func main() {func(name string) {fmt.Printf("hello, %s", name)}("知其黑,受其白")
}
running...
hello, 知其黑,受其白
上面的代码,我们注意到函数后面立刻跟上传入参数,表示对匿名函数的调用。
匿名函数可以赋值给变量,示例如下:
package mainimport "fmt"func main() {// 将匿名函数赋值给变量 functionfunction := func(name string) {fmt.Printf("hello, %s", name)}// 使用变量 function 调用函数function("知其黑,受其白")
}
running...
hello, 知其黑,受其白
下面演示一段匿名函数充当回调函数的示例:
package mainimport "fmt"func visit(list []string, f func(string)) {// 遍历切片中的元素,并将值作为参数传给回调函数for _, value := range list {// 调用回调函数f(value)}
}func main() {// 定义一个切片list := []string{"知其黑,受其白", "wgchen.blog.csdn.net"}// 使用匿名函数打印切片内容visit(list, func(value string) {fmt.Println(value + " func \n")})
}
running...
知其黑,受其白 funcwgchen.blog.csdn.net func
在 Go 语言 中,其他基本类型能够实现接口,函数同样可以实现接口。
下面我们定义一个名为狗 Dog 的接口:
// 定义一个名为 Dog 的接口
type Dog interface {// 需要实现一个姓名 Name() 方法Say(interface{})
}
上面这个接口需要实现 Say()
方法,调用时需传入一个 interface{}
类型的变量。这是个啥类型呢?
这种类型表示你可以传入任意类型的值。
接下来,我们将实现这个接口。
实现接口有两种方式:
下面将定义一个哈士奇的结构体,并实现 Dog 接口,代码如下:
// 定义一个结构体类型:哈士奇
type Husky struct {}// 实现接口 Dog 定义的 Say 方法,方法中打印一句话
func (s *Husky) Say(p interface{}) {fmt.Println("哈士奇说: ", p)
}
接下来,将定义的 Husky 类型实例化并调用接口方法,代码如下:
// 定义接口
var dog Dog
// 实例化哈士奇结构体
husky := new(Husky)
// 将实例化的结构体赋给接口
dog = husky
// 使用接口调用实例化结构体的方法 Say()
dog.Say("知其黑,受其白")
完整代码
package mainimport "fmt"// 定义一个名为 Dog 的接口
type Dog interface {// 需要实现一个姓名 Name() 方法Say(interface{})
}// 定义一个结构体类型:哈士奇
type Husky struct{}// 实现接口 Dog 定义的 Say 方法,方法中打印一句话
func (s *Husky) Say(p interface{}) {fmt.Println("哈士奇说: ", p)
}func main() {// 定义接口var dog Dog// 实例化哈士奇结构体husky := new(Husky)// 将实例化的结构体赋给接口dog = husky// 使用接口调用实例化结构体的方法 Say()dog.Say("知其黑,受其白")
}
running...
哈士奇说: 知其黑,受其白
函数要想实现接口,需要先将自己定义为类型,然后实现接口方法,同时需要再方法中调用函数本体,示例代码如下:
// 将函数定义为类型
type FuncHusky func(interface{})// 实现接口 Dog 的 Say 方法
func (f FuncHusky) Say(p interface{}) {// 调用 f() 函数本体f(p)
}
FuncHusky 无需实例化,只需要将函数转换为 FuncHusky 类型即可,示例代码如下:
// 定义接口
var dog Dog
// 将匿名函数转换为 FuncHusky 类型,此时 FuncHusky 类型实现了 Say 方法,赋值给接口是成功的
dog = FuncHusky(func(i interface{}) {fmt.Println("哈士奇说: ", i)
})
// 使用接口调用 Call 方法
dog.Say("知其黑,受其白")
完整代码
package mainimport "fmt"// 定义一个名为 Dog 的接口
type Dog interface {// 需要实现一个姓名 Name() 方法Say(interface{})
}// 将函数定义为类型
type FuncHusky func(interface{})// 实现接口 Dog 的 Say 方法
func (f FuncHusky) Say(p interface{}) {// 调用 f() 函数本体f(p)
}func main() {// 定义接口var dog Dog// 将匿名函数转换为 FuncHusky 类型,此时 FuncHusky 类型实现了 Say 方法,赋值给接口是成功的dog = FuncHusky(func(i interface{}) {fmt.Println("哈士奇说: ", i)})// 使用接口调用 Call 方法dog.Say("知其黑,受其白")
}
running...
哈士奇说: 知其黑,受其白
在 Go 语言 中,闭包是个啥概念呢?
一句话来讲:引用了外部变量的匿名函数 。
公式如下:
函数 + 引用外部变量 = 闭包
下面代码演示了如何在 Go 语言中定义闭包:
package mainimport "fmt"func main() {// 定义一个字符串str := "wgchen.blog.csdn.net"// 创建一个匿名函数function := func() {// 给字符串 str 赋予一个新的值,注意: 匿名函数引用了外部变量,这种情况形成了闭包str = "知其黑,受其白"// 打印fmt.Println(str)}// 执行闭包function()
}
running...
知其黑,受其白
闭包在引用外部变量后具有记忆效应,闭包中可以修改变量,变量会随着闭包的生命周期一直存在,此时,闭包如同变量一样拥有了记忆效应。
示例代码如下:
package mainimport "fmt"// 定义一个累加函数,返回类型为 func() int,
// 入参为整数类型,每次调用函数对该值进行累加
func Add(value int) func() int {// 返回一个闭包return func() int {// 累加value++// 返回累加值return value}
}func main() {// 创建一个累加器,初始值为 1accumulator := Add(1)// 累加1并打印fmt.Println(accumulator())// 再来一次fmt.Println(accumulator())// 创建另一个累加器,初始值为 10accumulator2 := Add(10)// 累加1并打印fmt.Println(accumulator2())
}
running...
2
3
11
通过输出可以看出闭包的记忆效应,每次调用 accumulator()
后,都会对 value
进行累加操作。
可以通过闭包的记忆效应来实现设计模式中工厂模式的生成器。
下面的代码示例展示了创建游戏玩家生成器的过程。
package mainimport "fmt"/*
定义一个玩家生成器,
它的返回类型为 func() (string, int),输入名称,
返回新的玩家数据
*/
func genPlayer(name string) func() (string, int) {// 定义玩家血量hp := 1000// 返回闭包return func() (string, int) {// 引用了外部的 hp 变量, 形成了闭包return name, hp}
}func main() {// 创建一个玩家生成器generator := genPlayer("知其黑,受其白")// 返回新创建玩家的姓名, 血量name, hp := generator()// 打印fmt.Println(name, hp)
}
running...
知其黑,受其白 1000
Golang 【basic_leaming】函数详解