欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

第四篇:函数

程序员文章站 2022-06-17 12:01:49
...

第四篇:函数

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、函数正常退出

第四篇:函数

相关标签: go系列教程 go