创建函数

已完成

在 Go 中,函数允许你将一组可以从应用程序的其他部分调用的语句组合在一起。 你可以使用函数来组织代码并使其更易于阅读,而不是创建包含许多语句的程序。 更具可读性的代码也更易于维护。

到目前为止,我们一直在调用 fmt.Println() 函数,并且在 main() 函数中编写代码。 在本节中,我们将探讨如何创建自定义函数。 我们还将介绍一些可用于 Go 函数的其他技巧。

main 函数

与之交互的函数是 main() 函数。 Go 中的所有可执行程序都具有此函数,因为它是程序的起点。 你的程序中只能有一个 main() 函数。 如果创建的是 Go 包,则无需编写 main() 函数。 我们将在后续模块中介绍如何创建包。

在深入了解如何创建自定义函数的基本知识之前,让我们看看 main() 函数的一个重要特性。 你可能留意到,main() 函数没有任何参数,并且不返回任何内容。 但这并不意味着其不能从用户读取值,如命令行参数。 如要访问 Go 中的命令行参数,可以使用用于保存传递到程序的所有参数的 os 包os.Args 变量来执行操作。

下面的代码从命令行读取两个数字,并为其求和:

package main

import (
    "fmt"
    "os"
    "strconv"
)

func main() {
    number1, _ := strconv.Atoi(os.Args[1])
    number2, _ := strconv.Atoi(os.Args[2])
    fmt.Println("Sum:", number1+number2)
}

os.Args 变量包含传递给程序的每个命令行参数。 由于这些值的类型为 string,因此需要将它们转换为 int 以进行求和。

若要运行程序,请使用以下命令:

go run main.go 3 5

输出如下:

Sum: 8

让我们看看如何重构上述代码,并创建第一个自定义函数。

自定义函数

下面是用于创建函数的语法:

func name(parameters) (results) {
    body-content
}

请注意,使用 func 关键字来定义函数,然后为其指定名称。 在命名后,指定函数的参数列表。 你可以指定零个或多个参数。 你还可以定义函数的返回类型,该函数也可以是零个或多个。 (我们将在下一节中讨论如何返回多个值)。在定义所有这些值之后,你可以编写函数的正文内容。

若要练习此技巧,我们将重构上一节的代码,为自定义函数中的数字求和。 我们将使用以下代码:

package main

import (
    "fmt"
    "os"
    "strconv"
)

func main() {
    sum := sum(os.Args[1], os.Args[2])
    fmt.Println("Sum:", sum)
}

func sum(number1 string, number2 string) int {
    int1, _ := strconv.Atoi(number1)
    int2, _ := strconv.Atoi(number2)
    return int1 + int2
}

此代码创建一个名为 sum 的函数,该函数采用两个 string 参数,并将它们强制转换为 int,然后返回求和所得的结果。 定义返回类型时,函数需要返回该类型的值。

在 Go 中,你还可以为函数的返回值设置名称,将其当作一个变量。 例如,你可以重构如下 sum 函数:

func sum(number1 string, number2 string) (result int) {
    int1, _ := strconv.Atoi(number1)
    int2, _ := strconv.Atoi(number2)
    result = int1 + int2
    return
}

请注意,你现在需要将函数的结果值括在括号中。 你还可以在函数中使用该变量,并且只需在末尾添加 return 行。 Go 将返回这些返回变量的当前值。 在函数末尾编写 return 关键字非常简单方便,尤其是在有多个返回值时。 我们不建议使用此方法。 可能不确定函数将返回什么。

返回多个值

在 Go 中,函数可以返回多个值。 你可以采用类似于定义函数参数的方式来定义这些值。 换句话说,你可以指定一个类型和名称,但该名称是可选的。

例如,假设你想要创建一个函数,以将两个数字求和,又让它们相乘。 函数代码将如下所示:

func calc(number1 string, number2 string) (sum int, mul int) {
    int1, _ := strconv.Atoi(number1)
    int2, _ := strconv.Atoi(number2)
    sum = int1 + int2
    mul = int1 * int2
    return
}

你现在需要两个变量来存储函数的结果。 (否则就不会进行编译。)它的外观如下所示:

package main

import (
    "fmt"
    "os"
    "strconv"
)

func main() {
    sum, mul := calc(os.Args[1], os.Args[2])
    fmt.Println("Sum:", sum)
    fmt.Println("Mul:", mul)
}

Go 的另一个有趣功能是,如果不需要函数的某个返回值,可以通过将返回值分配给 _ 变量来放弃该函数。 _ 变量是 Go 忽略返回值的惯用方式。 它允许程序进行编译。 因此,如果只需要求和,则可以使用以下代码:

package main

import (
    "fmt"
    "os"
    "strconv"
)

func main() {
    sum, _ := calc(os.Args[1], os.Args[2])
    fmt.Println("Sum:", sum)
}

在后续模块中探讨错误处理时,我们将详细介绍如何忽略函数的返回值。

更改函数参数值(指针)

将值传递给函数时,该函数中的每个更改都不会影响调用方。 Go 是“按值传递”编程语言。 每次向函数传递值时,Go 都会使用该值并创建本地副本(内存中的新变量)。 在函数中对该变量所做的更改都不会影响你向函数发送的更改。

例如,假设你创建了一个用于更新人员姓名的函数。 请注意,运行此代码时会发生的变化:

package main

import "fmt"

func main() {
    firstName := "John"
    updateName(firstName)
    fmt.Println(firstName)
}

func updateName(name string) {
    name = "David"
}

即使你在函数中将该名称更改为 David,输出仍为 John。由于 updateName 函数中的更改仅会修改本地副本,因此输出不会发生变化。 Go 传递变量的值,而不是变量本身。

如果你希望在 updateName 函数中进行的更改会影响 main 函数中的 firstName 变量,则需要使用指针。 指针是包含另一个变量的内存地址的变量。 向函数发送指针时,不是传递值,而是传递内存地址。 因此,对该变量所做的每个更改都会影响调用方。

在 Go 中,有两个运算符可用于处理指针:

  • & 运算符使用其后对象的地址。
  • * 运算符取消引用指针。 你可以前往指针中包含的地址访问其中的对象。

让我们修改前面的示例,以阐明指针的工作方式:

package main

import "fmt"

func main() {
    firstName := "John"
    updateName(&firstName)
    fmt.Println(firstName)
}

func updateName(name *string) {
    *name = "David"
}

运行前面的代码。 请注意,输出现在显示的是 David,而不是 John

首先要做的就是修改函数的签名,以指明你要接收指针。 为此,请将参数类型从 string 更改为 *string。 (后者仍是字符串,但现在它是指向字符串 的 指针。)然后,将新值分配给该变量时,需要在该变量的左侧添加星号 (*) 以暂停该变量的值。 调用 updateName 函数时,系统不会发送值,而是发送变量的内存地址。 变量左侧的 & 符号指示变量的地址。