目录
简介
go语言中函数特性
go语言中函数的定义和调用
调用过程
go语言函数定义语法
递归调用
函数的返回值
注意:
golang函数的参数
变长参数
type定义函数类型
init 函数
注意事项与细节
匿名函数(lambda表达式)
闭包(难)
我们把所有的功能单元都定义在函数中,可以重复使用。函数包含函数的名称、参 数列表和返回值类型,这些构成了函数的签名(signature).
go语言中有3种函数:普通函数、匿名函数(没有名称的函数)、方法(定义在struct上 的函数)。
go语言中不允许函数重载(overload),也就是说不允许函数同名。
g0语言中的函数不能嵌套函数,但可以嵌套匿名函数。
函数是一个值,可以将函数赋值给变量,使得这个变量也成为函数。
函数可以作为参数传递给另一个函数。
函数的返回值可以是一个函数。
函数调用的时候,如果有参数传递给函数,则先拷贝参数的副本,再将副本传递给函数。
函数参数可以没有名称。
函数在使用之前必须先定义,可以调用函数来完成某个任务。函数可以重复调用,从而达到代码重用。
在调用一个函数时,会给该函数分配一个新的空间(通常为栈区),编译器会通过自身的处理让这个新的空间和其它的栈的空间区分开来
调用结束时,程序会销毁这个函数对应的栈空间
func function_name([parameter list])[return_types]{
函数体
}
语法解析
func : 函数由 func 开始声明 function_name : 函数名称,函数名和参数列表一起构成了函数签名。 [parameter list] :参数列表,参数就像一个占位符,当函数被调用时,你可以将值传递给参数,这个值被称为实际参数。参数列表指定的是参数类型、顺序、及参数个数。参数是可选的,也就是说函数也可以不包含参数。 return_types : 返回类型,函数返回一列值。return_types是该列值的数据类型。有些功能不需要返回值,这种情况下return_types不是必须的。
函数体 :函数定义的代码集合。
每次调用函数均会创建一个新的栈区,在调用结束时销毁
//递归斐波那契、猴子吃桃
package main
import "fmt"
func fibonacci(n int)int{
if n==1||n==2 {
return 1
}else{
return fibonacci(n-1)+fibonacci(n-2)
}
}
func peach(n int)int{
if n==10{
return 1
}else{
return (peach(n+1)+1)*2
}
}
func main() {
var choice int
fmt.Println("输入1运行斐波那契数列,输入其他数字运行猴子吃桃")
fmt.Scanln(&choice)
if choice ==1{
for i:=1;i<6;i++ {
fmt.Println("第",i,"个斐波那契数为:",fibonacci(i))
}
}else{
var n int
fmt.Println("你要查询第几天的桃子数?")
fmt.Scanln(&n)
fmt.Println("第",n,"天的桃子数为:",peach(n))
}
}
函数可以有 0 或多个返回值,返回值需要指定数据类型,返回值通过 return 关键字来指定。 return 可以有参数,也可以没有参数,这些返回值可以有名称,也可以没有名称。go中的函数可以有多个返回 值。
return关键字中指定了参数时,返回值可以不用名称。如果return省略参数,则返回值部分必须带名称
当返回值有名称时,必须使用括号包围,逗号分隔,即使只有一个返回值
但即使返回值命名了,return 中也可以强制指定其它返回值的名称,也就是说 return 的优先级更高
命名的返回值是预先声明好的,在函数内部可以直接使用,无需再次声明。命名返回值的名称不能和函数参数名称相同,否则报错提示变量重复定义
return中可以有表达式,但不能出现赋值表达式,这和其它语言可能有所不同。例如 return a+b 是正确的,但 return c=a+b 是错误的。
当函数的返回值过多时,例如有4个以上的返回值,应该将这些返回值收集到容器中,然后以返回容器的方式去返回。例如,同类型的返回值可以放进slice中,不同类型的返回值可以放进map中。
但函数有多个返回值时,如果其中某个或某几个返回值不想使用,可以通过下划线_来丢弃这些返回值。
高阶函数
go语言的函数,可以作为函数的参数,传递给另外一个函数;也可以作为另外一个函数的返回值返回
func sayHello(name string){
fmt.Printf("Hello,%s",name)
}
func test(name string,f func(string)){
f(name)
}
func main(){
test("tom",sayHello)
}
func add(x,y int)int{
return x + y
}
func cal(s string)func(int,int)int{
switch s {
case "+":
return add
default:
return nil
}
}
func main(){
add := cal("+")
r := add(1,2)
fmt.Printf("r:%v\n",r)
}
Go语言函数可以有0或多个参数,参数需要指定数据类型。
声明函数时的参数列表叫做形参,调用时传递的参数叫做实参。
Go语言是通过传值的方式传参的,意味着传递给函数的是拷贝后的副本,所以函数内部访问、修改的也是这个副本。
Go语言可以使用变长参数,有时候并不能确定参数的个数,可以使用变长参数,可以在函数定义语句的参数部分使用 ARGS...TYPE
的方式。这时会将...
代表的参数全部保存到一个名为ARGS
的slice
中,注意这些参数的数据类型都是TYPE
。
map、slice、interface、channel这些数据类型本身就是指针类型的,所以就算是拷贝传值也是拷贝 的指针,拷贝后的参数仍然指向底层数据结构,所以修改它们可能会影响外部数据结构的值。
//变长参数
package main
import "fmt"
func main () {
test(1,2,3,4)
test(5,6,7,8,9)
}
func test (args ... int){
for _, v := range args {
fmt.Println("v:",v)
}
}
func main(){
type f1 func(int,int)int
var ff f1
ff = sum
r := ff(1,2)
fmt.Printf("r:%v\n",r)
ff = max
r = ff(1,2)
fmt.Printf("r:%v\n",r)
}
每一个源文件都可以包含一个 init 函数,该函数会在 main 函数执行前,被 Go 运行框架调用,也就是说 init 会在 main 函数前被调用。
如果一个文件同时包含全局变量定义,init 函数和 main 函数,则执行的流程全局变量定义->init 函数->main 函数
//init函数
package main
import "fmt"
var age = test()
func test() int{
fmt.Println("test()")
return 90
}
func init(){
fmt.Println("init()...")
}
func main() {
fmt.Println("main()...age = ",age)
}
输出结果
test()
init()...
main()...age = 90
init 函数最主要的作用,就是完成一些初始化的工作
package utils
import "fmt"
var(
Age int
Name string
)
func init() {
fmt.Println("utils 包的 init()...")
Age = 100
Name = "tom"
}
这时候有一个值得思考的问题: 如果 main.go 和 utils.go 都含有 变量定义,init 函数时,执行的流程又是怎么样的呢?
答案是从引入的utils包开始,按照之前的顺序:变量定义 -> init函数 -> (进入main包)变量定义 -> init函数 -> main函数...
func main(){
max := func(a int,b int)int{
if a > b{
return a
}else{
return b
}
}//在此处直接写(1,2),可以省略下面一行
i := max(1,2)
fmt.Printf("i:%v\n",i)
}
运行结果
i : 2
闭包就是一个函数和与其相关的引用环境组合的一个整体(实体)
//闭包
package main
import "fmt"
func AddUpper() func (int) int{
n := 10
return func(x int) int {
n+=x
return n
}
}
func main() {
f:=AddUpper()
fmt.Println(f(1)) //输出11
fmt.Println(f(2)) //13
fmt.Println(f(3)) //16
}
在AddUpper函数中,n的值仅在第一次调用时被初始化,在随后两次调用中都是上一次"n+x"后存留在函数中的值。
要搞清楚闭包的关键,就是要分析出返回的函数它使用(引用)到哪些变量,因为函数和它引用到的变量共同构成闭包。
再来一个例子加深理解:
package main
import (
"fmt"
"strings"
)
func makeSuffix(suffix string) func (string) string{
return func (name string) string {
if !strings.HasSuffix(name,suffix){
return name + suffix
}
return name
}
}
func main() {
f2 := makeSuffix(".jpg")
fmt.Println("文件名处理后=",f2("winter"))
fmt.Println("文件名处理后=",f2("bird.jpg"))
}
返回的匿名函数和 makeSuffix (suffix string) 的 suffix 变量 组合成一个闭包,因为 返回的函数引用到 suffix 这个变量
闭包的好处,如果使用传统的方法,也可以轻松实现这个功能,但是传统方法需要每次都传入 后缀名,比如 .jpg ,而闭包因为可以保留上次引用的某个值,所以传入一次就可以反复使用。