Go语言入门8:函数

Keywords: #技术 #Golang #Go 入门笔记
Table of Contents

Go 语言入门基础学习笔记之 Go 语言的函数

golang

函数

函数定义

func function_name( [parameter list] ) [return_types] {
   函数体
}
  • func:函数由 func 开始声明
  • function_name:函数名称,参数列表和返回值类型构成了函数签名。
  • parameter list:参数列表,参数就像一个占位符,当函数被调用时,你可以将值传递给参数,这个值被称为实际参数。参数列表指定的是参数类型、顺序、及参数个数。参数是可选的,也就是说函数也可以不包含参数。
  • return_types:返回类型,函数返回一列值。return_types 是该列值的数据类型。有些功能不需要返回值,这种情况下 return_types 不是必须的。
  • 函数体:函数定义的代码集合。
/* 函数返回两个数的最大值 */
func max(num1, num2 int) int {
   /* 声明局部变量 */
   var result int

   if (num1 > num2) {
      result = num1
   } else {
      result = num2
   }
   return result 
}

Go 语言还支持命名返回值,比如:

func Factorial(n uint64)(result uint64) {
    if (n > 0) {
        result = n * Factorial(n-1) // 直接对命名返回值 result 赋值
        return // 这里直接返回 result
    }
    return 1 // 这里返回 1,result 会被自动赋值为 1
}

这样写的好处在于:

  • 在函数体中不需要再次声明定义这个变量。
  • 允许在函数中使用 result 直接作为返回值。
  • 使得代码更简洁,容易阅读,并且可以避免在每个返回语句中重复指定返回值。
  • 如果没有提供名称,返回值则需要在 return 语句中显式指定,例如 return someValue

返回多个值

Go 函数可以返回多个值,并且也支持匿名和有名。

// 匿名的返回值
func swap(x, y string) (string, string) {
   return y, x
}

// 有名的返回值
func swap1(x, y string) (r1 string, r2 string){
    r1 = y
    r2 = x
    return 
}

// 相同类型的类型名可以合并
func swap2(x, y string) (r1, r2 string){
    r1 = y  // 如果 r1 r2不赋值直接返回,则为默认值(空)
    r2 = x
    return 
}

函数参数

调用函数,可以通过两种方式进行参数传递

  1. 值传递:指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。
  2. 引用传递:指在调用函数时将实际参数的地址传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数。

默认情况下,Go 语言使用的是值传递,即在调用过程中不会影响到实际参数。

Go 语言中所有的等号赋值函数传参都是进行拷贝的值传递。

值传递:

package main

import "fmt"

func main() {
   /* 定义局部变量 */
   var a int = 100
   var b int = 200

   fmt.Printf("交换前 a 的值为 : %d\n", a )
   fmt.Printf("交换前 b 的值为 : %d\n", b )

   /* 通过调用函数来交换值 */
   swap(a, b)

   fmt.Printf("交换后 a 的值 : %d\n", a )
   fmt.Printf("交换后 b 的值 : %d\n", b )
}

/* 定义相互交换值的函数 */
func swap(x, y int) int {
   var temp int

   temp = x /* 保存 x 的值 */
   x = y    /* 将 y 值赋给 x */
   y = temp /* 将 temp 值赋给 y*/

   return temp;
}

// 输出结果
交换前 a 的值为 : 100
交换前 b 的值为 : 200
交换后 a 的值 : 100
交换后 b 的值 : 200

引用传递:

package main

import "fmt"

func main() {
   /* 定义局部变量 */
   var a int = 100
   var b int= 200

   fmt.Printf("交换前,a 的值 : %d\n", a )
   fmt.Printf("交换前,b 的值 : %d\n", b )

   /* 调用 swap() 函数
   * &a 指向 a 指针,a 变量的地址
   * &b 指向 b 指针,b 变量的地址
   */
   swap(&a, &b)

   fmt.Printf("交换后,a 的值 : %d\n", a )
   fmt.Printf("交换后,b 的值 : %d\n", b )
}

func swap(x *int, y *int) {
   var temp int
   temp = *x    /* 保存 x 地址上的值 */
   *x = *y      /* 将 y 值赋给 x */
   *y = temp    /* 将 temp 值赋给 y */
}

// 输出结果
交换前a 的值 : 100
交换前b 的值 : 200
交换后a 的值 : 200
交换后b 的值 : 100

函数用法

  1. 函数作为另外一个函数的实参:函数定义后可作为另外一个函数的实参数传入
  2. 闭包:闭包是匿名函数,可在动态编程中使用
  3. 方法:方法就是一个包含了接受者的函数

Go 语言可以很灵活的创建函数,并作为另外一个函数的实参。以下实例中我们在定义的函数中初始化一个变量,该函数仅仅是为了使用内置函数 math.sqrt()

package main

import (
   "fmt"
   "math"
)

func main(){
   /* 声明函数变量 */
   getSquareRoot := func(x float64) float64 {
   // 这是一个匿名函数
      return math.Sqrt(x)
   }

   /* 使用函数 */
   fmt.Println(getSquareRoot(9))

}

// 输出结果
3

Go 语言支持匿名函数,可作为闭包。匿名函数是一个内联语句或表达式。匿名函数的优越性在于可以直接使用函数内的变量,不必申明。

匿名函数是一种没有函数名的函数,通常用于在函数内部定义函数,或者作为函数参数进行传递。

匿名函数可以在声明时直接使用,如下面这个例子。也可以当成值赋值给一个变量,这个变量就相当于是这个匿名函数的一个函数名。

package main
import "fmt"

func main() {
  a := 1
  b := 2
  c := func(a int, b int) int {
    return a + b
  }(a, b)
  fmt.Println(c)
}

以下实例中,我们创建了函数 getSequence() ,返回另外一个函数。该函数的目的是在闭包中递增 i 变量,代码如下:

package main

import "fmt"

func getSequence() func() int {
// func()表示getSequence()返回一个函数类型的值
// int表示func()返回一个int类型的值
   i := 0
   return func() int {
      i+=1
     return i  
   }
}

func main(){
   /* nextNumber 为一个函数,函数 i 为 0 */
   nextNumber := getSequence()  

   /* 调用 nextNumber 函数,i 变量自增 1 并返回 */
   fmt.Println(nextNumber())
   fmt.Println(nextNumber())
   fmt.Println(nextNumber())
   
   /* 创建新的函数 nextNumber1,并查看结果 */
   nextNumber1 := getSequence()  
   fmt.Println(nextNumber1())
   fmt.Println(nextNumber1())
}

// 输出结果
1
2
3
1
2
package main

import "fmt"

func main() {
    // 定义一个匿名函数并将其赋值给函数变量add
    add := func(a, b int) int {
        return a + b
    }

    // 调用匿名函数
    result := add(3, 5)
    fmt.Println("3 + 5 =", result)

    // 定义一个匿名函数并将其赋值给函数变量multiply
    multiply := func(x, y int) int {
        return x * y
    }

    product := multiply(4, 6)
    fmt.Println("4 * 6 =", product)

    // 将匿名函数作为参数传递给其他函数
    calculate := func(operation func(int, int) int, x, y int) int {
        return operation(x, y)
    }

    sum := calculate(add, 2, 8)
    fmt.Println("2 + 8 =", sum)

    // 也可以直接在函数调用中定义匿名函数
    difference := calculate(func(a, b int) int {
        return a - b
    }, 10, 4)
    fmt.Println("10 - 4 =", difference)
}

// 输出结果
3 + 5 = 8
4 * 6 = 24
2 + 8 = 10
10 - 4 = 6

Go 语言中还有一个特殊的函数用法,deferdefer 后面跟的代码或者匿名函数,在当前层函数退出前return 后)才会被执行。

package main
import "fmt"

func work() {
    fmt.Println("Step 1")
    defer func(){  // ()用来放置形参
        fmt.Println("Step 3")
    }() // ()用来传递实参
    fmt.Println("Step 2")
}

在上面一个例子中,在匿名函数前使用的了 defer,使得这个匿名函数在 work 函数最后才会被执行。

在同一个函数中可以有多个 defer 关键字,由于 defer 使用的结构,因此它们执行的时的顺序恰好与书写顺序相反

image.png

init 函数与 import 导包

Go 语言给每个包提供了一个 init() 函数,作为当前包的一个主入口。

image.png

在上面张图中,展示了 Go 语言程序有关包、init()main() 的调用流程。

main 程序作为项目的主入口,一开始会调用一系列的包,不同的包中也会不断的调用其他包,整体是以递归的方式进行解析。包的解析流程是从 constvarinti(),主程序的解析流程也是类似,从 constvarinit()main(),由于 init 的执行时机优先于 main,因此我们可以将需要在 main 函数中执行的操作写在 init 函数中。

实例:

│ main.go
├─lib1
│   lib1.go
└─lib2
    lib2.go

以上是实例的目录情况,main.go 是入口程序,lib1lib2 是要导入的包。

package main

import (
    "GolangStudy/5-init/lib1"
    "GolangStudy/5-init/lib2"
)

func main() {
    lib1.Lib1Test()
    lib2.Lib2Test()
}
// 输出结果
lib1. init() ...
lib2. init() ...
lib1Test()...
lib2Test()...

这里需要指定 lib1lib2 相对于 GOROOT 目录的路径位置,否则会报错找不到包。后续会使用 go mod 来更方便的管理包模块,不再需要添加路径。

package lib1

import "fmt"

//当前lib1包提供的API
func Lib1Test() {
    fmt.Println("lib1Test()...")
}

func init() {
    fmt.Println("lib1. init() ...")
}
package lib2

import "fmt"

//当前lib2包提供的API
func Lib2Test() {
    fmt.Println("lib2Test()...")
}

func init() {
    fmt.Println("lib2. init() ...")
}

如果要让其他程序获取当前包中的变量或者函数,变量和函数名的开头必须大写!表示一个公共开放的变量或者函数。否则只能在自己当前的包内使用。


当然在 main 入口程序中导入 lib1lib2 包,也可以设置别名匿名

如果导入了一个包,但是未使用,Go 语言在编译时也会报错。但是存在一种情况:你只需要使用一个包中的 init 初始化函数,但是却不使用它,我们应该怎么做?针对这样的需求,Go 语言提供了 _ 别名用于表示导入匿名包,如:

import (
    _ "GolangStudy/5-init/lib1"  // 导入包执行init,但无法使用包中的方法
}

除此之外,你也可以为导入的包设置一个正常的别名

package main
import (
    mylib2 "GolangStudy/5-init/lib2"
)
func main() {
    mylib2.Lib2Test()
}

也可以通过 . 来设置一个无名的包。尽量少使用,避免有重名的方法,产生错误。

package main
import (
    . "GolangStudy/5-init/lib2"
)
func main() {
   Lib2Test()
}

参考课程:

  1. 8小时转职Golang工程师
  2. Go语言教程 | 菜鸟教程