第四篇:函数

第四篇:函数

4.1函数基础

4.1.1 认识函数

在这里插入图片描述

在认识函数之前,我们先来看一个场景:

1、在程序里,有4个地方要判断当前用户是不是vip

isVip := true
if isVip == true {
    fmt.Println("尊敬的vip,你好")
}else{
    fmt.Println("给我滚")
}
.....
if isVip == true {
    fmt.Println("尊敬的vip,你好")
}else{
    fmt.Println("给我滚")
}
....
if isVip == true {
    fmt.Println("尊敬的vip,你好")
}else{
    fmt.Println("给我滚")
}
....
if isVip == true {
    fmt.Println("尊敬的vip,你好")
}else{
    fmt.Println("给我滚")
}

这么写有什么问题啊?是不是有很多重复代码,代码很冗长。

现在第二个问题来了:

2、产品经理说,“给我滚”太嚣张了,要用“请滚”。

isVip := true

if isVip == true {
    fmt.Println("尊敬的vip,你好")
}else{
    fmt.Println("请滚")
}
.....
if isVip == true {
    fmt.Println("尊敬的vip,你好")
}else{
    fmt.Println("请滚")
}
....
if isVip == true {
    fmt.Println("尊敬的vip,你好")
}else{
    fmt.Println("请滚")
}
....
if isVip == true {
    fmt.Println("尊敬的vip,你好")
}else{
    fmt.Println("请滚")
}

发现什么问题了没有,你4个地方写了这个功能,要修改的话,是不是得一个个找出来,一个一个修改啊?是不是很难维护你的代码?

下面就要引出我们的函数了。

什么是函数

在程序中,函数就是具备某一功能的工具。

怎么理解这句话呢,我举个例子。

老王现在要去修下水道,他带上了螺丝刀和扳手,遇到应用场景,直接拿出来用就行了。而不是每次都需要重新造一个工具出来。

在这个例子里,螺丝刀和扳手就相当于函数。遇到应用场景我直接用就行了,不需要重新再写功能。

事先将工具准备好,就叫做函数的定义

遇到应用场景拿出来用,就叫做函数的调用

在这里插入图片描述

为什么要有函数

不用函数会造成:1、代码冗长 ;2、可维护性差;3、可读性差

###4.1.2函数定义

格式如下:

func 函数名(参数1 类型,参数2 类型,...)(返回值名1 类型,返回值名2 类型,...){
    函数体代码...
    return 返回值1,返回值2
}

返回值和参数也都可以没有,就可以写成下面这样:

func 函数名 (){
    函数体代码...
}

在这里插入图片描述

###4.1.3函数的调用

函数名(),我们用函数名加()的形式去调用函数。
如果需要传参数,那就要在()里写上参数,如:函数名(参数1,参数2)

如果需要接受返回值,那就声明一个或者多个变量去接收,如:变量名:=函数名(参数1,参数2…)

###4.1.4函数的返回值

1、函数中返回值形式如下:

return 返回值
return 返回值1, 返回值2

2、在需要有返回值的时候,func后面一定要写上返回值的类型,如:

func 函数名()int{}
func 函数名()(int, string){}

3、命名返回值

我也可以给返回值指定变量名,如:

func 函数名()(aa int,bb int){
    aa = 3
    bb = 4
    return          //我后面什么都不写,函数就会在代码体中找到aa和bb并返回
}

4、返回值使用场景

什么时候该用返回值:

调用函数,经过一系列的操作,最后要拿到一个明确的结果,则必须要有返回值。

什么时候不用返回值:

调用函数,仅仅只是执行一系列的操作,最后不需要得到什么结果,则无需有返回值

在这里插入图片描述

###4.1.5函数的参数

1、分类

实参:函数定义时写在()内的变量名,叫做实参

形参:函数调用时写在()内的值,叫做形参

如:

func bigNum(x,y int)int{      //这里的x和y就是形参
    if x>=y {
	    retrun x
    }else{
	    retrun y
    }
}    
var a,b int= 1,2
var biggest = bigNum(a,b)      //这里的a和b就是实参

2、形参中的变长形参

有这么一种情况,我不知道我要传递的参数是几个,可能是2个,可能是3个,也可能更多。这个时候怎么办呢。这就需要变长形参了。我们在参数类型前加上“…”来接收多数据,最后得到一个该类型的切片。如:

func myFu (a ...int){
    fmt.Println(a)
}

myFu(1,2,3,4,5,6)    //结果  [1 2 3 4 5 6]

在这里插入图片描述

##4.2函数进阶

###4.2.1函数变量及匿名函数

有这么一句话,“函数即变量”。

什么意思啊?我们都知道变量名绑定的是一个内存地址,那函数名存的啥啊?代码看看:

func test (){
	fmt.Println("我特么是个函数体代码")
}

fmt.Println(test)     //结果:0x48df40

看到没?函数名指向的也是一个内存地址。

那么我们就想了,如果我把这个内存地址给一个变量,会是什么结果呢?

func test (){
	fmt.Println("我特么是个函数体代码")
}

test2 := test
test2()    //控制台上我们得到了“我特么是个函数体代码”

这个例子就说明了,函数即变量,我们定义函数其实就是定义了一个变量,只是这个变量的类型是函数类型。
我们来看看函数类型长什么样:

func test1(){
    fmt.Println("我是test1")
}

func test2(a,b int){
    fmt.Println("我是test2")
}

func test3(a,b int)string{
    fmt.Println("我是test3")
    return "啦啦啦"
}

fmt.Println(reflect.TypeOf(test1))    //func()
fmt.Println(reflect.TypeOf(test2))    //func(int, int)
fmt.Println(reflect.TypeOf(test3))    //func(int, int) string

现在我们知道了函数类型长什么样,是不是可以像定义变量一样去定义一个函数啊?

我们怎么定义数组的?是不是 变量名 := 类型{值}。我们来试试看用这个方法来定义一个函数,代码如下:

test1 := func(){fmt.Println("我是test1")}
test2 := func(x int){fmt.Printf("我是test2,我有参数%v\n",x)}
test3 := func()string{return "我有返回值"}

//我们来看看test1的值是什么?是不是和func定义的函数名一样也是内存地址
fmt.Println(test1)   // 结果:0x48dec0

//那结果是个函数的内存地址,我加括号是不是可以直接运行呢?
test1()                   // 我是test1
test2(10)                 // 我是test2,我有参数10
fmt.Println(test3())      // 我有返回值

我们接着往下思考。

上面这个例子中,test1、test2、test3的值是内存地址,那么,变量名等号后面的,是不是也是内存地址呢?试试憋:

fmt.Println(func(){fmt.Println("我是test1")})
//结果:0x48ff10

那既然是函数内存地址,加括号()就能运行咯?再试试:

func(){fmt.Println("我是test1")}()
//结果:我是test1

在这里插入图片描述

这个就引出了我们的一个概念,匿名函数。形如func(){fmt.Println("我是test1")}这样没有名字的函数就叫做匿名函数

在这里插入图片描述

###4.2.2 函数的嵌套

1、函数的嵌套调用

func max(a,b int)int{
    if a >= b {
	    return a
    }else{
	    return b
    }
}

func max4(a,b,c,d int)int{
    res1 := max(a,b)
    res2 := max(res1,c)
    res3 := max(res2,d)
    return res3
}

fmt.Println(max4(1,2,3,4))

2、函数的嵌套定义

在go语言里,不支持一个func下再使用func定义函数。
那怎么才能实现嵌套定义呢?还记不记得我们说过的匿名函数,我们可以用变量声明+匿名函数的方式去实现嵌套定义。如下:

func test1(){
    fmt.Println("我是最外层函数")
    //声明变量+匿名函数
    test2 := func(){
	    fmt.Println("我是里层函数")
    }
    test2()
}

test1()
//结果:我是最外层函数
//		我是里层函数

###4.2.3作用域

1、语法块

语法块就是一个代码块,一个{}内的代码就是一个语法块,语法块内的变量叫做局部变量,如:

if 2>1 {
    a := 3
}

for i := 1;i < 3;i++ {
    fmt.Println(i)
}

func test(){
    fmt.Println("我在test函数的语法块里")
}

我们也可以自己定义一个语法块,用{}把代码括起来就行,如下:

func test(){
    fmt.Println("我在test函数的语法块里")
    {
	    fmt.Println("我在自定义语法块里")
    }
}

2、作用域

在这里插入图片描述

作用域就是变量的有效范围。

分类:全局作用域,局部作用域

局部作用域:一个语法块,就是一个局部作用域,定义的变量叫做局部变量,仅在当前语法块有效

全局作用域:在{}以外的地方定义的变量,叫做全局变量,全局有效

变量的查找顺序:局部—>…—>全局

package main

import "fmt"

var a = 10

func test(){
	a := 100
	{
		a = 200
	}
	fmt.Println(a)
}

func main() {
	fmt.Println(a)   //结果:10
	test()           //结果:200
}

程序运行逻辑:

1、首先执行main函数里的fmt.Println(a)

2、局部作用域里找变量a,没有

3、全局作用域里找变量a,找到了,10

4、执行main函数里的)test()

5、局部作用域里找test,没有

6、全局作用域里找test,找到了,然后执行test里的代码

7、a:=100,那就在test函数的局部作用域声明了一个局部变量

8、{a = 200},这个局部作用域里,没有变量a,就往外层找,找到上一层中定义的a,然后改成200

9、fmt.Println(a),在局部作用域里找变量a,找到了,这时的值是200

###4.2.4闭包函数

在这里插入图片描述

什么是闭包函数:

闭包就是闭合和包起来的意思。

什么是包起来?就是一个函数被另一个函数包起来,这有点像我们之前讲的嵌套定义,不过这里外层函数的返回值是里层函数的函数名,如下:

func test()func(){
	inter := func() {
		fmt.Println("我被包起来了")
	}
	return inter
}

什么是闭合?就是内层函数引用的外部变量有且只能来自于外层函数,如下:

func test()func(){
	var a =  0
	inter := func() {
		a ++                //我引用了外部变量,这个变量来自于它的外层函数
		fmt.Println(a)
}
	return inter
}

f := test()   //f就相当于inter
f()           //a ++ ,修改它外层作用域里的a,a的值修改后为1
f()           //a ++ ,依旧是修改外层函数的a,a的值当前为1,修改后为2

闭包函数的应用:

1、延时计算

2、装饰器,但在go里没有现成的封装好的装饰器语法,得自己写

在这里插入图片描述

4.2.5panic恐慌

panic在英语里是恐慌的意思,程序员的恐慌无疑就是程序运行时报错。

程序报错可以分为两种:1、编译时出错;2、运行时出错

1、编译时出错如下:

在这里插入图片描述

2、panic恐慌如下:

在这里插入图片描述

4.2.5.1 panic恐慌到程序崩溃

下面是panic到程序崩溃的流程图:

在这里插入图片描述

说明:

1、程序一步步运行到test2下的a := myArry[4]引发panic恐慌

2、剩下的代码不会执行,控制权转移给test2函数

3、接着test2函数退出,控制权转移给test1函数。

4、接着test1函数退出,控制权转移给main函数

5、main函数退出,程序崩溃

在这里插入图片描述

4.2.5.1panic函数

我们可以通过panic函数,手动触发panic恐慌。

panic(“提示信息”),如下:

func main(){
    fmt.Println("进入主函数")
    panic("蠢货,我是panic,怕不怕")
    fmt.Println("退出主函数")
}

//进入主函数
//panic: 蠢货,我是panic,怕不怕
//goroutine 1 [running]:
//main.main()
	//D:/GoWorks/src/demo1/test.go:7 +0x80
//Process finished with exit code 2

4.2.6recover恐慌平息

panic恐慌发生了,怎么办?当然得处理,不然会被炒鱿鱼!

如何处理,这就是我们要说的recover恐慌平息

用法很简单,如下:

errMessage := recover()

//recover函数有一个返回值,用来接收panic恐慌的报错信息
//recover函数在panic恐慌发生后运行,来平息恐慌

注意:

你们有没有发现什么问题?我panic恐慌发生后,后面的代码就不会再执行了,而我recover又要在panic恐慌之后运行。是不是很矛盾?

你们想的没错,的确是这样,recover()放在panic之后是不会执行的:

func main(){
    fmt.Println("进入主函数")
    panic("蠢货,我是panic,怕不怕")
    errMessage := recover()
    fmt.Println(errMessage)
}

//进入主函数
//panic: 蠢货,我是panic,怕不怕
//goroutine 1 [running]:
//main.main()
	//D:/GoWorks/src/demo1/test.go:9 +0x80
//Process finished with exit code 2

那既然这样,我recover还有什么意义啊?

别急,这就要引出我们下一个知识点,defer延时函数

###4.2.7defer延时函数

在这里插入图片描述

1、什么是defer延时函数

延时很好理解,就是我预约之后过段时间再执行嘛。

func main(){
	defer fmt.Println("我是第一个defer")
	fmt.Println("哈哈哈哈")
}

//哈哈哈哈
//我是第一个defer

函数先执行了defer之后的代码,最后回过来再执行defer函数

func main(){
	defer fmt.Println("我是第一个defer")
	defer fmt.Println("我是第二个defer")
	defer fmt.Println("我是第三个defer")
	fmt.Println("哈哈哈哈")
}

//哈哈哈哈
//我是第三个defer
//我是第二个defer
//我是第一个defer

1、我第一个defer告诉函数,我要最后一个执行。
在这里插入图片描述
2、第二个defer也告诉函数,我要最后一个执行。程序就说了,这不行啊,已经有人预约了最后一个执行了,我把你放在它后面把,你倒数第二个执行。

3、第三个defer也出来了。程序说,不行,前面有两个人预约了,你不能最后一个,这样你倒数第三个。

2、defer实现原理:

那多个defer,程序是怎么实现的呢?堆积木大家都堆过,多个defer就像堆积木:

先进后出!

3、怎么用defer函数:

defer函数多用于处理panic恐慌:

func main(){
	defer func() {
		message := recover()
		fmt.Println(message)
	}()
	panic("我是你的噩梦")
	fmt.Println("哈哈哈哈")
}

//我是你的噩梦

1、程序运行到panic的时候,后面就不执行了,控制权给了main()函数

2、正常情况下,程序会直接崩溃,但main函数说,等等,我还没执行完,有个defer预约了

3、执行defer函数,运行到recover之后,panic恐慌就被平息了

4、函数正常退出

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值