【Go语言学习】——函数

函数


参考博客

package main

import (
	"fmt"
)

//函数定义,注意函数中参数和返回类型都是值在前,类型在后面
// func 函数名(参数值 类型,参数值 类型)(返回值,返回类型){
//     函数体
// }

//如果返回只有一个值,可以直接写成一个类型代替(返回值,返回类型)

//有参数也有返回值
//返回值可以命名也可以命名,命名就相当于在函数中提前声明了一个变量,而且返回时可以不写名称,默认返回声明中的返回值
func fun1(x int, y int) (ret int) {
	ret = x + y
	return //默认返回ret
}

//没有参数但有返回值,返回参数没有提前命名,需要明显写出返回值
func fun2() int {
	ret := 999
	return ret
}

//有参数无返回值
func fun3(x int, y int) {
	fmt.Println(x + y)
}

//多个返回值
func fun4() (int, string) {
	return 1, "卡卡"
}

//参数简写,当有连续多个参数类型相同时,可以省略除最后一个的参数的类型
func fun5(x, y int, m, n string) int {
	return x + y
}

//可变长参数,其中y...代表可变长度的参数,可以为0至任意,实际上就是生产一个对应类型的切片
//注意可变长参数必须位于参数的最后
func fun6(x string, y ...int) {
	fmt.Println(x)
	fmt.Printf("type:%T,value:%v\n", y, y) //类型为[]int
}

//main函数就是无参数无返回值
func main() {
	r := fun1(1, 2)
	fmt.Println(r)
	fmt.Println(fun2())
	fun3(3, 4)
	m, n := fun4() //接收时要用两个变量
	fmt.Println(m, n)
	fmt.Println(fun5(5, 6, "111", "222"))
	fun6("天亮了")
	fun6("天黑了", 1, 2, 3, 4, 5, 6, 7)
    //在一个命名的函数中不能再声明命名函数,可以使用匿名函数
}
  • defer
    defer 后的语句会按照逆序延迟执行,执行时机在返回值和执行ret命令退出函数的中间,常常用于资源清理,文件关闭,解锁和记录时间等。而需要注意的点如下:

    return中返回值是指如果定义函数时声明了返回值变量,那么就为这个变量赋值,否则就是用另外一个变量(与函数里的都不同)接受值。所以使用defer修改返回值时要注意是否修改的是返回的那个变量。
    defer执行会先将后面跟随的语句进行注册然后延迟执行,语句中的所有参数都需要被确认,所以如果defer后面是一个嵌套函数,那么会先把内层的函数执行获得结果,然后再注册以便后续的倒叙延迟执行

    package main
    
    import "fmt"
    
    //defer后的语句会被延迟处理,而且按照defer定义的逆序执行
    //defer执行时机在返回值和执行ret命令中间,即先返回x,然后执行defer,最后执行ret命令退出函数
    //这也说明了return不是原子操作,分为返回值赋值和执行ret指令,defer执行在两个操作中间
    func deferdemo() {
    	fmt.Println("start")
    	defer fmt.Println(1)
    	defer fmt.Println(2)
    	defer fmt.Println(3)
    	fmt.Println("end")
    }
    
    //5,因为返回值没有命名,所以会把x的值赋值给另外一个变量,不是x,所以执行defer对x改变不会影响到返回值
    func f1() int {
    	x := 5
    	defer func() {
    		x++
    	}()
    	return x //返回值=x
    }
    
    //6,返回值是x,所以先是将5赋值给x,然后对x改变就是对返回值进行改变
    func f2() (x int) {
    	defer func() {
    		x++ //修改x的值就是修改返回值
    	}()
    	return 5 //返回值x=5
    }
    
    //5,返回值是y,所以先是将x的值赋值给y,然后对x的值修改不会影响到y的值
    func f3() (y int) {
    	x := 5
    	defer func() {
    		x++ //修改x的值不影响y
    	}()
    	return x //返回值y=x=5
    }
    
    //5,虽然返回值是x,并且对x进行了修改,但是修改的x位于匿名函数中,也就是局部变量x,所以修改的x不是同一个x
    func f4() (x int) {
    	defer func(x int) {
    		x++ //这里的x是匿名函数自己的局部变量,不是返回值x
    	}(x) //将x的值复制给匿名函数的参数
    	return 5 //返回值x=5
    }
    
    //5,在匿名函数中增加了返回值,但不会影响到外部的返回值
    func f5() (x int) {
    	defer func(x int) int {
    		x++      //这里的x是匿名函数自己的局部变量,不是返回值x
    		return x //虽然返回了x但是没有返回给外部的返回值,所以不会改变返回值
    	}(x) //将x的值复制给匿名函数的参数
    	return 5 //返回值x=5
    }
    
    //6,在defer中传递指针,在函数中通过指针修改了返回值
    func f6() (x int) {
    	defer func(x *int) {
    		(*x)++ //因为传入的是指针,所以这里的x指向了返回值的地址,因此对该地址的值修改是会影响到返回值的
    	}(&x) //将x的指针传入匿名函数
    	return 5 //返回值x=5
    }
    
    func calc(index string, a, b int) int {
    	ret := a + b
    	fmt.Println(index, a, b, ret)
    	return ret
    }
    
    func main() {
    	deferdemo()
    	fmt.Println(f1())
    	fmt.Println(f2())
    	fmt.Println(f3())
    	fmt.Println(f4())
    	fmt.Println(f5())
    	fmt.Println(f6())
    	//defer注册要延迟执行的函数时该函数所有的参数都需要确定其值,而且defer针对嵌套函数只会延迟执行外层,里层的函数会先执行得到结果来确定参数
    	x := 1
    	y := 2
    	defer calc("AA", x, calc("A", x, y))
    	x = 10
    	defer calc("BB", x, calc("B", x, y))
    	y = 20
    	//首先输出A 1 2 3,确定外层函数的第三个参数先执行内部的calc("A", x, y),此时x=1,y=2,返回结果3并注册
    	//然后输出B 10 12 22,同样为了确定第三个参数先执行calc("B", x, y),此时x=10,y=2,返回结果12并注册
    	//然后开始执行defer的内容,先执行后一个calc,注册时为calc("BB", 10, 12)
    	//最后执行前一个defer的内容,注册时为calc("AA", 1, 3)
    }
    
    • 函数的作用域

    函数作用域包括全局变量作用域,局部变量作用域,代码块的作用域,函数变量取值的顺序就是先看函数内部找,然后找外部的变量。即代码块内先找,然后在代码块外找,先看局部变量,再看全局变量

    package main
    
    import "fmt"
    
    //定义全局变量初始化值时直接用=
    var x = 100
    
    //全局变量任何地方都能使用
    func f1() {
    	fmt.Println(x)
    }
    
    //函数查找变量同名变量优先使用局部变量
    func f2(x int) {
    	y := 50
    	fmt.Println(x, y)
    }
    
    //语句块的作用域,比如if和for语句块中声明的变量只能在对应语句块中有意义
    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() {
    	f1()
    	f2(90)
    	testLocalVar2(1, 2)
    	// fmt.Println(y)局部变量中的变量只能在自己的函数里面使用,此处y不能使用
    
    	//语句块作用域只能在对应语句块中使用
    	for i := 0; i < 10; i++ {
    		fmt.Println(i) //变量i只在当前for语句块中生效
    	}
    	//fmt.Println(i) //此处无法使用变量i
    }
    
  • 函数类型

    go语言中函数可以是一个变量的类型,因此函数的参数或者返回值的类型都可以是一个函数,而函数的参数和返回值的有无以及类型的不同就会导致函数类型的不同。给变量赋值不同的函数类型时只需要不写上括号就代表是赋值,如果写上括号就代表是调用函数

    package main
    
    import "fmt"
    
    //函数可以赋值给变量,类型就是对应函数类型
    //函数也可以作为参数的类型,有无或者不同的参数类型和有无或者不同的返回值类型的函数类型不同
    
    func f1() {
    	fmt.Println("Hello kaka")
    }
    
    func f2() int {
    	return 10
    }
    
    //函数作为函数的参数,类型就是func()返回类型
    func f3(x func() int) {
    	ret := x()
    	fmt.Println(ret)
    }
    
    func f4(x, y int) int {
    	return x + y
    }
    
    //函数还可以作为返回值
    func f5(x func() int) func(int, int) int {
    	return f4
    }
    
    func add(x, y int) int {
    	return x + y
    }
    
    func main() {
    	//可以把函数赋值给一个变量,但不要写括号,不然就是调用这个函数了
    	a := f1
    	fmt.Printf("%T\n", a) //类型是func()
    	b := f2
    	fmt.Printf("%T\n", b) //类型是func() int 有返回值的与上面的类型不一样
    	f3(b)
    	c := f3
    	fmt.Printf("%T\n", c) //类型是func(func() int) 带对应参数类型的func
    	f7 := f5(f2)
    	fmt.Printf("%T", f7) //类型是func(int,int) int,因为f7是调用f5函数的返回值结果
    
    	//使用type定义类型然后赋值给变量
    	//type的使用格式:type 类型名 类型
    	type calculation func(int, int) int
    	var d calculation = add
    	fmt.Printf("%T\n", d)
    	fmt.Println(d(1, 2))
    
    	// 直接定义类型然后赋值
    	var f func(int, int) int=add
    	fmt.Printf("%T\n", f)
    	fmt.Println(f(3, 4))
    }
    
    
  • 匿名函数

    package main
    
    import "fmt"
    
    //匿名函数就是不用写名字的函数,需要保存给一个变量再使用或者直接调用
    //匿名函数一般用在函数内部,多用于实现回调函数和闭包
    
    func main() {
    
    	//将匿名函数保存给一个变量然后再使用
    	f1 := func(x, y int) {
    		fmt.Println(x + y)
    	}
    	f1(10, 20)
    
    	//如果只调用一次的函数,还可以简写成立即执行函数
    	func(x, y int) {
    		fmt.Println(x + y)
    	}(100, 200) //这里是传入的参数
    }
    
  • 闭包

    闭包就是指函数加上引用的外部域的变量,主要原理如下:函数可以作为返回值;函数查找变量的值由内向外查找。而且当给一个声明一个变量为一个闭包后,它所引用的外部变量的值的和闭包的生命周期一样,所以对闭包进行多次调用执行的结果是相互影响的。

    package main
    
    import (
    	"fmt"
    	"strings"
    )
    
    //闭包就是函数(未执行)和引用的外部作用域的变量
    //闭包的原理就是:函数可以作为返回值;函数查找变量的值先查找自己函数内部,然后再往外部找
    
    func adder() func(int) int {
    	var x int
    	return func(y int) int {
    		x += y
    		return x
    	}
    }
    
    func adder2(x int) func(int) int {
    	return func(y int) int {
    		x += y
    		return x
    	}
    }
    
    //希望f1(f2)
    func f1(f func()) {
    	fmt.Println("this is f1")
    	f() //调用传入的函数
    }
    
    func f2(x, y int) {
    	fmt.Println("this is f2")
    	fmt.Println(x + y)
    }
    
    func f3(f func(int, int), x, y int) func() {
    	return func() {
    		f(x, y)
    	}
    
    }
    
    //实现前缀加后缀字符串
    func makeSuffixFunc(suffix string) func(string) string {
    	return func(name string) string {
    		if !strings.HasSuffix(name, suffix) {
    			return name + suffix
    		} else {
    			return name
    		}
    	}
    }
    
    //同时实现加减法
    func calc(base int) (func(int) int, func(int) int) {
    	add := func(i int) int {
    		base += i
    		return base
    	}
    	sub := func(i int) int {
    		base -= i
    		return base
    	}
    	return add, sub
    }
    
    //定义闭包f后匿名函数(未执行)和外部作用域的变量一直有效,值一直保存着
    func main() {
    	f := adder()
    	//f不仅包含了adder中的匿名函数,还包括了引用的外部作用域的x
    	fmt.Println(f(10)) //10
    	fmt.Println(f(20)) //10+20=30
    	fmt.Println(f(30)) //30+30=30
    
    	g := adder2(100)
    	//f2包含了adder2中的匿名函数,还有已经赋值为100的x
    	fmt.Println(g(200)) //100+200=300
    	fmt.Println(g(300)) //300+300=600
    	fmt.Println(g(400)) //600+400=1000
    
    	//实现f1(f2)
    	//注意函数作为参数传入后不会自己执行,需要在函数内再调用才能执行
    	f1(f3(f2, 1, 2))
    
    	//实现前缀加上后缀
    	jpgFunc := makeSuffixFunc(".jpg") //jpgFunc含有匿名函数和赋值为.jpg的字符串suffix
    	txtFunc := makeSuffixFunc(".txt") //txtFunc含有匿名函数和赋值为.txt的字符串suffix
    	fmt.Println(jpgFunc("test"))      //test.jpg
    	fmt.Println(jpgFunc("fff.jpg"))   //fff.jpg,含有jpg后缀了直接输出
    	fmt.Println(txtFunc("test"))      //test.txt
    
    	//同时实现加减法
    	add, sub := calc(10)        //包含了两个匿名函数且使用了同一个外部引用base,因此值互通
    	fmt.Println(add(1), sub(2)) //11=10+1,9=11-2
    	fmt.Println(add(3), sub(4)) //12=9+3,8=12-4
    	fmt.Println(add(5), sub(6)) //13=8+5,7=13-6
    }
    
  • 错误处理

    go语言中没有异常机制,但是使用panic/recover模式来处理错误,panic可以在任何地方引发,但recover只有在defer调用的函数中有效。

    recover()必须要搭配defer使用,而且由于执行panic后就进入崩溃退出了,所以panic后的语句不会执行,因此defer要再panic之前定义
    执行panic就相当于执行返回值为panic包含的值的return语句,所以首先会复制函数里的值,然后再执行RET指令,因此defer就是执行在退出之前,然后再输出panic里的值。

    package main
    
    import "fmt"
    
    func funcA() {
    	fmt.Println("func A")
    }
    
    func funcB() {
    	//defer定义的函数语句会在执行panic之前执行
    	defer func() {
    		fmt.Println("开始恢复")
    		//recover() 执行recover后就相当于取消了panic,不回退出也不会打印出panic定义的错误
    		err := recover() //err就是panic函数里所包含的信息,使用recover可以继续执行defer里的函数,然后再继续执行,但panic后的语句已经被跳过不会再执行
    		fmt.Println(err) //打印出panic中定义的错误
    		//如果程序出出现了panic错误,可以通过recover恢复过来,继续执行
    		if err != nil {
    			fmt.Println("recover in B")
    		}
    	}()
    	panic("panic in B") //程序崩溃退出,并打印出panic中定义的错误,如果有defer则会在退出前先执行defer里的内容
    	// fmt.Println("func B") //由于执行panic直接进入return环节,所以不可能执行该语句
    }
    
    func funcC() {
    	fmt.Println("func C")
    }
    func main() {
    	funcA()
    	funcB()
    	funcC()
    }
    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值