函数
函数是Go里的核心设计,通过关键字 func 来声明。
func funcName(input1 type1,input2 type2)(output1 type1,output1 type2){
// 处理逻辑代码部分
// 返回多个值
return value1, value2
}
# 说明
关键字 func 用来声明一个函数 funcName
函数可以有一个或是多个参数,每个参数后面带类型,多个参数用 逗号 分隔
函数可以有多个返回值
示例中返回值声明了两个变量,也可以不声明,直接就两个类型也可以。
如果只有一个返回值,而且没有声明返回值变量,也可以省略包括返回值的括号
如果没有返回值,可以省略最后的返回信息
如果有返回值,必须在函数的外层添加 return 语句。
//示例
package main
import "fmt"
// 返回 a b中的最大值
func max(a,b int) int{
if a > b {
return a
}
return b
}
func main(){
x := 3
y := 4
z := 5
max_xy := max(x, y)
max_xz := max(x, z)
fmt.Printf("max(%d,%d) = %d\n",x,y,max_xy)
fmt.Printf("max(%d,%d) = %d\n",x,z,max_xz)
fmt.Printf("max(%d,%d) = %d\n",x,y,max(x,y)) //也可以在这里调用函数
}
# 说明
max函数有两个参数,类型都是int 第一个参数的类型可以省略,默认为离他最近的类型。
同时,注意到返回值就一个类型,这个就是省略写法
多返回值
Go 支持多返回值
package main
import "fmt"
func SumAndProduct(a,b int)(int,int){
// 多返回值,用 逗号 分隔
return a+b, a*b
}
func main(){
x := 3
y := 4
xPlusy, xTimesy := SumAndProduct(x,y)
fmt.Printf("%d + %d = %d\n",x,y,xPlusy)
fmt.Printf("%d * %d = %d\n",x,y,xTimesy)
}
# 注意
示例中的返回值,没有声明变量名,只用了两个类型。
如果你的函数是导出的(首字母大写),最后要命名返回值。否则会造成文档的可读性差
变参
变参,是指函数有不确定数量的参数。Go支持变参。
func myfunc (args ...int){}
args ...int 是告诉 go 这个函数接受不定数量的参数。
注意,这些参数的类型都是int。
在函数体重,变量 args 是一个int的slice。
如:
func myfunc(args ...int){
for _, n := range arg{
fmt.Printf("And the Number is: %d\n",n)
}
}
形如 ...type 格式的类型,只能作为函数的参数类型存在,而且必须是最后一个参数。
不定参数的传递
func myfunc(args ...int){
// 按原样传递
myfunc3(args ...)
// 传递片段,实际上任意的int slice 都可以传递进去
myfunc3(args[1:]...)
}
任意类型的不定参数
类型: interface()
语法: func funcName(args ...interface())
如:fmt.Printf的函数
func Printf(format string, args ...interface()){
//……
}
// 示例
package main
import "fmt"
func myPrint(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 tpye")
}
}
}
func main(){
var v1 int = 1
var v2 int64 = 234
var v3 string = "hello"
var v4 float32 = 1.234
myPrint(v1,v2,v3,v4)
}
匿名函数
不需要定义函数名的一种函数。在Go里,可以像普通变量一样被传递和使用。Go 支持随时在代码里定义匿名函数。
# 语法
func (a,b int,z float64)bool{
return a*b < int(z)
}
匿名函数可以直接赋值给一个变量或是直接执行
f := func(x, y int) int{
return x, y
}
func(ch chan int){
ch <- ACK
}(reply_chan)
// 花括号后直接跟参数列表,表示函数调用。
闭包
Go 的匿名函数,其实就是一种闭包
闭包hi可以包含自由变量(为绑定到特定对象)的代码,这些变量不在这个代码块内或是任何全局上下文中定义,而是在定义代码块的环境中定义。
要执行的代码块为自由变量提供绑定的计算环境。
价值
可以作为函数对象或是匿名函数。对于类型系统而言,这意味着不仅要表示数据,还要表示代码。
这些函数可以存储到变量中作为参数传递。最重要的时候,能够被函数动态创建和返回。
Go 语言中的闭包同样也会引用到函数外使用的变量,闭包的实现确保只要闭包还在使用,那么被闭包引用的变量就会一直存在。
// 示例
package main
import "fmt"
func main(){
var j int = 5
a := func()(func()){
var i int = 10
return func(){
fmt.Printf("i, j: %d, %d\n", i, j)
}
}()
a()
j *= 2
a()
}
# 输出结果
i,j : 10, 5
i,j : 10,10
# 说明
变量 a 指向的闭包函数引用了局部变量 i,j。
i 的值被隔离,在闭包外不能被修改。改变 j 的值后,再次调用 a 发现结果是修改过的。
在变量 a 指向的闭包函数中,只有内部的匿名函数才可以访问 i ,无法通过其他方式访问,保证了i 的安全性。
错误处理
Go 语言引入了一个处理错误的标准模式,即error接口,定义如下
type error interface{
Error() string
}
大多数函数都有返回值,如果返回错误,一般将error作为多种返回值的最后一个。
# 具体使用方法
type PathError struct{
Op string
Path string
Err error
}
//这样定义了后,如何知道PathError作为error来传递呢,用下面的实现方法。
func (e *PathError) Error() string{
return e.Op + "" + e.Path + ":" + e.Err.Error()
}
//如下面的代码,当syscall.Stat()失败返回err时,将err包装到一个PathError对象中返回。
func Stat(name string)(fi FileInfo, err error){
var stat syscall.Stat_t
err = syscall.Stat(name, &stat)
if err != nil{
return nil, &PathError("stat", name, err)
}
return fileinfoFromStat(&stat, name), nil
}
// 如果不满足只打印一行错误信息,可以用类型转换来处理
fi, err := os.Stat("a.txt")
if err != nil{
if e, ok := err.(*os.PathError); ok && e.Err != nil
//获取PathError类型变量 e 中的其他信息并处理
}
}
defer
defer语句是Go中一个非常有用的特性,可以将一个方法延迟到包裹该方法的方法返回时执行,也可以用来处理关闭文件句柄等收尾操作
Go官方文档中对defer的执行时机做了阐述,分别是。
包裹defer的函数返回时
包裹defer的函数执行到末尾时
所在的goroutine发生panic时
一个函数中可以存在多个defer,调用顺序是先进后出的原则。LIFO
一些特殊的注意点,可以查看: Go语言中defer的一些坑
panic()
内置函数 panic() recover() 以报告和处理运行时错误和程序中的错误场景
func panic(interface())
func recover() interface()
当一个函数执行过程中调用 panic() 函数时,正常的函数执行将立即终止,但函数中之前使用defer延迟执行的语句将正常执行。
错误信息报告将被报告,包括在调用panic() 函数时传入的参数,这个过程称为错误处理流程。
panic() 的参数类型是interface(),这个函数接收任意类型数据。
recover()
recover()用于终止错误处理流程。
一般情况下,recover()应该在一个使用defer的函数中执行以有效截取错误处理流程。
//示例
# 如果我们认为tt()函数执行有问题的话,可以用下面的方式在调用代码中截取recover()
defer func(){
if r := recover(); r != nil{
log.Printf("Runtime error caught: %v", r)
}
}()
tt()
# 说明
无论 tt() 是否触发错误处理,匿名函数defer都会在函数退出的时候执行。
如果 tt() 触发了错误流程,recover()将使得该错误处理流程终止。
如果处理流程被触发,程序传给panic函数的参数不是nil,则函数还是会打印详细的错误信息