# Golang 函数

对于熟悉 Java 或者其他编程语言的同学来说,对于函数和方法可能有些误解,在 Go 语言中,函数和方法不太一样,有明确的区分。但对于 Java 而已,一般可以认为函数就是方法、方法就是函数。在 Go 语言中,函数是指不属于任何结构体、类型的方法,也就是说,函数是没有接受者的,而方法是有接受者的,我们说的方法要么属于一个结构体,要么属于一个新定义的类型。

//函数
func hello(c *Client) {
  
}

//方法
func (c *Client) hello() {
  
}

# 一、Golang 函数声明

函数语法格式:

func funcName(parametername type1, parametername type2) (output1 type1, output2 type2) {
    //这里是处理逻辑代码
    //返回多个值
    return value1, value2
}
  • func:函数由 func 开始声明
  • funcName:函数名称,函数名和参数列表一起构成了函数签名
    • 小写:同一个包内可调用,不同包不可调用
    • 大写:不同包内可调用(通过 import 包)
  • parametername type:参数列表
  • output type:返回值,Go 语言支持一个函数返回多个值

注意:Go 程序中至少有一个 main 函数。

# 二、Golang 函数参数

形式参数:定义函数时,用于接收外部传入的数据,叫做形式参数,简称形参。

实际参数:调用函数时,传给形参的实际的数据,叫做实际参数,简称实参。

函数调用:

  • 函数名称必须匹配
  • 实参与形参必须一一对应:顺序,个数,类型

# 1. 一般参数

// 两个参数,一个 param1,一个 param2
func funcName(param1 type1, param2, type2) {
  	
}

// 如果 param1 和 param2 类型相同,可以这么写
func funcName(param1, param2 type) {
  
}

# 2. 可变参数

  • 可变参数放在最后面
// 使用 ... (三个点)就可以实现可变参数
func funcName(arg...type){
  	
}

# 三、Golang 函数参数传递

# 1. 值传递

值传递实际上就是一份拷贝,函数内部对该值的修改,不会影响到函数外部的值,因为它们已经不是同一个东西了。

package main

import (
   "fmt"
)

func main(){
  x := 16
  fmt.Println("修改前 x=", x)  //16
  //调用函数
  changeX(x)
  fmt.Println("修改后 x=", x)  //16,没有改变

}

func changeX(x int) {
  x = 100
  fmt.Println("修改时 x=", x) //100
}

# 2. 引用传递

引用传递本质上也是值传递,只不过这份值是一个指针(地址)。 所以我们在函数内对这份值的修改,其实不是改这个值,而是去修改这个值所指向的数据,所以是会影响到函数外部的值的。

package main

import (
	"fmt"
)

func main(){
	x := 16
	fmt.Println("修改前 x=", x)  //16
	//调用函数
	changeX(&x)  //传地址
	fmt.Println("修改后 x=", x)  //100,改变了
}

func changeX(x *int) {
	*x = 100
	fmt.Println("修改时 x=", *x) //100
}

传指针使得多个函数能操作同一个对象。

传指针比较轻量级(8bytes),只是传内存地址,我们可以用指针传递体积大的结构体。如果用参数值传递的话, 在每次 copy 上面就会花费相对较多的系统开销(内存和时间)。所以当你要传递大的结构体的时候,用指针是一个明智的选择。

Go语言中 slice,map 和 channel 这三种类型的实现机制类似指针, 所以可以直接传递,而不用取地址后传递指针。

注:若函数需改变 slice 的长度,则仍需要取地址传递指针,因为 slice 扩容后会重新申请一个 array 来作为底层数组,这个时候就改变指向了)

# 四、Golang 函数返回值

# 1. 一个函数可以有多个返回值

如上文所说,Golang 是支持一个函数返回多个值的,这样的一个好处是我们在需要返回多个值的时候不需要像其他语言一样去封装一个结构体或者是类,比较方便。

// 返回值可以不起名字
func swap(x, y string) (string, string) {
   return y, x
}

// 返回值也可以起名字
func handle(x, y string)(res string, err error){
  	//...
 		return   					//这里可以不用写 return 什么
   //return res, err  //当然也可以写
}

# 2. 接收函数的多个返回值

// 接收多个值
res1, res2 := invoke(param1, param2)

// 如果有的值不想要,则可以用空白符 _ 来占位 
res1, _ := invoke(param1, param2)

# 五、Golang 延迟函数

# 1. 延迟是什么

即延迟(defer)语句,延迟语句被用于执行一个函数调用,在这个函数之前,延迟语句返回。

# 2. 延迟函数

你可以在函数中添加多个 defer 语句。当函数执行到最后时,这些 defer 语句会按照逆序执行,最后该函数返回。特别是当你在进行一些打开资源的操作时,遇到错误需要提前返回,在返回前你需要关闭相应的资源,不然很容易造成资源泄露等问题。

  • 如果有很多调用 defer,那么 defer 是采用后进先出模式 —— 栈
  • 在离开所在的方法时,执行(报错的时候也会执行)
func ReadWrite() bool {
    file.Open("file")
    //	打开资源后立刻 defer 是一个好习惯,它会保证无论程序是否正常结束都能关闭资源
    defer file.Close()
    if failureX {
          return false
    } i
    f failureY {
          return false
    } 
    return true
}

# 3. 延迟方法

延迟并不仅仅局限于函数。延迟一个方法调用也是完全合法的。

package main

import (  
    "fmt"
)

type person struct {  
    firstName string
    lastName string
}

func (p person) fullName() {  
    fmt.Printf("%s %s",p.firstName,p.lastName)
}

func main() {  
    p := person {
        firstName: "John",
        lastName: "Smith",
    }
    defer p.fullName()
    fmt.Printf("Welcome ")  
}

输出:

Welcome John Smith 

# 4. 延时参数

延迟函数的参数在执行延迟语句时被执行,而不是在执行实际的函数调用时执行。

package main

import (  
    "fmt"
)

func printA(a int) {  
    fmt.Println("value of a in deferred function", a)
}
func main() {  
    a := 5
    defer printA(a)
    a = 10
    fmt.Println("value of a before deferred function call", a)
}

输出:

value of a before deferred function call 10  
value of a in deferred function 5 

可以参考,其实 a 已经作为 5 被传递到 defer 中,然后压入栈中了,所以只需 printA 的时候 a 还是 5,而不是后面修改成的 10。

# 5. 逆序输出

前面说了,defer 的执行顺序类似于栈,先进后出。

package main

import (  
    "fmt"
)

func main() {  
    name := "Naveen"
    fmt.Printf("Orignal String: %s\n", string(name))
    fmt.Printf("Reversed String: ")
    for _, v := range []rune(name) {
        defer fmt.Printf("%c", v)
    }
}

输出:

Orignal String: Naveen  
Reversed String: neevaN 

# 6. defer 注意点

  1. 当外围函数中的语句正常执行完毕时,只有其中所有的延迟函数都执行完毕,外围函数才会真正的结束执行;
  2. 当执行外围函数中的 return 语句时,只有其中所有的延迟函数都执行完毕后,外围函数才会真正返回;
  3. 当外围函数中的代码引发运行恐慌(panic)时,只有其中所有的延迟函数都执行完毕后,该运行时恐慌才会真正被扩展至调用函数。

# 7. defer 原理

参考:https://draveness.me/golang/docs/part2-foundation/ch05-keyword/golang-defer/

待补充....

# 六、Golang 匿名函数

匿名函数:没有名字的函数。

定义一个匿名函数,直接进行调用。通常只能使用一次。也可以使用匿名函数赋值给某个函数变量,那么就可以调用多次了。

Go 语言是支持函数式编程:

  1. 将匿名函数作为另一个函数的参数:回调函数
  2. 将匿名函数作为另一个函数的返回值:闭包

# 1. 匿名函数

package main

import "fmt"


// 有名函数
func fun1(){
    fmt.Println("我是fun1()函数。。")
}

func main() {

     fun1()
     fun1()
     fun2 := fun1
     fun2()

     //匿名函数
     func (){
        fmt.Println("我是一个匿名函数。。")
     }()
		
     //	用一个变量来接收一个匿名函数,就可以再它的作用域内多次调用该函数
     fun3:=func(){
        fmt.Println("我也是一个匿名函数。。")
     }
     fun3()
     fun3()

     //	定义带参数的匿名函数
     func (a,b int){
        fmt.Println(a,b)
     }(1,2)

     //	定义带返回值的匿名函数
     res1 := func (a, b int)int{
        return a + b
     }(10,20) //匿名函数调用了,将执行结果给res1
     fmt.Println(res1)
		
  
     res2 := func (a,b int)int{
        return a + b
     } // 将匿名函数的值,赋值给 res2,这个时候 res1 是一个函数
     fmt.Println(res2)

     fmt.Println(res2(100,200))
}

# 2. 回调函数

回调函数:callback,就是将一个函数 func2 作为函数 func1 的一个参数。那么 func2 叫做回调函数,func1 叫做高阶函数。

package main

import "fmt"

func main() {
  
    // 设计一个函数,用于求两个整数的加减乘除运算
    fmt.Printf("%T\n", add)  //func(int, int) int
    fmt.Printf("%T\n", oper) //func(int, int, func(int, int) int) int
		
    // 普通调用 add
    res1 := add(1, 2)
    fmt.Println(res1)
		
  	// 将 add 作为回调函数传入 oper
    res2 := oper(10, 20, add)
    fmt.Println(res2)
		
  	// 将 sub 作为回调函数传入 oper
    res3 := oper(5,2,sub)
    fmt.Println(res3)
		
  	// 乘法运算
    func1 := func(a,b int)int{
        return a * b
    }
		
		// 将 func1 作为回调函数传入 oper
    res4:=oper(10,4,func1)
    fmt.Println(res4)
		
  	// 也可以直接在传参的时候声明匿名函数
    res5 := oper(100,8,func(a,b int)int{
        if b == 0{
            fmt.Println("除数不能为零")
            return 0
        }
        return a / b
    })
    fmt.Println(res5)

}

// 根据传入的回调函数进行算术运算
func oper(a, b int, fun func(int, int) int) int {
    fmt.Println(a, b, fun) //打印3个参数
    res := fun(a, b)
    return res
}

//	加法运算
func add(a, b int) int {
    return a + b
}

//	减法运算
func sub(a, b int) int {
    return a - b
}

# 3. 闭包

闭包(closure):一个外层函数中,有内层函数,该内层函数中,会操作外层函数的局部变量(外层函数中的参数,或者外层函数中直接定义的变量),并且该外层函数的返回值就是这个内层函数。那么这个内层函数和外层函数的局部变量,统称为闭包结构。

  • 局部变量的生命周期会发生改变,正常的局部变量随着函数调用而创建,随着函数的结束而销毁。
  • 但是闭包结构中的外层函数的局部变量并 不会 随着外层函数的结束而销毁,因为内层函数还要继续使用。
package main

import "fmt"

func main() {

     res1 := increment() 		 // res1 = fun
     fmt.Printf("%T\n",res1) // func() int
     
  	 //	带括号表示执行 res1,得到返回结果 v1
     v1 := res1()
     fmt.Println(v1) // 1
     //	再次执行 res1,得到返回结果 v2
     v2 := res1()
     fmt.Println(v2) // 2
     fmt.Println(res1()) // 3
     fmt.Println(res1()) // 4
     fmt.Println(res1()) // 5
     fmt.Println(res1()) // 6
		
     // 用一个新的变量来接收 increment() 的返回结果
     // 这个时候 increment 函数又重新执行了一遍
     res2 := increment()
     fmt.Printf("%T\n",res2) // func() int
  	 // 执行 res2
     v3 := res2()
     fmt.Println(v3) // 1
     fmt.Println(res2()) // 2
		 
     // res1 和 res2 没什么关系
     fmt.Println(res1()) // 7
}


func increment()func() int {   //外层函数
    //1. 定义了一个局部变量
    i := 0
    //2. 定义了一个匿名函数,给变量自增并返回
    fun := func() int { //内层函数
        i++
        return i
    }
    //3. 返回该匿名函数
    return fun
}
上次更新: 7/27/2021, 6:41:10 PM