1、函数
1)声明
函数声明包括函数名、形式参数列表、返回值列表(可省略)以及函数体。
func 函数名(形式参数列表)(返回值列表){
函数体
}
如果一个函数在声明时,包含返回值列表,那么该函数必须以 return 语句结尾,除非函数明显无法运行到结尾处,例如函数在结尾时调用了 panic 异常或函数中存在无限循环。
函数的类型被称为函数的标识符,如果两个函数形式参数列表和返回值列表中的变量类型一一对应,那么这两个函数被认为有相同的类型和标识符,形参和返回值的变量名不影响函数标识符也不影响它们是否可以以省略参数类型的形式表示。
每一次函数在调用时都必须按照声明顺序为所有参数提供实参(参数值),在函数调用时,Go语言没有默认参数值,也没有任何方法可以通过参数名指定形参,因此形参和返回值的变量名对于函数调用者而言没有意义。
在函数中,实参通过值传递的方式进行传递,因此函数的形参是实参的拷贝,对形参进行修改不会影响实参,但是,如果实参包括引用类型,如指针、slice(切片)、map、function、channel 等类型,实参可能会由于函数的间接引用被修改。
2)返回值
Go语言支持多返回值,多返回值能方便地获得函数执行后的多个返回参数。
非命名返回值:如果返回值有多个,则用括号将多个返回值类型括起来,用逗号分隔每个返回值的类型。
命名返回值:Go语言支持对返回值进行命名,这样返回值就和参数一样拥有参数变量名和类型。命名的返回值变量的默认值为类型的默认值。在命名的返回值方式的函数体中,在函数结束前需要显式地使用 return 语句进行返回。
命名和非命名返回值参数不能混用。
示例:
package main import "fmt" func main() { x, y := add(1, 3) fmt.Println(x, y) a, b := sub(1, 3) fmt.Println(a, b) } func add(x, y int) (int, string) { return x + y, "hello" } func sub(x, y int) (a int, b string) { a = x - y b = "hello" return }
2、匿名函数
1)定义
匿名函数是指不需要定义函数名的一种函数实现方式,由一个不带函数名的函数声明和函数体组成。
匿名函数的定义格式如下:
func(参数列表)(返回参数列表){
函数体
}
匿名函数可以在声明后调用;匿名函数可以被赋值。
示例:
package main import "fmt" func main() { func(str string){ // 声明后马上调用 fmt.Printf("hello, %s\n", str) }("world") f := func(str string){ fmt.Printf("hello, %s\n", str) } f("world") }
3、函数类型实现接口
示例:
package main import ( "fmt" ) // 调用器接口 type Invoker interface { // 需要实现一个Call方法 Call(interface{}) } // 函数定义为类型 type FuncCaller func(interface{}) // 实现Invoker的Call func (f FuncCaller) Call(p interface{}) { // 调用f函数本体 f(p) } func main() { // 声明接口变量 var invoker Invoker // 将匿名函数转为FuncCaller类型,再赋值给接口 invoker = FuncCaller(func(v interface{}) { fmt.Println("from function", v) }) // 使用接口调用FuncCaller.Call,内部会调用函数本体 invoker.Call("hello") }
4、闭包
Go语言中闭包是引用了自由变量的函数,被引用的自由变量和函数一同存在,即使已经离开了自由变量的环境也不会被释放或者删除,在闭包中可以继续使用这个自由变量。
一个函数类型就像结构体一样,可以被实例化,函数本身不存储任何信息,只有与引用环境结合后形成的闭包才具有“记忆性”,函数是编译期静态的概念,而闭包是运行期动态的概念。
闭包对它作用域上部的变量可以进行修改,修改引用的变量会对变量进行实际修改。
5、可变参数
1)可变参数类型
可变参数是指函数传入的参数个数是可变的,需要将函数定义为可以接受可变参数的类型。
func funcName(args ...type) {
}
形如...type格式的类型只能作为函数的参数类型存在,并且必须是最后一个参数,它是一个语法糖(syntactic sugar),即这种语法对语言的功能并没有影响,但是更方便程序员使用,通常来说,使用语法糖能够增加程序的可读性,从而减少程序出错的可能。
从内部实现机理上来说,类型...type本质上是一个数组切片,也就是[]type,这也是为什么上面的参数 args 可以用 for 循环来获得每个传入的参数。
示例:
package main import "fmt" func f(args ...int) { for _, arg := range args { fmt.Println(arg) } } func main() { f(1,2,3) }
2)任意类型的可变参数
如果你希望传任意类型,可以指定类型为 interface{}。
示例:
package main import "fmt" func MyPrintf(args ...interface{}) { for _, arg := range args { switch arg.(type) { case int: fmt.Println(arg, "is an int value.") case string: fmt.Println(arg, "is a string value.") case int64: fmt.Println(arg, "is an int64 value.") default: fmt.Println(arg, "is an unknown type.") } } } func main() { var v1 = 1 var v2 int64 = 234 var v3 = "hello" var v4 float32 = 1.234 MyPrintf(v1, v2, v3, v4) }
3)在多个可变参数函数中传递参数
可变参数变量是一个包含所有参数的切片,如果要将这个含有可变参数的变量传递给下一个可变参数函数,可以在传递时给可变参数变量后面添加...
,这样就可以将切片中的元素进行传递,而不是传递可变参数变量本身。
6、defer
Go语言的 defer 语句会将其后面跟随的语句进行延迟处理,在 defer 归属的函数即将返回时(函数结束可以是正常返回时,也可以是发生宕机时),将延迟处理的语句按 defer 的逆序进行执行,也就是说,先被 defer 的语句最后被执行,最后被 defer 的语句,最先被执行。
7、panic
当 panic() 触发的宕机发生时,panic() 后面的代码将不会被运行,但是在 panic() 函数前面已经运行过的 defer 语句依然会在宕机发生时发生作用。
示例:
package main import "fmt" func main() { defer fmt.Println("宕机后要做的事情1") defer fmt.Println("宕机后要做的事情2") panic("宕机") }
8、recover
Recover 是一个Go语言的内建函数,可以让进入宕机流程中的 goroutine 恢复过来,recover 仅在延迟函数 defer 中有效,在正常的执行过程中,调用 recover 会返回 nil 并且没有其他任何效果,如果当前的 goroutine 陷入恐慌,调用 recover 可以捕获到 panic 的输入值,并且恢复正常的执行。
示例:
package main import ( "fmt" "runtime" ) // 崩溃时需要传递的上下文信息 type panicContext struct { function string // 所在函数 } // 保护方式允许一个函数 func ProtectRun(entry func()) { // 延迟处理的函数 defer func() { // 发生宕机时,获取panic传递的上下文并打印 err := recover() switch err.(type) { case runtime.Error: // 运行时错误 fmt.Println("runtime error:", err) default: // 非运行时错误 fmt.Println("error:", err) } }() entry() } func main() { fmt.Println("运行前") // 允许一段手动触发的错误 ProtectRun(func() { fmt.Println("手动宕机前") // 使用panic传递上下文 panic(&panicContext{ "手动触发panic", }) fmt.Println("手动宕机后") }) fmt.Println("运行后") }
panic 和 recover 的组合有如下特性:
- 有 panic 没 recover,程序宕机。
- 有 panic 也有 recover,程序不会宕机,执行完对应的 defer 后,从宕机点退出当前函数后继续执行。
在 panic 触发的 defer 函数内,可以继续调用 panic,进一步将错误外抛,直到程序整体崩溃。
如果想在捕获错误时设置当前函数的返回值,可以对返回值使用命名返回值方式直接进行设置。