一、前言

主要以下几方面介绍Go语言中的函数:

  • 函数初体验
  • 具名返回值
  • 接收任意长度参数
  • 递归函数

二、函数初体验

2.1 什么是函数

函数是一段封装了特定功能的代码块,它可以接收输入(参数)、执行操作,并返回输出(返回值)。函数使得代码可以更好地组织、重用和维护。

2.2 函数的作用

使用函数有以下几个主要优势:

  • 模块化与重用: 函数可以将代码分解为小的、可重用的模块。这有助于避免重复编写相同的代码,提高代码的可维护性和可读性。
  • 抽象与封装: 函数将一系列操作封装在一个单元中,隐藏了内部的实现细节。这使得代码更加模块化,更容易理解和维护。
  • 代码组织: 函数帮助你将大块代码分解为更小、更易于管理的部分,从而更好地组织代码结构。
  • 提高可读性: 使用函数来命名不同的操作,使代码更易于理解。函数名可以提供关于函数用途的信息,从而使代码更易于阅读和理解。
  • 测试与调试: 函数使得单元测试更容易,因为你可以针对单个函数进行测试,确保其功能正确。

2.3 函数基本语法

在说明函数基本语法之前,我们需要考虑多个场景,比如:

  • 单个参数单个返回值怎么定义函数
  • 单个参数无返回值怎么定义函数
  • 多个参数(相同类型的)多个返回值(相同类型的)怎么定义函数
  • 多个参数(不同类型的)多个返回值(不同类型的)怎么定义函数
  • 多个参数无返回值怎么定义函数

下面根据上面列举的情况进行函数基本语法的一一说明:

1. 单个参数单个返回值的函数

这种情况下,函数接受一个参数并返回一个值。

func 函数名(参数名1, 参数1名类型) (返回值1, 返回值类型1) {
    // 函数体
    // 执行的代码逻辑
    return 返回值
}

2. 单个参数无返回值的函数

这种情况下,函数接受一个参数但不返回任何值。

func 函数名(参数名1, 参数名1类型)  {
    // 函数体
    // 执行的代码逻辑
}

3. 多个参数(相同类型的)多个返回值(相同类型的)的函数

这种情况下,函数接受多个相同类型的参数,并返回多个相同类型的值。

func 函数名(参数名1, 参数名2 参数类型) (返回值1, 返回值2, 返回值类型) {
    // 函数体
    // 执行的代码逻辑
    return 返回值1, 返回值2
}

4. 多个参数(不同类型的)多个返回值(不同类型的)的函数

这种情况下,函数接受多个不同类型的参数,并返回多个不同类型的值。

func 函数名(参数名1 参数类型1, 参数名2 参数类型2) (返回值1, 返回值类型1, 返回值2, 返回值类型2) {
    // 函数体
    // 执行的代码逻辑
    return 返回值1, 返回值2
}

5. 多个参数无返回值的函数

这种情况下,函数接受多个参数但不返回任何值。

func 函数名(参数名1 参数类型1, 参数名2 参数类型2) {
    // 函数体
    // 执行的代码逻辑
}

2.4 定义一个函数

针对上面函数基本语法进行示例说明:

1. 单个参数单个返回值的函数

定义一个函数,计算输入的整数的平方。将下面代码粘贴到00-InitialExperiencewithFunctions-00.go文件中并保存该文件

package main

import "fmt"

func square(x int) int {
    return x * x
}

func main() {
    result := square(2)
    fmt.Println("执行结果是:", result)
}

针对上面代码分为几个部分进行详细说明:

部分 1 - 导入包

import "fmt"

这部分代码使用 import 关键字导入了标准库中的 "fmt" 包,用于打印输出。

部分 2 - 函数定义

func square(x int) int {
    return x * x
}

这部分代码定义了一个名为 square 的函数,它接受一个整数参数 x,并返回 x * x 的结果,即 x 的平方。

部分 3 - 主函数

func main() {
    result := square(2)
    fmt.Println("执行结果是:", result)
}

这部分代码定义了 main 函数,是程序的入口点。在 main 函数中,我们调用了 square 函数,并将返回值赋给变量 result。然后,使用 fmt.Println 打印出包含结果的消息。

运行上面代码

$ go run .\00-InitialExperiencewithFunctions-00.go
执行结果是: 4

2. 单个参数无返回值的函数

定义一个函数,将下面代码粘贴到00-InitialExperiencewithFunctions-01.go文件中并保存该文件

package main

import "fmt"

func greet(name string) {
    fmt.Println("Hello", name)
}

func main() {
    greet("Go")

}

针对上面代码分为几个部分进行详细说明:

部分 1 - 导入包

import "fmt"

这部分代码使用 import 关键字导入了标准库中的 "fmt" 包,用于打印输出。

部分 2 - 函数定义

func greet(name string) {
    fmt.Println("Hello", name)
}

这部分代码定义了一个名为 greet 的函数。该函数接受一个名为 name 的参数,参数类型为 string(字符串)。在函数体内,它使用 fmt.Println 打印出 "Hello",后跟传递的 name 参数的值。

部分 3 - 主函数

func main() {
    greet("Go")
}

这部分代码定义了程序的主函数 main。在 main 函数中,我们调用了 greet 函数,并传递参数值 "Go"。这将触发 greet 函数的执行,输出 "Hello Go"。

运行上面代码

$ go run .\00-InitialExperiencewithFunctions-01.go
Hello Go

3. 多个参数(相同类型的)多个返回值(相同类型的)的函数

定义一个函数,,用于计算输入的两个整数的和与差。将下面代码粘贴到00-InitialExperiencewithFunctions-02.go文件中并保存该文件

package main

import "fmt"

func addAndSubtract(a, b int) (int, int) {
    sum := a + b
    diff := a - b
    return sum, diff
}

func main() {
    sumResult, diffResult := addAndSubtract(1, 1)
    fmt.Println("相加后的结果:", sumResult)
    fmt.Println("相减后的结果:", diffResult)
}

针对上面代码分为几个部分进行详细说明:

部分 1 - 导入包

import "fmt"

这部分代码使用 import 关键字导入了标准库中的 "fmt" 包,用于打印输出。

部分 2 - 函数定义

func addAndSubtract(a, b int) (int, int) {
    sum := a + b
    diff := a - b
    return sum, diff
}

这部分代码定义了一个名为 addAndSubtract 的函数。该函数接受两个整数参数 ab,并在函数体内计算它们的和与差。然后,它使用 return 语句返回两个值:和 sum,差 diff

部分 3 - 主函数

func main() {
    sumResult, diffResult := addAndSubtract(1, 1)
    fmt.Println("相加后的结果:", sumResult)
    fmt.Println("相减后的结果:", diffResult)
}

这部分代码定义了程序的主函数 main。在 main 函数中,我们调用了 addAndSubtract 函数并传递参数 1 和 1。然后,我们使用 sumResultdiffResult 变量来接收 addAndSubtract 函数的两个返回值。最后,使用 fmt.Println 打印出相加和相减的结果。

运行上面代码

go run .\00-InitialExperiencewithFunctions-02.go
相加后的结果 2
相减后的结果 0

4. 多个参数(不同类型的)多个返回值(不同类型的)的函数

定义一个函数,,用于计算输入的一个整数的乘与一个浮点数的除。将下面代码粘贴到00-InitialExperiencewithFunctions-03.go文件中并保存该文件

package main

import "fmt"

func calculate(a int, b float64) (int, float64) {
    result1 := a * 2
    result2 := b / 2
    return result1, result2
}
func main() {
    multiplicationResult, divisionResult := calculate(2, 0.2)
    fmt.Println("相乘后的结果:", multiplicationResult)
    fmt.Println("相除后的结果:", divisionResult)

}

针对上面代码分为几个部分进行详细说明:

部分 1 - 导入包

import "fmt"

这部分代码使用 import 关键字导入了标准库中的 "fmt" 包,用于打印输出。

部分 2 - 函数定义

func calculate(a int, b float64) (int, float64) {
    result1 := a * 2
    result2 := b / 2
    return result1, result2
}

这部分代码定义了一个名为 calculate 的函数。该函数接受一个整数参数 a 和一个浮点数参数 b。在函数体内,它执行乘法操作,将整数参数乘以 2,并执行除法操作,将浮点数参数除以 2。然后,它使用 return 语句返回两个值:整数结果 result1 和浮点数结果 result2

部分 3 - 主函数

这部分代码定义了程序的主函数 main。在 main 函数中,我们调用了 calculate 函数并传递参数值 2 和 0.2。然后,我们使用 multiplicationResultdivisionResult 变量来接收 calculate 函数的两个返回值。最后,使用 fmt.Println 分别打印出乘法和除法的结果。

运行上面代码

go run .\00-InitialExperiencewithFunctions-03.go
相乘后的结果 4
相除后的结果 0.1

5. 多个参数无返回值的函数

定义一个函数,用于计算输入的两个整数的和与积。将下面代码粘贴到00-InitialExperiencewithFunctions-04.go文件中并保存该文件

package main

import "fmt"

func printSumAndProduct(a, b int) {
    sum := a + b
    product := a * b
    fmt.Println("Sum:", sum)
    fmt.Println("Product:", product)
}

func main() {
    printSumAndProduct(2, 2)
}

针对上面代码分为几个部分进行详细说明:

部分 1 - 导入包

import "fmt"

这部分代码使用 import 关键字导入了标准库中的 "fmt" 包,用于打印输出。

部分 2 - 函数定义

func printSumAndProduct(a, b int) {
    sum := a + b
    product := a * b
    fmt.Println("Sum:", sum)
    fmt.Println("Product:", product)
}

这部分代码定义了一个名为 printSumAndProduct 的函数。该函数接受两个整数参数 ab。在函数体内,它计算两个参数的和与积,并使用 fmt.Println 打印出结果。

部分 3 - 主函数

func main() {
    printSumAndProduct(2, 2)
}

这部分代码定义了程序的主函数 main。在 main 函数中,我们调用了 printSumAndProduct 函数,并传递参数值 2 和 2。这将触发 printSumAndProduct 函数的执行,输出两个整数的和与积。

运行上面代码

$ go run .\00-InitialExperiencewithFunctions-04.go
Sum: 4
Product: 4

三、具名返回值

3.1 什么是具名返回值

在Go语言中,函数可以返回多个值,其中一种方式是使用具名返回值。具名返回值允许你在函数签名中为返回的值指定名称,使得函数内部可以直接对这些值进行赋值操作,然后在函数末尾使用 return 语句来返回这些具名的值。这有助于增加代码的可读性和可维护性。

3.2 具名返回值使用场景

具名返回值在Go语言中有多种使用场景,这些场景通常涉及到提高代码的可读性、可维护性和错误处理。以下是一些常见的具名返回值的使用场景:

  • 提高代码可读性:给返回值取一个有意义的名字可以提高函数的可读性。特别是在函数有多个返回值时,使用具名返回值可以让代码更加清晰和自解释
  • 错误处理:具名返回值常用于函数返回结果和错误信息的场景。它可以明确指定返回值的类型,并在函数内部通过赋值语句来设置返回值和错误信息,使错误处理更加清晰。
  • 多返回值函数:具名返回值特别适用于函数返回多个值的情况。它们可以帮助函数的调用方更容易地理解返回的内容
  • 结构体成员初始化:在创建结构体时,具名返回值可以用于初始化结构体的成员,使代码更加简洁

总之,具名返回值在Go语言中用于提高代码可读性、错误处理、多返回值函数和结构体成员初始化等多种场景。它们有助于编写更加清晰、自解释和可维护的Go代码。

3.3 具名返回值优点

具名返回值的优点包括:

  • 增加可读性:具名返回值可以使函数签名更加清晰,明确地指定了每个返回值的含义,使代码更易理解。
  • 可以在函数内部随时修改:具名返回值可以在函数内部多次赋值,而不需要在 return 语句中重新声明。
  • 减少错误:由于返回值有名称,减少了因返回值顺序错误而导致的 bug。

3.4 具名返回值基本语法

具名返回值基本语法如下:

func functionName(parameters) (returnVar1 type1, returnVar2 type2, ...) {
    // 函数体
    //  returnVar1, returnVar2, ... 赋值
    return // 可以省略返回值列表,Go会自动返回当前的 returnVar 
}

上面参数说明:

  • func functionName(parameters): 这是函数的声明部分,用于定义函数名称和参数列表。functionName 是函数的名称,parameters 是函数的参数列表。
  • (returnVar1 type1, returnVar2 type2, ...): 在括号中声明了一个或多个具名返回值,每个返回值由名称和类型组成。这些名称可以在函数体内用于赋值。
  • 函数体({ ... }): 在花括号内是函数的实际操作,你可以在函数体内进行各种计算和操作,包括为具名返回值赋值。
  • 对 returnVar 赋值:在函数体内,你可以通过 returnVar1 = value1 的方式为具名返回值赋值。这样的赋值操作可以在函数体的任何地方进行,而不一定要在函数末尾。
  • return 语句:在函数末尾,你可以使用 return 语句来结束函数的执行并返回具名返回值。你可以省略返回值列表,Go会自动返回具名返回值的当前值。

3.5 具名返回值示例

下面演示如何使用具名返回值来处理函数的多个返回值,以及如何在函数中处理错误情况。

将下面代码粘贴到01-InitialExperiencewithFunctions-00.go

package main

import "fmt"

func divide(dividend, divisor float64) (result float64, err error) {
    if divisor == 0 {
        err = fmt.Errorf("除数不能为零")
        return // 使用具名返回值,不需要明确指定返回的变量名
    }
    result = dividend / divisor
    return // 可以省略返回值列表,Go会自动返回 result 和 err
}

func main() {
    result, err := divide(10, 0)
    if err != nil {
        fmt.Println("发生错误:", err)
    } else {
        fmt.Println("结果是:", result)
    }

}

针对上面代码分为几个部分进行详细说明:

部分 1 - 导入包

import "fmt"

这部分代码使用 import 关键字导入了标准库中的 "fmt" 包,用于打印输出。

部分 2 - 函数定义

func divide(dividend, divisor float64) (result float64, err error) {
    // 函数体
    if divisor == 0 {
        err = fmt.Errorf("除数不能为零")
        return // 使用具名返回值,不需要明确指定返回的变量名
    }
    result = dividend / divisor
    return // 可以省略返回值列表,Go会自动返回 result  err
}

这部分代码包含了一个名为 divide 的函数的定义,以下是详细的说明:

  • func divide(dividend, divisor float64) (result float64, err error): 这是函数的声明部分,它定义了一个叫做 divide 的函数。函数接受两个参数 dividenddivisor,它们都是 float64 类型的浮点数。函数的返回值列表包括 resulterr,它们都是具名返回值,意味着它们的名称在函数签名中已经指定。
  • 在函数体内,使用了 if 语句来检查 divisor 是否等于0。如果 divisor 等于0,那么程序进入条件分支。在这个分支内部,使用 fmt.Errorf 创建一个格式化的错误信息,将这个错误信息赋值给 err 变量。
  • 接着,使用 return 语句来立即退出函数。这里的特点是不需要明确指定返回的变量名,因为返回值是具名的,Go会自动返回 resulterr 的当前值。在这个特定情况下,result 返回0(因为没有重新赋值),err 返回一个包含错误消息的错误值。
  • 如果 divisor 不等于0,那么程序会继续执行,计算 dividend 除以 divisor 的结果,并将结果赋值给 result 变量。
  • 最后,函数使用 return 语句来结束执行。同样,它可以省略返回值列表,因为Go会自动返回 resulterr 的当前值。

这个函数的目的是执行除法操作,并处理可能的错误情况(例如,除数为0的情况)。如果除法操作成功,它返回商作为 result,如果除法操作失败,它返回一个错误作为 err

部分 3 - main函数定义

func main() {
    result, err := divide(10, 0)
    if err != nil {
        fmt.Println("发生错误:", err)
    } else {
        fmt.Println("结果是:", result)
    }
}

这部分代码包含了 main 函数的定义,这个 main 函数用于演示如何使用 divide 函数,并根据错误情况打印相应的消息:

  • func main(): 这是程序的入口点,所有Go程序都必须包含一个名为 main 的函数。这个函数没有参数。
  • main 函数内部,你调用了 divide(10, 0),即在 divide 函数中传递了两个参数(10和0)。
  • 使用 := 运算符来接收 divide 函数的返回值,其中 result 接收商的值,err 接收错误的值。
  • 接着,使用 if 语句来检查 err 是否为 nil。如果 err 不为 nil,则说明发生了错误,会打印错误信息。
  • 如果 errnil,则说明没有错误发生,会打印除法的结果。

运行上面代码

$ go run .\01-InitialExperiencewithFunctions-00.go
发生错误: 除数不能为零

四、接收不定长度参数

4.1 什么是不定长度参数

不定长度参数(variadic parameters),也称为可变参数,是一种编程语言特性,允许函数接受可变数量的参数。这意味着函数可以被传递零个、一个、多个或者任意数量的参数,而不需要提前确定参数的数量。

在Go语言中,不定长度参数通过省略号 ... 来表示,通常位于函数参数列表的最后一个参数位置。这些参数在函数内部被视为切片(slice),可以使用切片操作来处理它们。

4.2 不定长度参数使用场景

不定长度参数的使用场景包括:

  • 处理可变数量的输入:例如,计算一组数字的总和或平均值,而不需要提前知道有多少个数字。

  • 简化函数接口:不定长度参数可以减少函数的参数数量,使函数更加通用和灵活,同时降低了使用函数的复杂性。

  • 编写可变参数函数:有时可能希望编写可接受不同类型或数量参数的函数,不定长度参数允许你实现这样的函数。

4.3 不定长度参数特点

在Go语言中,不定长度参数(variadic parameters)有以下特点:

  • 接受可变数量的参数:不定长度参数允许函数接受可变数量的参数。这意味着你可以向函数传递零个、一个、多个或任意数量的参数,而不需要在函数声明时指定确切数量的参数。
  • 省略号 ... 表示:在Go语言中,不定长度参数通常使用省略号 ... 来表示,它们必须出现在函数参数列表的最后一个参数位置。这个省略号告诉编译器该参数可以接受多个值,并将它们打包成一个切片(slice)。
  • 参数类型必须一致:不定长度参数的类型必须是一致的,即它们必须具有相同的数据类型。这是因为不定长度参数在函数内部被视为一个切片,需要一致的数据类型来存储这些值。
  • 切片操作:在函数内部,你可以像操作切片一样处理不定长度参数,包括遍历、添加、删除等操作。这使得你可以方便地对传递的参数进行处理。
  • 调用不定长度参数函数:调用带有不定长度参数的函数时,可以传递参数的方式与普通函数调用类似。你可以传递一个切片作为参数,也可以直接传递多个值。如果传递切片,需要使用 ... 语法将切片的元素打散为单独的参数。
  • 函数重载的替代方案:不定长度参数使得函数能够处理不同数量的参数,因此可以替代函数重载的需求。这增加了函数的通用性。
  • 方便的可变参数函数:不定长度参数非常适合编写可变参数的函数,例如,可以使用 fmt.Println 函数来接受不定数量的参数并打印它们。

4.4 不定长度参数基本语法

在Go语言中,不定长度参数(variadic parameters)的基本语法如下:

func functionName(arg1 type1, arg2 type2, args ...typeN) returnType {
    // 函数体
}

其中,关键要点包括:

  • functionName:函数名。
  • arg1, arg2:普通参数,可以有零个或多个。
  • args ...typeN:不定长度参数,使用省略号 ... 表示,必须位于参数列表的最后一个参数位置。typeN 是参数的数据类型。
  • returnType:函数的返回类型。

不定长度参数可以接受任意数量的参数,类型必须与 typeN 一致。在函数内部,不定长度参数被视为一个切片(slice)或数组,你可以像操作切片一样处理它们。

4.5 不定长度参数示例

将下面代码粘贴到02-InitialExperiencewithFunctions-00.go

package main

import (
    "fmt"
    "strings"
)

func name(s ...string) string {
    fmt.Println("接收不定长度的函数: ", s)
    m := strings.Join(s, "-")
    fmt.Println("拼接后的字符串: ", m)
    return m // 返回拼接后的字符串

}
func main() {
    name("a", "b", "c")

}

针对上面代码分为几个部分进行详细说明:

部分 1 - 导入包

import (
    "fmt"
    "strings"
)

这部分代码使用 import 关键字导入了两个包:"fmt" 和 "strings"。这些包提供了各种函数,用于格式化输出和字符串处理。

部分 2 - 函数定义

func name(s ...string) string {
    fmt.Println("接收不定长度的函数: ", s)
    m := strings.Join(s, "-")
    fmt.Println("拼接后的字符串: ", m)
    return m // 返回拼接后的字符串
}

这部分代码定义了一个名为 name 的函数。它是一个接收不定长度的字符串参数的函数。函数内部执行了以下操作:

  • 使用 fmt.Println 打印接收到的字符串参数。
  • 使用 strings.Join 函数将接收到的字符串参数连接起来,并使用连字符 "-" 作为分隔符。
  • 使用 fmt.Println 打印连接后的字符串。
  • 最后,返回连接后的字符串。

部分 3 - 主函数

func main() {
    name("a", "b", "c")
}

这部分代码定义了程序的主函数 main。在 main 函数内部,调用了 name 函数,并传递了三个字符串参数:"a"、"b" 和 "c"。

执行上面代码

$ go run .\02-InitialExperiencewithFunctions-00.go
接收不定长度的函数:  [a b c]
拼接后的字符串:  a-b-c

五、递归函数

5.1 什么是递归函数

在 Go 语言中,递归函数是指在函数体内调用自己的函数。递归函数是一种解决问题的常用方法,特别适用于那些可以被分解成相似子问题的问题,每个子问题都可以通过调用相同的函数来解决。

简单点说就是函数自己调用自己,直到达到条件后才能结束返回结果

5.2 递归函数特点

递归函数具有以下特点:

  • 自调用: 递归函数在其函数体内调用自身,这是递归的核心特点。通过这种方式,函数可以重复执行相同的操作,但针对不同的参数。
  • 基本情况: 递归函数通常包括一个或多个基本情况(也称为基本案例或终止条件)。基本情况是递归的终止点,当满足某个条件时,递归停止,返回一个明确的值,而不再进行递归调用。基本情况通常是问题的最小规模,可以直接解决的情况。
  • 问题分解: 递归函数将问题分解为更小的子问题,然后通过递归调用自身来解决这些子问题。这种分解使得问题的解决变得更加可管理和可理解。
  • 递归链: 在递归调用中,每个函数调用都会生成一个新的函数调用,形成一个递归链。这个链条可以很长,直到达到基本情况才会开始回溯(逆向追踪)和计算最终结果。
  • 内存消耗:递归函数可能会占用大量内存,因为每个递归调用都需要在内存中保持一个函数的状态。在某些情况下,递归函数可能导致栈溢出,因此需要小心使用。
  • 可读性: 递归函数可以使某些问题的实现更加清晰和自然,因为它们直接反映了问题的递归性质。但在某些情况下,递归代码可能比迭代代码更难理解和调试。
  • 性能:递归函数在某些情况下可能不如迭代(循环)方式高效,因为递归涉及函数调用的开销和栈管理。在某些情况下,可以通过迭代方式实现相同的功能,而不涉及递归。

5.3 递归函数原理

递归函数就像一种魔法,可以帮助我们解决复杂的问题,但它们有一个特殊的秘密:自己调用自己。让我们来详细看看它是如何工作的:

  • 问题的分解: 当我们遇到一个复杂的问题时,我们通常会将它分解成更小的、更容易解决的部分。递归函数也是这样做的,它将大问题分解成一系列小问题。
  • 自我调用: 递归函数像一个魔术师一样,会在处理每个小问题时,不断地对自己说:“嗨,我知道你是一个大问题,但你看,我已经知道如何处理这个小问题了。我会继续告诉你如何处理更小的部分,直到没有更小的问题为止。
  • 终止条件: 但是,递归函数不会一直无限地自我调用。它有一种“魔法停止”的方式,称为终止条件。这就像魔术师在表演结束时告诉你:“好了,我不再需要继续了。”终止条件是递归函数的出口,当满足终止条件时,递归停止,结果就出来了。
  • 结果的汇总: 一旦每个小问题都得到解决,递归函数开始将这些小问题的结果合并起来,就像魔术师最终展示出一个惊人的魔法一样。
  • 回到开始: 最后,递归函数将结果传递回调用自己的地方,就像魔术师向观众展示魔法后,回到了表演的开始。
  • 递归链: 整个过程就像魔术师不断地重复表演,每次表演都会生成一个新的表演。这些表演形成了一个链条,直到达到终止条件才停止。

总的来说,递归函数的原理是将复杂问题分解成小问题,通过自我调用来解决这些小问题,然后将它们合并在一起,最终得到整个问题的解决方案。

5.4 递归函数业务场景

递归函数在许多业务场景中都有广泛的应用。它们特别适用于处理具有递归结构或可以分解成子问题的问题。以下是一些递归函数的常见业务场景:

  • 多层数据结构数据查找:处理多级数据结构的业务场景中,递归函数通常用于遍历、搜索、修改或执行其他操作。这些数据结构可以是树、图、嵌套的对象等。
  • 文件系统和目录结构: 在处理文件系统的目录结构时,递归函数可以用于搜索文件、计算文件大小、生成文件列表等操作。
  • 网页菜单自动生成:在网页开发中,递归函数常用于实现网页菜单的动态生成和渲染。网页菜单通常具有嵌套的层次结构,递归函数能够轻松地处理这种层次结构并生成菜单项的HTML代码。
  • 路由管理:涉及到路由管理的情况下,递归函数可以用于实现路由树或路由表的管理。路由管理的目标通常是实现 URL 路径与处理程序(handler)之间的映射,以便根据 URL 请求找到正确的处理程序。
  • 数值计算: 计算阶乘、斐波那契数列、组合数等数学问题通常可以使用递归函数来解决。

5.5 递归函数基本语法

在Go语言中,递归函数的基本语法与普通函数类似,但它包含了对自身的调用。以下是递归函数的基本语法要点:

1.函数声明

首先,您需要声明递归函数。这包括函数名称、参数列表和返回值。递归函数通常需要一个或多个参数,用于传递问题的状态。

func recursiveFunction(parameter(s)) returnType {
    // 函数体
}

上面参数说明:

  • func: 这是Go语言中声明函数的关键字。
  • recursiveFunction: 这是函数的名称,可以根据需要给函数取一个合适的名字。
  • parameter(s): 这里应该是函数的参数列表,括号内列出了函数接受的一个或多个参数。每个参数包括参数名称和参数类型,多个参数之间用逗号 , 分隔。参数用于传递给函数的输入值,供函数在函数体内使用。
  • returnType: 这是函数的返回值类型,指定了函数将返回的结果的数据类型。如果函数不返回任何值,可以将返回值类型指定为 void 或省略。

2.基本情况

在递归函数内部,通常需要定义一个或多个基本情况。这些基本情况表示递归的终止条件。如果满足基本情况,递归将停止,并返回一个特定的结果,而不再继续递归调用。

if baseCondition {
    return baseResult
}

上面参数说明:

  • baseCondition 是一个条件表达式,通常用来检查递归是否应该终止。如果 baseCondition 为真(true),则表示已经达到了递归的终止条件。
  • baseResult 是在满足基本情况时返回的结果。这是递归的停止点,函数不再继续递归调用,而是返回 baseResult

3.递归步骤

在函数体内部,通常会调用函数本身,并传递不同的参数,以便处理规模更小的子问题。这些递归调用构成了递归的核心。在递归步骤中,问题逐渐变得更小,直到达到基本情况。

result := recursiveFunction(modifiedParameter(s))
return result

上面参数说明:

  • recursiveFunction(modifiedParameter(s)): 这是递归调用函数 recursiveFunction 的部分。在括号中,modifiedParameter(s) 表示参数 s 经过某种修改后的值,作为递归函数的输入。递归函数的参数通常会随着每次递归调用而改变,以处理规模更小的子问题。这里的 modifiedParameter(s) 是一个占位符,实际上应该是适用于具体业务场景的参数。
  • result := ...: 这一行代码用于将递归函数的返回值(也就是计算结果)存储在 result 变量中。这样,我们可以在函数的后续代码中使用这个结果。
  • return result: 最后,return result 用于将计算的结果返回给函数的调用者。这是递归函数的结束点,它将结果传递回调用堆栈,最终汇总所有的递归结果。

5.6 递归函数示例

定义一个阶乘的递归函数,将下面代码粘贴到03-InitialExperiencewithFunctions-00.go

package main

import "fmt"

func factorial(n int) (result int) {
    if n > 0 {
        result = n * factorial(n-1)
        return
    } else {
        return 1
    }

}

func main() {
    i := 5
    res := factorial(i)
    fmt.Printf("%d的阶乘结果是:%v", i, res)

}

针对上面代码分为几个部分进行详细说明:

部分 1 - 导入包

import (
    "fmt"
)

这一部分是使用 import 关键字导入了 Go 语言的标准库中的 "fmt" 包。这个包包含了用于格式化输入和输出的函数

部分 2 - factorial函数定义

func factorial(n int) (result int) {
    if n > 0 {
        result = n * factorial(n-1)
        return
    } else {
        return 1
    }
}

这是一个自定义函数 factorial 的定义:

  • func factorial(n int) (result int):这行代码定义了一个名为 factorial 的函数,它接受一个整数 n 作为参数,返回一个整数 result 作为结果。
  • {} 内部的代码是函数体,包含了函数的实际执行逻辑。
  • if n > 0:这是一个条件语句,检查 n 是否大于 0。如果条件成立(即 n 大于 0),则执行下面的代码块。
  • result = n * factorial(n-1):这是一个递归调用,用于计算阶乘。它将 nfactorial(n-1) 的结果相乘,并将结果赋值给 result
  • return:根据条件,函数会返回不同的值。如果 n 大于 0,它将返回 result;否则,将返回 1(0 的阶乘定义为 1)。

部分 3 - main 函数的定义

func main() {
    i := 5
    res := factorial(i)
    fmt.Printf("%d的阶乘结果是:%v\n", i, res)
}

这是 main 函数的定义,是程序的入口点:

  • func main():这行代码定义了 main 函数,是 Go 程序的入口点。
  • {} 内部的代码是函数体,包含了程序的主要逻辑。
  • i := 5:这行代码声明并初始化了一个整数变量 i,赋值为 5。这个变量将被用作计算阶乘的输入参数。
  • res := factorial(i):这行代码调用了之前定义的 factorial 函数,计算 i 的阶乘,并将结果赋值给变量 res
  • fmt.Printf("%d的阶乘结果是:%v\n", i, res):这是一个格式化输出语句,用于将计算结果打印到控制台。它使用了 Printf 函数,其中 %d%v 是格式化占位符,分别用于插入整数 ires 的值。

运行上面代码:

$ go run .\03-InitialExperiencewithFunctions-00.go
5的阶乘结果是:120