go语言之函数

835 阅读5分钟

函数由关键字 func、函数名、参数列表、返回值、函数体和返回语句组成,每一个程序都包含很多的函数,函数是基本的代码块。另外有一点需要注意的是在 Go 里面函数重载是不被允许的。

func 函数名(形式参数列表)(返回值列表){
    函数体
}
  • 函数的参数及返回值

    • 正常的参数列表
      func add(x int, y int) int {
      	x = x - 10
      	y = y - 10
      	return x + y
      }
      
    • 参数类型省略
      // x和y都是int类型
      func add(x, y int) int {
      	x = x - 10
      	y = y - 10
      	return x + y
      }
      
    • 可变参数
      func add(x int, y ...int) int {
      	x = x - 10
      	fmt.Println("%T\n",y) // y的类型是int类型的切片 []int
      	return x
      }
      
    • 省略参数名

      函数定义时,它的形参一般是有名字的,不过我们也可以定义没有形参名的函数,只有相应的形参类型,这种方法可以在接口中使用,在继承接口实现这个方法的时候就参数名可以随便命名

       func f(int, int, float64) 
      

      注意:在函数中,实参通过值传递的方式进行传递,因此函数的形参是实参的拷贝,对形参进行修改不会影响实参,但是,如果实参包括引用类型,如指针、slice(切片)、map、function、channel 等类型,实参可能会由于函数的引用传递被修改。如果函数的返回值太多,那么此时可以通过传递地址的方式将实参的地址传递到函数中,那么此时函数修改的就是真实参数的值了,就不需要再进行返回值

    • go语言支持多个返回值返回
      func test(x, y int) (int, int) {
      	return x, y
      }
      
    • 带有变量名的返回值
      func test1(x, y int) (c int, d int) {
      	c = x
      	d = y
      	return  // 如果返回值带有变量名那么return之后可以不指定返回的参数,默认返回同名的参数
      }
      

      注意:同一种类型返回值和命名返回值两种形式只能二选一,混用时将会发生编译错误

  • 函数的类型

    Go语言里面拥有三种类型的函数:

    1. 普通的带有名字的函数

    2. 匿名函数或者 lambda 函数

    3. 方法

    第一个普通的带有名字的函数上边已经讲了,下边我们说一下后边两种:

    • 匿名函数或者lambda函数

      匿名函数的定义如下

      func(参数列表)(返回参数列表){
          函数体
      }
      
      • 匿名函数的调用

        匿名函数可以在声明后调用,例如:

        func(data int) {
            fmt.Println("hello", data)
        }(100)
        

        在最后的小括号就是对于这个函数的调用,参数为100

      • 赋值再调用
        // 将匿名函数体保存到f()中
        f := func(data int) {
            fmt.Println("hello", data)
        }
        
        // 使用f()调用
        f(100)
        
      • 匿名函数作为回调函数使用
        func test2(f func(int) int) int  {
        	i := f(100)
        	return i
        }
        
        func main() {
        	i := test2(func(i int) int {
        		return i
        	})
        	fmt.Println(i)
        }
        

        如果你使用过java的lambda表达式你可以想到这就是lambda表达式的形式,只不过lambda表达式更加的简略,直接传递参数和方法体,并不是一个完整的函数定义。

    • 方法

      方法也是一种函数,但是这是一种特殊的函数。对于面向对象,go中的方法就像java中的成员函数一样,他是用来描述对象的一类行为。但是在go中并没有class这一说法,所以go就是用了另类的函数(方法)用来描述一个某个类型的行为。他就是接受者类型。这个唯一的接受者被称为方法声明的接受者参数。接受者参数必须被()括住,并且声明在func和函数名称之间。

      比如我们定义一个Person结构体

      type Person struct {
      	Age int
      	Name string
      }
      // 方法
      func (p *Person) GetName() string  {
      	return p.Name
      }
      

      其中GetName就是方法(在go中开头大写表示共有,小写表示私有),他只属于Person这个结构体,其他的类型不能调用。所以我们可以明确的给类型T和*T声明一个方法。

      但是要注意的是如果要定义一个方法有以下几个需要注意的地方:

      1. T必须是定义的类型

      2. T的定义必须跟方法定义的在同一个包中。

      3. T不能是指针类型

      4. T不能是interface类型。

      • 指针接收器和非指针接收器

        上面我们使用了 *Person作为接收器,其实我们还可以使用Person作为接收器,二者的效果是一样的。

        type Person struct {
        	Age int
        	Name string
        }
        // 方法
        func (p Person) GetName() string  {
        	return p.Name
        }
        
  • defer

    关键字 defer 的用法类似于Java的 finally 语句块,它一般用于释放某些已分配的资源,典型的例子就是对互斥解锁,或者关闭一个文件。

    • defer的执行顺序

      当有多个 defer 行为被注册时,它们会以逆序执行(即后进先出),有如下程序

      func main() {
      	defer  test3()
      	defer  test4()
      	defer  test5()
      	defer  test6()
      }
      
      func test3()  {
      	fmt.Println("我是test3")
      }
      
      func test4()  {
      	fmt.Println("我是test4")
      }
      
      func test5()  {
      	fmt.Println("我是test5")
      }
      
      func test6()  {
      	fmt.Println("我是test6")
      }
      

      我们运行后打印出来的结果为

      我是test6
      我是test5
      我是test4
      我是test3
      
    • defer和返回值

      有如下两个方法

      func test7() int {
          var result int
          defer func() {
              result++
          }()
          return result
      }
      
      func test8() (result int) {
          defer func() {
              result++
          }()
          return result
      }
      

      我们在接受这两个方法的返回值并打印后发现test7返回的是0,而test8返回的是1,那这两个看似一样的方法为什么返回值却不一样呢?

      如果要解决这个问题必须要了解defer的作用原理:

      1. 将result赋值给返回值(可以理解成Go自动创建了一个返回值变量retVlaue,相当于执行retValue = result)
      2. 然后检查是否有defer,如果有则执行
      3. 返回刚才创建的返回值(retValue)

      所以在使用匿名返回值的时候,go返回的真正变量并不是我们return的result,所以当defer修改result的值时并不会影响返回值。但是如果我们指定了返回值的名称,那么go就只能返回这个参数,所以此时defer就可以影响返回值的内容了