一、函数的定义
/*
函数的定义格式
func 函数名(形参列表)(返回值列表){
}
*/
package main
import (
"fmt"
"math"
)
//函数一:
func Add(a, b int)int{
return a + b
}
//函数二:
func Sub(a, b int)int{
return a - b
}
//函数三:
func hhh(a, b float64)float64{
return math.Sqrt(a*a + b*b)
}
func main() {
c := Add(10, 11)
d := Sub(23, 30)
e := hhh(40,30)
fmt.Println("c=",c)
fmt.Println("d=",d)
fmt.Println("e=",e)
}
结果:
二、函数返回值和可变参数
- Go语言支持多返回值。在Go中,经常使用多返回值中的最后一个返回值参数返回函数执行中可能发生的错误err
1.同一种类型的返回值
-
若返回值是同一种类型,则用括号将多个返回值类型括起来,用逗号分隔每个返回值类型
/* 函数的定义格式 func 函数名(形参列表)(返回值列表){ } */ package main import ( "fmt" ) func hhh()(int, int){ a := 100 var b int=300 return a, b } func main() { c, d := hhh() fmt.Println("c=",c) fmt.Println("d=",d) }
结果:
2.带有变量名的返回值
-
Go语言支持对返回值进行命名,这样返回值就和参数一样有参数变量名和类型;命名的返回值变量的默认值为类型的默认值,即数值为0,字符串为空字符串,布尔值为false,指针为nil等。
package main import ( "fmt" ) func hhh()(a bool, b int){ a = true b = 300 return } func main() { c, d := hhh() fmt.Println("c=",c) fmt.Println("d=",d) }
结果:
3.可变参数
- 可变参数是指函数传入的参数个数是可变的。…interface{}
结果:package main import "fmt" //可变参数列表 ...interface{} func hhh(args ...interface{})(){ for _,arg := range args{ switch arg.(type){ case int: fmt.Println(arg,"is type of int") case float64: fmt.Println(arg,"is type of float64") case bool: fmt.Println(arg, "is type of bool") case string: fmt.Println(arg,"is type of string") default: fmt.Println(arg,"is the unknown type") } } } func main() { var i int=100 var f float64=3.1415 var b bool=true var s string="hello world" var iptr *int=&i hhh(i, f, b, s, iptr) }
三、函数变量
- 在Go中,函数也是一种类型,可以和其他类型一样,保存在变量中。如下所示
结果package main import ( "fmt" ) func hhh(a bool, b int)(){ fmt.Println("a=",a) fmt.Println("b=",b) } func main() { var fnc func(bool, int) fnc = hhh fnc(true, 10000) //实际调用的是hhh函数 }
四、匿名函数
- Go支持匿名函数,即在需要使用函数时再定义函数,匿名函数没有函数名只有函数题,函数可以作为一种类型被赋值给函数类型的变量,匿名函数也往往以变量的方式进行传递。
- 匿名函数是指不需要定义函数名的一种函数实现方式,由一个不带函数名函数声明和函数体组成。
结果:/* 匿名函数语法: func(参数列表)(返回值列表){ 函数体 } */ package main import "fmt" func main() { //将匿名函数保存到变量res中 res := func(a, b int){ c := a + b fmt.Println("c = ", c) } //使用res()调用 res(100,200) }
1.匿名函数作为参数传入
package main
import "fmt"
func visit(slc []int, f func(int)){
for _,v := range slc{
f(v)
}
}
func main() {
//使用匿名函数遍历打印切片,将匿名函数作为参数传入函数visit中
visit([]int{1,2,3,4,5,6,7,8,9,10}, func(v int){
fmt.Println(v)
})
}
结果:
2.闭包Closure
-
闭包:引用了外部变量的匿名函数。是指有权访问另一个函数作用域中的变量的函数。
-
Go中闭包是引用了自由变量的函数,被引用的自由变量和函数一同存在,即使已经离开了自由变量的环境也不会被释放或者删除,在闭包中可继续使用这个自由变量,因此,闭包里作用域返回的局部变量不会被立刻销毁回收,但过度使用闭包可能会占用更多内存,导致性能下降。简单来说:
闭包 = 函数 + 引用环境 -
闭包的本质是函数,但是这个函数会用到函数外的变量,它们共同组成的整体即闭包。如下面的str和匿名函数共同组成的整体叫做闭包。
package main
import "fmt"
func main(){
str := "hello world"
fmt.Println("变成闭包前:")
fmt.Println(str)
//匿名函数中访问str
foo := func(){
str = "hello sheena!!!!"
}
//调用匿名函数
foo()
fmt.Println("变成闭包后:")
//闭包str拥有了记忆
fmt.Println(str)
}
结果:
- 闭包具体请看博客Go语言之闭包
五、函数类型实现接口
- 把函数作为接口来调用
结果:package main import ( "fmt" ) //定义接口 type Sheena interface { //接口中的方法 MyPrint(interface{}) } //定义接口体 type MyStruct struct { } //结构体变量实现接口方法 func (ms *MyStruct)MyPrint(pi interface{}){ fmt.Println("from my struct" , pi) } //函数定义为类型 type FuncCall func(interface{}) //函数边领实现接口方法 func (fc FuncCall)MyPrint(fi interface{}){ //调用f函数本体 fc(fi) } func main(){ //声明接口变量 var sea Sheena //实例化结构体 st := new(MyStruct) //将实例化的结构体赋值到接口 sea = st //使用接口调用实例化结构体方法Struct.MyPrint sea.MyPrint(33333) //将匿名函数转为FuncCall类型,再赋值给接口 sea = FuncCall(func(v interface{}){ fmt.Println("from function aaa", v) }) //使用接口调用FuncCall,MyPrint,内部会调用函数本体即匿名函数 sea.MyPrint(1111) }
六、defer延迟执行语句
- Go中defer语句会将其后面跟着的语句进行延迟处理,在defer归属的函数即将返回时,将延迟处理语句按defer的逆序进行执行。即先被defer的语句最后执行,最后被defer的语句最先被执行。
- 如下例所示,defer语句是在函数要退出的时候才开始执行,故先打印“defer begin”和“defer end”
结果:package main import "fmt" func main(){ fmt.Println("::::defer begin::::") defer fmt.Println("a") //将defer放入延迟调用栈 defer fmt.Println("b") defer fmt.Println("c") defer fmt.Println("d") defer fmt.Println("e") //最后一个放入,位于栈顶,最先调用 fmt.Println("::::defer end::::") }
1.使用defer释放资源
- 使用延迟执行语句在函数退出时释放资源,对于先加锁,再执行业务,最后解锁的代码
package main import ( "fmt" "sync" ) var vslueMutex sync.Mutex func hhhh(key string)int{ mymp := map[string]int{"aaaa":111} vslueMutex.Lock() //defer后面的语句不会马上调用,而是延迟到函数结束时调用 defer vslueMutex.Unlock() return mymp[key] } func main(){ fmt.Println(hhhh("aaaa")) }
2.defer的原理
详情请看博客Go语言之defer(原理、常见的坑)
七、处理运行时错误
- Go语言的错误:
(1)一个可能造成错误的函数,需要返回值中返回一个错误接口(error),若调用是成功的,错误接口将返回nil,否则返回错误。
(2)在函数调用后需要检查错误,若发生错误,则需要进行必要的错误处理。
1.错误接口的定义格式
- error是Go语言系统声明的接口类型,代码如下(所有符合Error()string格式的方法,都能实现错误接口,Error()方法返回错误描述):
type error interface{
Error() string
}
2.自定义一个错误
- 语法:var err = errors.New(“this is an error!!!”)
- 在代码中使用错误定义
package main
import (
"errors"
"fmt"
)
var myError = errors.New("this is an error!!!!!!!")
func fc(i,j int)(int, error){
if(i == 0){
return 0, myError
}
return j, nil
}
func main(){
fmt.Println(fc(1,3))
fmt.Println(fc(0,1))
}
结果:
八、函数panic和恢复recover
1.宕机Panic–程序终止运行
(1) 简单示例
- 在Go中可以在程序中我们手动触发panic,让程序崩溃
package main
import "fmt"
func main(){
fmt.Println("helllo world!!!!!!")
panic("出错啦!!crash啦!!")
fmt.Println("!!!!!!helllo world")
}
结果:
- 上述代码中用了一个内建函数panic造成了程序崩溃,panic声明如下:func panic(v interface{}) (panic的参数可以为任意类型的)。
(2) 在运行依赖的必备资源缺失时主动触发panic
- 手动触发panic并不是一种偷懒的方式,反而能迅速报错,终止程序继续运行,防止程序发生更大的错误。
package main
import "fmt"
func main(){
a := []int{0,1,2,3,4,5,6,7,8,9}
i:=11
for i=0;i<len(a);i++{
fmt.Printf(" %d ",a[i])
}
fmt.Println()
if i>=len(a){
panic("数组越界啦!!!crash")
}
}
结果:
(3)panic和defer的结合使用
- 当触发panic时,panic之后的代码就不会被执行,如上述例子所示,但在panic前面已经运行过的defer语句依然会在panic发生时执行。即使defer是在函数最后执行的。注:defer语句要想执行,必须放在panic之前,如下例所示:
package main
import "fmt"
func main(){
a := []int{0,1,2,3,4,5,6,7,8,9}
i:=11
for i=0;i<len(a);i++{
fmt.Printf(" %d ",a[i])
}
fmt.Println()
defer fmt.Println("defer要执行的语句11111")
defer fmt.Println("defer要执行的语句22222")
if i>=len(a){
panic("数组越界啦!!!crash")
}
defer fmt.Println("defer要执行的语句33333不会执行")
}
结果:
2.宕机恢复recover–防止程序崩溃
- recover是一个Go语言的内建函数,可以让进入panic流程中的goroutine恢复过来。recover仅会在defer函数中有效。在正常的执行的过程中,调用recover会返回nil并没有其他任何效果。若当前的goroutine陷入恐慌,调用recover可捕获到panic的输入值并且恢复正常运行。常用于异常捕获。
package main
import (
"fmt"
"runtime"
)
func main(){
a := []int{0,1,2,3,4,5,6,7,8,9}
i:=11
for i=0;i<len(a);i++{
fmt.Printf(" %d ",a[i])
}
fmt.Println()
defer fmt.Println("defer要执行的语句11111")
defer fmt.Println("defer要执行的语句22222")
defer func(){
err := recover()
switch err.(type) {
case runtime.Error://运行时错误
fmt.Println("运行时发生错误:::error:", err)
default:
fmt.Println("unknown error:::error:", err)
}
}()
if i>=len(a){
panic("数组越界啦!!!crash")
}
defer fmt.Println("defer要执行的语句33333不会执行")
}
结果:
九、计算函数执行时间
- Go中可以使用time包中的Since()函数来获取到函数运行的时间,Now()获取到当前的时间,这样就可以计算函数运行的时间了。
package main
import (
"fmt"
"time"
)
func fct(arr []int)(){
sum := 0
for i:=0; i<len(arr); i++{
sum++
}
fmt.Println("结果:", sum)
}
func main(){
a := []int{0,1,2,3,4,5,6,7,8,9,10}
start := time.Now()//获取当前时间
fct(a)
res := time.Since(start)//计算出到当前时间到start的时间戳
fmt.Println("该函数执行完成的耗时是:", res)
}
结果: