Go学习笔记(10)Go函数

x33g5p2x  于2022-03-06 转载在 其他  
字(3.3k)|赞(0)|评价(0)|浏览(518)

Go函数

函数是基本的代码块,它的作用是可以将实现某个功能的多行代码封装到一段代码块中(即函数),在需要的时候去调用。同个函数可以被多次调用,实现代码重用。
     函数一般会有参数和返回值(也可以没有),函数名称、函数参数、函数返回值以及它们的类型被统称为函数签名
     在Go语言中,函数有以下一些特点:

  • 支持不定长变参、多返回值、命名返回值参数
  • 支持匿名函数、闭包
  • 函数可以作为一种类型使用
  • Go函数不支持嵌套、重载

Go函数定义

Go函数定义格式如下:

func function_name ([parameter list]) [return_type]{
	函数体
}
  • 由关键字func开始声明定义函数
  • parameter list:参数列表,一般格式为 param1 type1, param2 type2...
  • return_type:返回值类型,这里可以只写返回值类型,也可以为返回值命名(即支持命名返回值参数的写法:ret1 type1

函数值传递与引用传递

Go函数被调用的时候,传入的参数会被复制然后传递到函数内部使用,即函数体中使用的是参数副本。这种方式也称之为值传递

  • 值传递:即传递参数副本。函数接收参数副本之后,在使用变量的过程中可能对副本的值进行更改,但不会影响到原来的变量
  • 引用传递:如果希望在函数内对原始的参数进行直接修改,需要将参数的地址(变量名前加&符号)作为输入参数传递给函数(即引用传递)。此时传递的是地址的副本,但地址副本指向位置还是原来变量的位置,因此可以通过该指针来修改原来的变量。如果传递的是slicemap这类引用类型,它们默认都是采用引用传递

Go函数的各种使用形式

  • 函数返回多个值,在声明中在输入参数后面指定返回值的类型(用括号括起来),函数结束时return多个返回值
func main(){
	r1, r2 := A(1, "A")
}

func A(a int, b string) (int, string) {
	c := a + 1
	d := b + "aa"
	return c, d
}
  • 多个参数如果类型相同,可以合并写法,单个返回值可以不写括号
func main(){
	r3 := B(1, 2, 3)
}
func B(a, b, c int) int {
	return a + b + c
}
  • 命名返回值写法,使用这种形式,返回值的名称已经在声明中定义,不需要在函数体内定义,return后面也可以不写上返回值的名称,默认会返回函数声明中那几个命名的返回值
func main(){
	r4, r5, r6 := C()
}
func C() (a, b, c int) {
	a, b, c = 1, 2, 3
	return
}
  • 不定长变参,通过传入...type这样的形式来表示不定长变参,注意不定长变参只能作为最后一个参数。函数会接收一个某个指定类型的类似slice的参数
func main(){
	r7 ;= D(0,1,2,3)
}
func D(base int , s ...int) int {
	for _, v := range s {
		base += v
	}
}

这里小朋友是否有个问号:这种方式和你直接把参数放到一个slice中,再直接传递这个slice的方式有何区别?区别在于不定长变参是对原来的参数进行拷贝再放到slice中,因此函数内改变它们并不会改变原来的值,而直接传递slice引用传递,传递的是slice的指针,函数内改变会改变原来的值

  • 传递指针参数(引用传递)
func main(){
	p := 1
	G(&p)
	fmt.Println(p)
}
func G(p *int) {
	*p = 2
	fmt.Println(*p)
}
  • 传递引用类型参数(引用传递)
func main(){
	s := []int{1, 2, 3, 4}
	F(s)
	fmt.Println(s)
}
func F(s []int) {
	s[0] = 5
	s[1] = 6
	s[2] = 7
	s[3] = 8
	fmt.Println(s)
}
  • 函数作为一种类型来使用
func main(){
	 h := H
	 h()
}
func H() {
	fmt.Println("H")
}
  • 将函数调用的结果作为其它该函数的参数。只要只要这个被调用函数的返回值个数、返回值类型和返回值的顺序与调用函数所需求的实参是一致的
func main(){
	r := f1(f2(5))
}
func f1(a,b,c int) int {
	return a+b+c
}
func f2(a int) (b,c,d int){
	b = a
	c = a + 1
	d = a - 1
	return
}
  • 将函数作为一种参数类型,即函数可以作为其它函数的参数进行传递,然后在其它函数内调用执行,一般称之为回调。注意和上一点进行区分
type cb func(int) int

func main() {
    testCallBack(1, callBack)
    testCallBack(2, func(x int) int {
        fmt.Printf("我是回调,x:%d\n", x)
        return x
    })
}

func testCallBack(x int, f cb) {
    f(x)
}

func callBack(x int) int {
    fmt.Printf("我是回调,x:%d\n", x)
    return x
}

匿名函数

匿名函数,顾名思义即没有名字的函数。匿名函数不能独立地定义,它需要赋值给某个变量,即保存函数的地址到变量中,然后通过变量对函数进行调用。或者定义的同时直接调用

func main(){
	i := func(){
		fmt.Println("匿名函数赋值给变量再调用")
	}
	i()
	func(){
		fmt.Println("匿名函数直接调用")
	}()
}

Go的匿名函数支持闭包。关于闭包的概念:

闭包函数:声明在一个函数中的函数,叫做闭包函数
闭包:闭包函数总是可以访问其所在的外部函数中声明的参数和变量,即使在其外部函数被返回之后

有关闭包更加深入的理解后面会考虑再写一篇文章来介绍。下面是一段展示闭包的代码,closure函数中的匿名函数(闭包函数)可以访问到外部函数中的变量x。程序输出的结果为1112

func main(){
	 f := closure(10)
	 fmt.Println(f(1))
	 fmt.Println(f(2))
}
func closure(x int) func(int) int {
	return func(y int) int {
		return x + y
	}
}

defer 函数

defer函数的作用类似于其它语言中的析构函数,它会在函数体执行结束后再执行,如果有多个defer函数,它们按照调用顺序的相反顺序逐个执行

  • 即使函数发送错误也会执行,不过在代码中需要在发送错误之前先调用
  • defer函数允许将某些语句推迟到函数返回之前才执行
  • defer函数一般常用于释放某些已分配的资源,文件关闭,解锁,计时等操作
        defer函数的使用:直接在函数前加上defer关键字即可
func main(){
	for i := 0; i < 3; i++ {
	 	defer fmt.Println(i)          //将逆序输出  2  1  0
	 }
}

defer经常可以用在异常恢复的代码块中,在Go中没有Java的try-catch机制,它是使用panic-recover模式来处理程序中发生的错误

  • panic可以在任意地方引发,但recover只有在defer调用的函数中才有效
  • 包含recoverdefer函数需要定义在发生panic之前
func main(){
	beforePanic()
	panicRecover()
	afterPanic()
}
func beforePanic() {
	fmt.Println("before panic")
}
//panic-recover
func panicRecover() {
	defer func() {
		if err := recover(); err != nil {
			fmt.Println("Recover in this")
		}
	}()
	panic("Panic !!!")
}
func afterPanic() {
	fmt.Println("after panic")
}

相关文章