Go 零基础教程

Go 函数定义和调用

函数允许你将执行特定任务的一段代码封装起来,从而让你的代码变得更加模块化、可复用且易于理解。

本章将全面覆盖如何在 Go 语言中定义和调用函数,包括函数签名、参数传递以及返回值的核心概念。

1. 定义函数

在 Go 语言中,我们使用 func 关键字来定义一个函数。紧随其后的是函数名、参数列表(用圆括号括起来)、可选的返回值列表,最后是函数体(用大括号括起来)。

1.1 基础语法解析

定义函数的通用语法如下:

func 函数名(参数1 类型1, 参数2 类型2, ...) (返回值类型1, 返回值类型2, ...) {
    // 函数体代码
    return 返回值1, 返回值2, ...
}

让我们逐一拆解:

  • func 关键字: 明确告诉编译器,你正在定义一个函数。
  • 函数名: 给你的函数起的名字。应该使用具有描述性的名字,让人一眼看出它在干什么。
  • 参数列表: 函数接收的输入数据。每个参数由名字和类型组成。
  • 返回值列表: 函数执行完毕后输出的数据类型。如果函数不返回任何东西,这部分可以省略。
  • { ... }: 函数体,包含具体的执行逻辑。
  • return 语句: 用于将结果送出函数。如果函数规定了返回值,就必须有 return

1.2 第一个简单函数示例

来看一个计算两个整数之和的简单函数:

package main

import "fmt"

// 定义一个名为 add 的函数
func add(x int, y int) int {
    sum := x + y
    return sum // 返回计算结果
}

func main() {
    result := add(5, 3) // 调用 add 函数
    fmt.Println(result) // 输出: 8
}

在这个例子中,add 函数接收两个 int 类型的参数 xy,并且声明了它会返回一个 int 类型的值。

1.3 参数类型简写 (Shorthand)

如果函数有多个连续的参数属于同一种数据类型,你可以只在最后一个参数后面写上类型,前面的都可以省略:

// x 和 y 都是 int 类型,可以简写为 x, y int
func add(x, y int) int {
    return x + y
}

这种写法能让代码看起来更加精简干练。

1.4 无参数函数

函数当然也可以不接收任何参数,这时圆括号里留空即可:

package main

import "fmt"

func greet() {
    fmt.Println("Hello, world!")
}

func main() {
    greet() // 调用时括号也不能少
}

2. Go 语言的杀手锏:多返回值

Go 语言原生支持一个函数同时返回多个值。这是一个极其强大的特性,最典型的应用场景就是同时返回业务结果和错误信息 (Error)

package main

import (
    "fmt"
    "errors"
)

// divide 函数返回两个值:计算结果 (float64) 和 错误信息 (error)
func divide(x, y float64) (float64, error) {
    if y == 0 {
        // 如果除数为 0,返回 0 和一个具体的错误
        return 0, errors.New("除数不能为零")
    }
    // 计算成功,返回结果和 nil (表示没有错误)
    return x / y, nil
}

func main() {
    // 正常除法
    result, err := divide(10.0, 2.0)
    if err != nil {
        fmt.Println("发生错误:", err)
        return
    }
    fmt.Println("计算结果:", result) // 输出: 计算结果: 5

    // 触发零除错误
    result, err = divide(10.0, 0.0)
    if err != nil {
        fmt.Println("发生错误:", err) // 输出: 发生错误: 除数不能为零
        return
    }
    fmt.Println("计算结果:", result)
}

在这个例子中,接收方必须同时处理正常的返回值和可能发生的错误,这强制开发者写出更安全的防御性代码。

3. 命名返回值 (Named Return Values)

Go 还允许你在函数签名中直接为返回值命名。一旦命名,这些变量在函数刚开始执行时就会被自动初始化为零值。

当你使用命名返回值时,你可以在函数末尾使用一个不带任何参数的 “裸返回” (Naked return),它会自动将当前命名变量的值返回出去。

package main

import "fmt"

// sum 和 difference 是命名的返回值
func addAndSubtract(x, y int) (sum int, difference int) {
    sum = x + y         // 直接赋值给命名的返回值变量
    difference = x - y
    return              // 裸返回:自动返回 sum 和 difference 当前的值
}

func main() {
    s, d := addAndSubtract(5, 3)
    fmt.Println("和:", s, "差:", d) // 输出: 和: 8 差: 2
}
最佳实践警告: 虽然裸返回在短小的函数里看起来很酷,但在长函数中,它会严重破坏代码的可读性(读者很难一眼看出到底返回了什么)。因此,建议只在非常简短的函数中使用命名返回值和裸返回

4. 调用函数与值传递 (Pass by Value)

调用函数非常简单,写出函数名并传入匹配的参数即可:multiply(4, 6)

但你需要牢记 Go 语言中一个极其重要的底层原理:Go 语言中的函数参数传递,全部都是“值传递” (Pass by Value)

这意味着,当你把一个变量传给函数时,Go 实际上是把这个变量的值拷贝了一份,把复印件交给了函数。函数在内部怎么折腾这份复印件,都绝对不会影响到函数外面的原件

package main

import "fmt"

func modifyValue(x int) {
    x = x * 2 // 这里修改的是 x 的复印件
    fmt.Println("函数内部的值:", x) // 输出: 20
}

func main() {
    value := 10
    modifyValue(value) // 把 value 拷贝一份传进去
    
    // 原件毫发无损!
    fmt.Println("函数外部的值:", value) // 输出: 10
}

如果你确实需要让函数修改外部变量的值,你需要使用指针 (Pointers),我们会在后续课程中专门讲解。

4.1 忽略不需要的返回值

如果一个函数返回了多个值,但你只对其中某几个感兴趣,Go 编译器不允许你声明了变量却不用。此时你需要使用空白标识符 _ 来丢弃不需要的返回值。

package main

import "fmt"

func getCoordinates() (int, int, int) {
    return 10, 20, 30
}

func main() {
    // 我们只需要前两个坐标,第三个丢弃
    x, y, _ := getCoordinates() 
    fmt.Println("X:", x, "Y:", y) // 输出: X: 10 Y: 20
}