一、函数
1、函数的声明
func funcName(arg1 type,arg2 type,...)(output1 type, output2 type,...){
return output1,output1,...
}
2、函数的参数
func funcName(arg1,arg2 type)(output1,opuput2 type){
return output1,output1,...
}
3、可变参数
func funcName(arg type,arg ...int){
// TO DO
}
4、函数的返回值
4.1 单返回值
max := Max(a,b)
4.2 多返回值
sum,mutiplied := sumandProduct(a,b)
5、函数做为类型
type name func(arg1 type,arg2 type,...)(output1 type,output2 type,...)
可以把函数当成值传递
func function(f name)
6、函数作为参数
函数在 GO 语言里是第一类对象(一等公民)
7、匿名函数
不需要定义函数名,不能独立存在,可以赋值到某个变量中(保存函数地址)调用
func(arg1 type,arg2 type,...)(output1 type,...){
// TO DO
}(value1,value2,...)
8、闭包
一个闭包集成了函数声明时的作用域,这种状态(作用域内的环境)会共享到闭包环境中,因此这些变量可以在闭包中被操作,直到被销毁。
9、递归函数
关键: 设置退出条件,否则递归将陷入无限循环。
10、参数传递机制
10.1 按值传递
操作副本,实参不变
10.2 按引用传递
操作实参,实参变化
10.3 引用传递的好处
节省内存、函数可修改操作对象的状态
11、defer(延迟语句)
开发者可以在函数中添加多个 defer 语句,当函数执行到最后时(return语句执行前),这些 defer 语句会按照逆序(LIFO)执行,最后函数才退出。
11.1、用途
回收资源、清理收尾等工作
11.2、执行顺序
defer 是一个栈,遵循先入后出(LIFO)。return 之前逆序执行 defer 语句
11.3 defer 函数传递参数时
defer 函数调用是,就已经传递参数了,只是暂时不执行函数中的代码而已
11.4 当 外围函数的代码引发运行恐慌,只有当其中所有的defer语句执行完毕后,该运行时恐慌才会被真正扩展到调用函数
12、error
GO 中的错误也是一种类型,错误用内置的 error 表示,错误可以存储在变量中,从函数中返回等等。
12.1 如何创建 error
fmt.errorf()
errors.New()
12.2、error 接口
tyoe error interface{
Error() string
}
12.3、自定义 error
自定义结构体,实现 error 接口中的 Error() 方法
type PathError struct{
Op string
Path string
Err error
}
func(receiver *PathError) Error() string{
return receiver.Op + " " + receiver.Path + " " + receiver.Err.Error()
}
13、panic (恐慌)
13.1 产生
直接调用、运行时产生
13.2 产生的结果
可以中断原有的控制流程,但是所在函数中 defer 函数会正常执行
13.2 panic 和 error 的区别使用
导致关键流程出现不可修复性错误的情况使用 panic,其他情况使用 error
13.4 panic 是最后手段,代码中应该很少或者没有 panic 语句
14、recover
14.1 作用
让程序 从 panic 恢复过来
14.2 如何使用
defer func(){
recover()
}()
14.3 如何获取 recover()的返回值
一、函数
在 GO 语言中,方法和函数有明显的区分。函数是没有接收者,而方法是有接收者。函数是 GO 语言中的第一对象。
1、声明
func funcName(arg1 type,arg2 type,...)(output1 type,output2 type,...)
1.1 关键字 func
是用来声明函数的
1.2 funcName
是指函数的名字,如果函数名字以大写字母开头,则该函数是公开的,可以被其他包调用。反之,小写字母开头,作用域只属于所声明的包
1.3 函数不支持嵌套(函数不能在其他函数体声明)、重载(不能同函数名存在)、默认参数
(重载:函数名相同,参数个数或参数类型不同。)
1.4 函数作为 GO 中第一对象,函数同样可以通过声明的方式作为一个函数类型被使用,也可以赋值给变量
//作为函数类型
type addNum func(a int, b int)(sum int)
//函数类型赋值给变量
add := addNum
1.5 函数之间是可以相互比较的,如果引用相同函数或者返回值都是 nil,认为他们是相同函数。
2、函数的参数
函数可以有一个或多个参数,每个参数后面都有类型,通过","符号分隔。
如果参数列表中若干个相邻参数的类型相同,可以省略前面变量的类型声明
如果返回值列表中若干个相邻参数类型相同,也可以用同样的方式合并
//合并参数类型
func add(a,b int)(sum int,err error){
//TO DO
}
//合并返回值类型
func sumandproduct(a,b int)(add,mutiplied int){
//TO DO
}
2、可变参数
在 GO 语言中,函数的 最后一个(变参函数可以只是函数的其中一个参数)参数如果 是...type
的形式,那这个函数就是一个变参函数,可以处理变长的参数,而这个长度可以为 0 。在变参函数中,无论变参多少个,他们的类型是一样的。在函数体中,可以通过for index,value := range arg
的形式迭代访问。其实,可变参数就是对应类型的切片。类似的函数有: Println()
、Print()
、Printf()
、append()
等
func searchMaxNumber(arg ...int) (max int) {
max = arg[0]
for _,value := range arg{
if value > max{
max = value
}
}
return max
}
//调用
max := searchMaxNumber(1,2,3,4,5,6,7)
//下面这种写法是错误的
//max := searchMaxNumber([]int{1,2,3,4,5})
注意: 如果函数参数多个参数,其中一个是可变参数,可变参数必须放在参数列表中最后一个。函数参数最多也只能有一个可变参数
4、函数的返回值
返回值就是函数执行后返回的内容,函数可以返回多个返回值。如果有返回值,那么必须在函数的最后添加 return
语句。在函数块里,return
之后的语句都不会执行。对于返回值,可以不声明变量,但是要指定变量类型。
4.1 单返回值
func max(a,b int) (max int) {
if a > b {
max = a
}
max = b
return max
}
4.2 多返回值
func sumandProduct(a,b int)(sum,mutiplied int) {
sum = a + b
mutiplied = a * b
return sum,mutiplied
}
注意: 多返回值也就意味着返回值有时候并不是全都需要的,我们可以用 “_” 忽略函数的返回值。
5、函数做为类型
函数在 GO 中也算是一种变量,既然是变量,也可以用type
(类型别名)将其定义类型。函数作为类型最大的好处在于可以把这个类型的函数当成值传递。
如何查看函数类型
package main
import "fmt"
func main()
{
fmt.Printf("%T\n",getSum) //输出: func(...int) int
fmt.Println(getSum) //输出: 0x109fbd0,0x109fbd0 可以看成是函数名对应函数体的地址(类似 OC 中的 IMP,其实就是个指针)
//变量 imp 是函数类型,加上小括号,也可以调用
var imp func(...int) int = getSum
a := getSum(1,2,3,4,5)
b := imp(1,2,3,4,5)
fmt.Println(a,b)
}
func getSum(args ...int) int{
sum := 0
for i:= 0; i < len(args);i++ {
sum += args[i]
}
return sum
}
执行结果
func(...int) int
0x109fbd0
15 15
函数类型: func(参数列表数据类型)(返回值列表数据类型)
多个列表和多个返回值之间用 ,
隔开。
需求: 给定一组整型切片,返回都是偶数的新切片
func isOdd(value int) (result bool){
return value % 2 == 0
}
//定义 isOdd 的类型别名,另其成为值进行传递
type boolFunc func(value int) (result bool)
func filter(slice []int,f boolFunc) []int {
var result []int
for _,value := range slice{
if f(value) { //函数调用
result = append(result,value)
}
}
return result
}
//在main方法中
func main() {
newSlice := filter([]int{1,4,5,67,14,8},isOdd)
fmt.Println(newSlice)
}
6、函数作为参数
函数是第一类对象,可作为参数传递。只要被调用函数的返回值个数、返回值类型和返回值顺序与调用函数所需参数是一致的,就可以把这个被调用的函数当成其他函数的参数。
package main
import "fmt"
func main() {
adds := oper(5,10,add)
mutis := oper(3,5,muti)
fmt.Println(adds,mutis) // 15 15
sub := oper(10,5, func(i int, i2 int) int {
return i - i2
})
fmt.Println(imp) //5
}
//函数作为参数
func oper(a,b int, fun func(int,int)int)int{
return fun(a,b)
}
//定义加法
func add(a,b int)int{
return a + b
}
//定义乘法
func muti(a,b int)int{
return a * b
}
7、匿名函数
不需要定义函数名,不能独立存在,可以赋值到某个变量中(保存函数地址),直接调用匿名函数后面要紧跟函数运行参数。
func main() {
//变量 funcimplement 保存匿名函数的地址,并调用(可以多次调用)
funcimplement := func(a,b int)(sum int){
return a + b
}
fmt.Println(funcimplement(5,8))
}
或者
func main(){
func block() {
//进支持调用一次
result := func(a,b int)(mutiplied int){
return a *b
}(10,5)
}
注意: 参数列表中的第一对括号必须紧挨着关键字func
,因为匿名函数没有名字。花括号{}涵盖着函数体,最后一对括号表示对该匿名函数的调用。没有函数名,直接括号(参数列表即可)
8 闭包
闭包允许定义在其他环境下的变量,使得某个函数捕捉到一些外部状态(局部变量)。 闭包函数保存其中外部状态,不管外部函数是否退出,他都能够继续操作外部函数中的状态
简单来说: 一个外层函数中,有内层函数,在该内层函数中,会操作外层函数的局部变量(外层函数中的参数,或者外层函数定义的变量),并且该外层函数的返回值就是这个内层函数。这种结构统称为闭包。局部变量的声明周期发生改变,正常的局部变量随函数的调用而创建,随着函数的结束而销毁,但是闭包结构中的外层函数的局部变量并不会随着外层函数的结束而销毁,因为内层函数还要继续使用。
下面看个例子就明白了
package main
import "fmt"
func main(){
nextNumber := block()
fmt.Println(nextNumber()) //1
fmt.Println(nextNumber()) //2
fmt.Println(nextNumber()) //3
}
//下面就是一个闭包结构
func block() func()int {
i := 0
return func() int {
i++
return i
}
}
9、递归函数
递归,就是在运行的过程中调用自己。使用递归时,开发者需要注意设置退出条件,否则就会陷入无限循环。
//斐波那契数列:从第三项开始,每一项等于前两项之和
func fibonacci(n int) int {
if n < 2 {
return n
}
return fibonacci(n - 2) + fibonacci(n - 1)
}
//main函数
func main() {
for index := 0 ;index < 10 ;index++ {
fmt.Printf("%d \t",fibonacci(index))
}
}
10、参数传递机制
10.1 按值传递
GO 语言默认传递的是参数副本,函数在接到副本后,使用变量的过程中可能对副本进行更改,但不会影响原来的变量。换句话说,调用函数修改原来参数的值,不会影响原来实参的值,因为数值变化只作用于副本上。
10.2 按引用传递
如果要让函数直接修改参数的值,而不是对参数副本进行修改,就需要将参数的地址传递给函数,这个就是按引用传递,此时传递给函数的是一个指针。如果把指针传递给函数,指针的值就会被赋值,但指针的值指向的地址上的那个值不会被复制。这样一来,修改指针的值,实际上意味着这个值所指向的地址上的值被修改了。
10.3 引用传递的好处
1、传指针使得多个函数能够操作同一个对象
2、传指针比较轻量级(8 B),毕竟支持传内存地址
3、传递指针给函数不但可以节省内存,而且赋予了函数直接修改外部变量变量的能力。
11、defer(延迟语句)
GO 语言中让人颇为称道的一个设计就是 延迟语句,开发者可以在函数中添加多个defer
语句。当函数执行到最后时(return 语句执行之前),这些defer
会按照逆序执行,最后该函数才退出(多个 defer 调用,遵循后进先出的原则,其实就是栈结构(LIFO))。
11.1、用途
在进行一些 IO 操作的时候,如果遇到错误,需要提前返回,返回之前需要关闭响应的资源,否则容易造成资源泄露。
func ReadWrite() bool{
file.Open("file")
if aFailure{ //发生b错误,提前返回,返回之前关闭资源
file.Close()
return false
}else if bFailure{ //发生b错误,提前返回,返回之前关闭资源
file.Close()
return false
}
//正常执行返回
file.Close()
return ture
}
从上面我们可以看到,file.Close()
重复了 3 次。我们可以用 defer 优雅解决问题。
func ReadWrite() bool{
file.Open("file")
defer file.Close()
if aFailure{ //发生b错误,提前返回,返回之前关闭资源
return false
}else if bFailure{ //发生b错误,提前返回,返回之前关闭资源
return false
}
return ture
}
11.2、执行顺序
defer、return、返回值 三者的执行逻辑:
第一步: return 最先给返回值赋值 (若为有名的返回值则直接赋值,若为匿名返回值则先声明再赋值);
第二步: defer 最先执行一些收尾工作;
第三步: RET 指令携带返回值退出函数。
多次调用 defer 时,遵循 后进先出 原则 (LIFO)
//无名返回值
func deferFunction1() int {
var i int
//defer 一个匿名函数调用
defer func(){
i++
fmt.Printf("defer2 := %d\n",i)
}()
//defer 另一个匿名函数调用
defer func() {
i++
fmt.Printf("defer1 := %d\n",i)
}()
return i
}
//有名字的返回值
func deferFunction2() (i int ) {
//defer 一个匿名函数调用
defer func(){
i++
fmt.Printf("defer4 := %d\n",i)
}()
//defer 另一个匿名函数调用
defer func() {
i++
fmt.Printf("defer3 := %d\n",i)
}()
return i
}
//main方法
func main() {
fmt.Println(deferFunction1())
fmt.Println(deferFunction2())
}
输出结果:
defer1 := 1
defer2 := 2
0
defer3 := 1
defer4 := 2
2
这里要特别注意: 函数的返回值是否被命名。
11.3 defer 函数传递参数时
defer 函数调用是,就已经传递参数了,只是暂时不执行函数中的代码而已。其中,main
函数相对于 deferfunc
为 外围函数。
package main
import "fmt"
func main(){
a := 2
fmt.Println(a)
defer deferfunc(a)
a++
fmt.Printf("main中 a = %d\n",a)
}
func deferfunc(a int) {
fmt.Printf("deferfunc中的变量 a = %d\n",a)
}
/** 输出结果
2
main中 a = 3
deferfunc中的变量 a = 2
*/
11.4 当 外围函数的代码引发运行恐慌,只有当其中所有的defer语句执行完毕后,该运行时恐慌才会被真正扩展到调用函数
12、error
GO 语言通过 error
接口实现错误处理的标准模式。对于大部分语言而言,返回错误基本上都可以定义为以下模式,即将 error 作为多返回值中的最后一个返回值。当然,这个并非强制要求。
12.1 如何创建 error
创建error
方式标准库提供了以下两种方法,分别是:
使用 errors
包下的New()
方法创建一个错误
使用fmt
包下的Errorf()
方法创建一个错误
package main
import(
'fmt'
)
func main(){
err1 := erroes.New("创建第一个错误")
err2 := fmt.Errorf("请求失败,错误码为:%d\n",878)
fmt.println(err1,err2)
}
func checkAge(age int) error{
if age < 0{
return fmt.Errorf("年龄为 %d 是不合法的", age)
}
return nil
}
12.2 获取错误信息和自定义错误
获取更多错误信息,可以通过错误类型断言,然后获取字段,或者通过方法获取更多的错误信息。
//通过断言,访问字段获取错误信息
package main
import(
'fmt'
'os'
)
func main(){
file,err := os.Open("test.txt")
if err != nil{
if ins,ok := err.(*PathError);ok{
fmt.Printf("1、Path:%s\n",ins.Path)
fmt.Printf("2、Op:%s\n",ins.Op)
fmt.Printf("3、Err:%s\n",ins.Err)
}
}
fmt.Println("文件打开成功",file.Name())
}
日常开发中,我们常常会根据业务制定一些列违背业务的错误。那就涉及到如何描述这个错误。下面我们举例 GO 系统标准库的实际代码如果使用自定义的 error
类型。
package main
import(
'fmt'
)
func main(){
//计算矩形面积,判断传参是否合法
widht,height := 4.0,5.9
area,err := rectangleArea(widht,height)
if err != nil{
if ins,ok := err.(*areaError);ok{
if ins.heightNegative(){
fmt.Printf("error:高度 %f 小于零",ins.height)
}
if ins.widthNegative(){
fmt.Printf("error:宽度 %f 小于零",ins.width)
}
}
}
fmt.Printf("矩形的面积是:%f",area)
}
//自定义错误类型,并实现 error 接口
type areaError struct{
msg string
height,width float64
}
//实现 error 接口
func(e *areaError) Error()string{
return e.msg
}
func(e *areaError) heightNegative()bool{
return e.height < 0
}
func(e *areaError) widthNegative() bool{
return e.width < 0
}
//计算矩形面积
func rectangleArea(width,height float64)(float64,error){
msg := ''
if width < 0{
msg = "宽度小于零"
}
if height < 0{
if msg == ""{
msg = "高度小于零"
}else{
msg += ",高度小于零"
}
}
if msg != ""{
return 0,&areaError(msg,height,width)
}
return height * width,nil
}
13、panic (恐慌)panic()
是一个内建函数,可以中断原有的控制流程,当 一个 panic 产生,表示程序运行遇到问题,不知道到该怎么办。当 函数 func1 调用 panic 时,函数会被中断执行,但是 func1 中的 defer 函数会正常执行。然后 func1 返回调用他的地方。错误信息将被报告,包括在调用 panic() 函数时传入的参数,这个过程称为错误处理流程。
func panic(v interface{})
13.1 产生
直接调用、运行时产生。 例如: 数组越界。
panic()函数接口任意类型的数据。interface{} 表示任意类型。类似 OC 中的 id 类型
13.2 产生的结果
可以中断原有的控制流程 ,但是 函数所在 defer 函数会正常执行。
func deferPanic() {
// 延迟 一个匿名函数的调用
defer func() {
panic("defer panic")
}()
panic("test panic")
}
func main() {
deferPanic()
}
输出结果:
panic: test panic
panic: defer panic
13.2 panic 和 error 的区别使用
导致关键流程出现不可修复性错误的情况使用 panic,
其他情况使用 error
13.4 panic 是最后手段,代码中应该很少或者没有 panic 语句
14、recover
14.1 作用recover()
也是一个内建函数。可以让处于 panic
状态的程序恢复过来。如果当前程序处于 panic 状态,调用 reccover() 可以捕获到 panic 的输入值,并且恢复正常的执行。
14.2 如何使用
由于 recover()
用于终止错误处理流程,所以一般情况下,recover()
仅在 defer
语句的函数内 直接调用 才有效,否则总是返回 nil。
func test() {
defer func() {
//有效,在 defer 语句中的匿名函数中调用
fmt.Println(recover())
}()
defer func() {
//无效,间接调用 recover() ,返回值 nil
func(){
recover()
}()
}()
//无效 recover()相当于直接调用然后被外部函数打印,返回 nil
defer fmt.Println(recover())
//无效,相当于 直接调用 recover(),返回 nil
defer recover()
panic("发生错误")
}
14.3 如何获取 recover()的返回值
func recoverFunction() {
defer func() {
if r := recover(); r != nil {
log.Printf("捕获到的异常:%v\n",r)
}
}()
defer func() {
panic("第二个错误")
}()
panic("第一个错误")
}
输出结果:
2020/06/26 23:18:22 捕获到的异常:第二个错误
“第一个错误” 早就发生了,但是 recover()
函数中看不到 "第一个错误"的捕获时间,这个因为 recover()
只会捕获最后一个错误,而且捕获时机实在函数最后面,不影响"第一个错误"之后的defer
正常执行。最后,程序正常退出。