网站开发费用报价单/seo课程培训视频
一、函数定义
1.1、函数的定义
Go语言中定义函数使用func关键字,具体格式如下:
func 函数名(参数 type)(返回值type){
函数体
}
- 函数名:由字母、数字、下划线组成。但函数名的第一个字母不能是数字。在同一个包内,函数名也称不能重名(包的概念详见后文)。
- 参数:参数由参数变量和参数变量的类型组成,多个参数之间使用,分隔。
- 返回值:返回值由返回值变量和其变量类型组成,也可以只写返回值的类型,多个返回值必须用()包裹,并用,分隔。
- 函数体:实现指定功能的代码块。
1.2、函数的使用(汇总)
// 定义一个求和函数(有参数),有return返回值时(需要给返回值定义一个类型) func s1(x int, y int) int {return x + y }// 定义一个求和函数(有参数),没有return返回值时 func s2(x int, y int) {fmt.Println(x + y) }// 定义一个函数(无参数),没有return返回值时 func s3() {fmt.Println("szq") }// 定义一个函数(无参数),有return返回值时(需要给返回值定义一个类型) func s4() string {return "sudada" }// return返回值 可以命名(命名后可以直接使用,因为已经做了声明),也可以不命名 // return返回值"szq"在已经声明好的情况下,return后可以什么都不写,默认返回的就是"szq" func s5(x int, y int) (szq int) {szq = x + yreturn // 这里就可以不写szq,写上也没关系 }// 多个返回值 func s6() (int, string) {return 1, "2" }// 参数类型的简写(2个参数连续且类型一致时,可以这么写) func s7(x, y int) int {return x + y }// 可变长参数(...)必须放在函数参数的最后, y可以不传参,可以传一个参数,也可以传多个参数(拿到的就是一个切片类型的值) func s8(x string, y ...int) {fmt.Println(x)fmt.Println(y) // [1 2 3 4] 拿到的是一个int类型的切片 }// go语音中函数没有默认参数的概念 func main() {fmt.Println(s1(1, 2))s2(2, 3)s3()fmt.Println(s4())fmt.Println(s5(2, 2))fmt.Println(s6())s8("szq") }
1.3、函数传参
1.3.1、类型简写
函数的参数中如果相邻变量的类型相同,则可以省略类型,例如:
// 参数类型的简写(2个参数连续且类型一致时,可以这么写) func s7(x, y int) int {return x + y }
上面的代码中,s7函数有两个参数,这两个参数的类型均为int,因此可以省略x的类型,因为y后面有类型说明,x参数也是该类型。
1.3.2、可变长参数
可变参数是指函数的参数数量不固定。Go语言中的可变参数通过在参数名后加...来标识。
注意:可变参数通常要作为函数的最后一个参数。// 可变长参数(...)必须放在函数参数的最后, y可以不传参,可以传一个参数,也可以传多个参数(拿到的就是一个切片类型的值) func s8(x string, y ...int) {fmt.Println(x)fmt.Println(y) // [1 2 3 4] 拿到的是一个int类型的切片 }
可变长参数使用:
func intSum2(x ...int) int {fmt.Println(x) // x是一个切片sum := 0for _, v := range x {sum = sum + v}return sum }func main() {ret1 := intSum2()ret2 := intSum2(10)ret3 := intSum2(10, 20)ret4 := intSum2(10, 20, 30)fmt.Println(ret1, ret2, ret3, ret4) //0 10 30 60 }
固定参数搭配可变参数使用时,可变参数要放在固定参数的后面,示例代码如下:
func intSum3(x int, y ...int) int {fmt.Println(x, y)sum := xfor _, v := range y {sum = sum + v}return sum }func main() {ret5 := intSum3(100)ret6 := intSum3(100, 10)ret7 := intSum3(100, 10, 20)ret8 := intSum3(100, 10, 20, 30)fmt.Println(ret5, ret6, ret7, ret8) //100 110 130 160 }
本质上,函数的可变参数是通过切片来实现的。
1.4、函数 返回值
Go语言中通过return关键字向外输出返回值。
1.4.1、多返回值
Go语言中函数支持多返回值,函数如果有多个返回值时必须用()将所有返回值包裹起来。func calc(x, y int) (int, int) {sum := x + ysub := x - yreturn sum, sub } func main() {a, b := calc(1, 2)fmt.Println(a)fmt.Println(b) }
1.4.1、多返回值命名
函数定义时可以给返回值命名,并在函数体中直接使用这些变量,最后通过return关键字返回。// return返回值 可以命名(命名后可以直接使用,因为已经做了声明),也可以不命名 // return返回值"szq"在已经声明好的情况下,return后可以什么都不写,默认返回的就是"szq" func s5(x int, y int) (szq int) {szq = x + yreturn // 这里就可以不写szq,写上也没关系 }// 例子2 func calc(x, y int) (sum, sub int) {sum = x + ysub = x - yreturn }func main() {a, b := calc(1, 2)fmt.Println(a)fmt.Println(b) }
二、函数作用域
2.1、全局变量
全局变量是定义在函数外部的变量,它在程序整个运行周期内都有效。 在函数中可以访问到全局变量。
函数中查找变量的顺序:
1.先在函数的内部查找
2.找不到就往函数的外面查找,一直找到全局,如果找不到就报错。var szq = 123func s0() {fmt.Println(szq) }func main() {s0() // 123 }
2.2、局部变量
1.局部变量又分为两种: 函数内定义的变量无法在该函数外使用
func testLocalVar() {//定义一个函数局部变量x,仅在该函数内生效var x int64 = 100fmt.Printf("x=%d\n", x) }func main() {testLocalVar()fmt.Println(x) // 此时无法使用变量x }
2.如果局部变量和全局变量重名,优先访问局部变量。
//定义全局变量num var num int64 = 10func testNum() {num := 100fmt.Printf("num=%d\n", num) // 函数中优先使用局部变量 } func main() {testNum() // num=100 }
2.3、语句块定义的变量
1.通常我们会在if条件判断、for循环、switch语句上使用这种定义变量的方式。
func testLocalVar2(x, y int) {fmt.Println(x, y) //函数的参数也是只在本函数中生效if x > 0 {z := 100 //变量z只在if语句块生效fmt.Println(z)}fmt.Println(z) //此处无法使用变量z } func main() {testLocalVar2(1,2) }
2.还有for循环语句中定义的变量,也是只在for语句块中生效:
func testLocalVar3() {for i := 0; i < 10; i++ {fmt.Println(i) //变量i只在当前for语句块中生效}fmt.Println(i) //此处无法使用变量i }func main() {testLocalVar3() }
三、函数类型与变量
3.1、定义函数类型
1.什么是函数类型,举个例子:
// 函数作为参数赋值给变量时,有哪些类型 func s1(){fmt.Println("szq") }func s2() int{return 18 }func s3(x int) int{return x+1 }func main() {a := s1// 打印变量a的类型fmt.Printf("%T\n",a) //func():这是一个普通的函数类型b := s2// 打印变量b的类型fmt.Printf("%T\n",b) //func() int :这是一个函数类型(包括函数的返回值类型)c := s3// 打印变量c的类型fmt.Printf("%T\n",c) //func(int) int :这是一个函数类型(包括函数的参数类型和函数的返回值类型) }
2.函数作为一个参数
// 定义函数s1 func s1() int {return 18 }// s1函数作为参数传递给s2函数 func s2(x func() int){res := x()fmt.Println(res) }func main() {re1 := s1 // 将函数赋值给变量re1fmt.Printf("%T\n",re1) // func() ints2(s1) // 函数s1作为参数传递给函数s2(s1函数的类型必须满足s2的接收参数类型,即:func() int)s2(re1) // 变量re1作为参数传递给函数s2(变量re1的类型必须满足s2的接收参数类型,即:func() int) }
3.函数作为一个返回值
// 定义函数s1 func s1() int {return 18 }func ff(a int,b int)int{return a+b }// 函数作为一个返回值返回 // x func() int 表示x的类型为:func() int // y func(int,int)int 表示s3函数的返回值类型为:func(int,int)int func s3(x func() int)(y func(int,int)int){fmt.Println(x())res := func(a int,b int)int{return a+b}//res := ff // 这里也可以换一种写法,ff函数的格式需要满足y即可return res }func main() {szq:=s3(s1) // 把函数s1做为参数传入到函数s3中,执行函数s3的代码后拿到一个返回值resfmt.Println(szq(1,2)) // 拿到的返回值res类型为"函数类型:func(int,int)int",那么就可以正常的传值,查看执行结果 } //返回结果: //18 //3
3.2、函数类型变量 (变量的类型为函数类型,然后为该变量赋值)
了解以上函数类型之后,我们可以使用type关键字来定义一个函数类型,具体格式如下:
type calculation func(int, int) int
上面语句定义了一个calculation类型,它是一种函数类型,这种函数接收两个int类型的参数并且返回一个int类型的返回值。// 定义一个函数类型 type calculation func(int, int) int// 定义一个全局变量c,c的类型为calculation var c calculation// 定义一个普通函数,类型为:add(x, y int) int func add(x, y int) int {return x + y }func main() {c = add // 给变量c做赋值操作(变量c = add函数)fmt.Println(add(1,2)) // 3fmt.Printf("%T\n",c) // main.calculationfmt.Println(c(5,5)) // 像调用add一样调用c,返回值为:10f := add // 将函数add赋值给变量ffmt.Printf("type of f:%T\n", f) // type of f:func(int, int) intfmt.Println(f(10, 20)) // 像调用add一样调用f,返回值为:30 }
四、defer语句
4.1、Go语言中的
defer
语句会将其后面跟随的语句进行延迟处理。在defer
归属的函数即将返回时,将延迟处理的语句按defer
定义的逆序进行执行,也就是说,先被defer
的语句最后被执行,最后被defer
的语句,最先被执行。举个例子:
func main() {fmt.Println("start")defer fmt.Println(1)defer fmt.Println(2)defer fmt.Println(3)fmt.Println("end") } //输出结果: //start //end //3 //2 //1
由于
defer
语句延迟调用的特性,所以defer
语句能非常方便的处理资源释放问题。比如:资源清理、文件关闭、解锁及记录时间等。4.2、defer执行时机
在Go语言的函数中
return
语句在底层并不是原子操作,它分为给返回值赋值和RET(return)指令两步。而defer
语句执行的时机就在返回值赋值操作后,RET(return)指令执行前。具体如下图所示:
4.3、defer经典案例
// 第一步:返回值赋值 //defer //第二步:真的的return,返回一个值func f1() int {x := 5defer func() {x++ // 修改的是x,不是返回值}()return x }func f2() (x int) {defer func() {x++}()return 5 // 返回值为x }func f3() (y int) {x := 5defer func() {x++ // 修改的是x}()return x // 返回值为y,y的值=x=5 } func f4() (x int) {defer func(x int) {x++ // 改变的是函数中的副本}(x)return 5 // 返回值=x=5 }func main() {fmt.Println(f1()) //5fmt.Println(f2()) //6fmt.Println(f3()) //5fmt.Println(f4()) //5 }
4.4、defer面试题
func calc(index string, a, b int) int {ret := a + bfmt.Println(index, a, b, ret)return ret }func main() {// var x int = 1x := 1// var y int = 2y := 2// 函数内套用函数调用时,代码走到这一层就会先执行函数内的函数,会把对应变量的值传过去。 即:defer calc("AA", 1, calc("A", 1, 2)) == defer calc("AA", 1, 3),详见下面的分析结果:defer calc("AA", x, calc("A", x, y))// 这里的x因为在前面已经做了定义,所以可以直接用"="号赋值x = 10// 函数内套用函数调用时,代码走到这一层就会先执行函数内的函数,会把对应变量的值传过去即:defer calc("BB", 10, calc("B", 10, 2)) == defer calc("BB", 10, 12),详见下面的分析结果:defer calc("BB", x, calc("B", x, y))// 这里的y因为在前面已经做了定义,所以可以直接用"="号赋值y = 20 }// 分析以上代码的执行过程: // 1)x := 1 // 2)y := 2 // 3)defer calc("AA", x, calc("A", x, y)) == defer calc("AA", 1, calc("A", 1, 2)) == defer calc("AA", 1, 3) // 4)x = 10 // 5)defer calc("BB", x, calc("B", x, y)) == defer calc("BB", 10, calc("B", 10, 2)) == defer calc("BB", 10, 12) // 6)y = 20 // 7)defer calc("BB", 10, 12) == BB 10 12 22 // 8)defer calc("AA", 1, 3) == AA 1 3 4//输出结果: //A 1 2 3 先执行函数内的函数 //B 10 2 12 先执行函数内的函数 //BB 10 12 22 在按照defer的逻辑,最后定义的最先执行 //AA 1 3 4 按照defer的逻辑,最先定义的最后执行
五、匿名函数和闭包
5.1、匿名函数 (匿名函数多用于实现回调函数和闭包,匿名函数一般都用在函数内部)
函数当然还可以作为返回值,但是在Go语言中函数内部不能再像之前那样定义函数了,只能定义匿名函数。匿名函数就是没有函数名的函数,匿名函数的定义格式如下:
func(参数)(返回值){
函数体
}1.匿名函数作为变量执行,例子如下:
// 匿名函数使用1 var f1 = func(x,y int){fmt.Println(x+y) }func main() {f1(1,2) // 调用匿名函数,返回值为:3// 匿名函数使用2f2 := func(x,y int){fmt.Println(x+y)}f2(10,20) // 调用匿名函数,返回值为:3// 如果只是调用一次的函数,可以简写成"立即执行函数"func(x,y int){fmt.Println(x+y)}(2,3) // 调用"立即执行函数",返回值为:5 }
2.匿名函数作为立即执行函数(只是一次调用的函数),例子如下:
func main() {// 如果只是调用一次的函数,可以简写成"立即执行函数"func(x,y int){fmt.Println(x+y)}(2,3) // 调用"立即执行函数",返回值为:5 }
5.2、闭包 (一个函数除了引用函数内定义的变量外,还引用了函数外部的变量)
闭包的底层原理:
1.函数可以作为返回值,
2.函数内部查找顺序,现在函数内部找,找不在再往外部找。闭包指的是一个函数和与其相关的引用环境组合而成的实体。简单来说,
闭包=函数+引用环境
。 首先我们来看一个例子:// 闭包的实际应用,要求:s1(s2) 把s2作为参数传入到s1 func s1(f func()) {fmt.Println("s1")f() }func s2(x, y int) {fmt.Println("s2")fmt.Println(x + y) }//s3函数要接收s2函数的传参,并且返回值的类型要符合s1函数参数类型 //f func(int, int), x, y int 这些都是给s2函数使用的 //func() 这个是给s1函数使用的 func s3(f func(int, int), x, y int) func() {res := func() {f(x, y)}return res // 这里的返回值res是一个func()类型,符合s1函数的接收类型 }func main() {ret := s3(s2, 100, 300) // 这里ret也就等于s3函数内的ress1(ret) // s1函数传入ret,本质上执行的是s2函数的代码。 }//输出结果 //s1 //s2 //400
例子2:在函数内修改函数的参数值
func calc(base int) (func(int) int, func(int) int) {add := func(i int) int {base += ireturn base}sub := func(i int) int {base -= ireturn base}return add, sub }func main() {f1, f2 := calc(10)fmt.Println(f1(1), f2(2)) //11 9,这里每次改的都是calc函数内的base,也就是说随着每次的改修,base的值会发生改变。fmt.Println(f1(3), f2(4)) //12 8fmt.Println(f1(5), f2(6)) //13 7 }
六、内置函数
6.1、内置函数介绍
内置函数 介绍 close 主要用来关闭channel len 用来求长度,比如string、array、slice、map、channel new 用来分配内存,主要用来分配值类型,比如int、struct。返回的是指针 make 用来分配内存,主要用来分配引用类型,比如chan、map、slice append 用来追加元素到数组、slice中 panic和recover 用来做错误处理 6.2、panic 错误处理
func funcA() {fmt.Println("func A") }func funcB() {panic("出现了严重的错误!") // 主动报错 }func funcC() {fmt.Println("func C") } func main() {funcA()funcB()funcC() }// 输出结果: // func A // 报错之前执行的代码 // panic: 出现了严重的错误! // 报错的输出// goroutine 1 [running]: // main.funcB(...) // 报错的代码位置点 // /Users/suzhaoqiang/Desktop/Go/Goland_Document/学习过程/2021-8-16/05.go:10 // main.main() // 报错代码的执行点 // /Users/suzhaoqiang/Desktop/Go/Goland_Document/学习过程/2021-8-16/05.go:18 +0x96
6.3、panic + recover 接收错误信息,代码继续执行
注意:
recover()必须搭配defer使用。
defer一定要在可能引发panic的语句之前定义。func funcA() {fmt.Println("func A") }func funcB() {defer func() {err := recover() // recover()拿到的就是panic的输出。使用recover()后程序不会崩溃退出,继续往下执行。fmt.Println(err)}()panic("出现了严重的错误!") // defer要在panic之前定义,否则panic程序崩溃,没法执行defer语句了。 }func funcC() {fmt.Println("func C") } func main() {funcA()funcB()funcC() }//输出结果: //func A //出现了严重的错误! //func C