vault backup: 2026-06-07 12:14:39
This commit is contained in:
+173
-197
@@ -1,222 +1,198 @@
|
||||
---
|
||||
tags:
|
||||
- Go
|
||||
- golang
|
||||
- go基础语法
|
||||
- 函数
|
||||
tags: [go, golang, go基础语法, 函数]
|
||||
create time: 2026-06-07 15:00
|
||||
---
|
||||
|
||||
# Go语言函数
|
||||
# Go 语言函数
|
||||
|
||||
同其他语言一样,Go语言也有函数的概念,主要是为了代码复用,函数是基本的代码块,用于执行一个任务。
|
||||
Go 语言最少有`main()`函数,同时`main()`函数也是整个程序执行的入口。
|
||||
## 概述
|
||||
|
||||
Go 的函数是一等公民:可以赋值给变量、作为参数传递、返回匿名函数(闭包)。本文覆盖多返回值、变参、闭包、匿名函数和值传递机制。
|
||||
|
||||
## 正文
|
||||
|
||||
### 为什么 Go 函数支持多返回值?
|
||||
|
||||
在多数语言中,函数只能返回一个值——错误信息需要单独处理(如异常或输出参数)。Go 的选择是:**让错误成为显式的返回值**。
|
||||
|
||||
## 函数定义
|
||||
Go语言函数定义格式如下:
|
||||
```go
|
||||
func function_name([parameter list]) [return_types] {
|
||||
function_body
|
||||
name, age, err := GetUser(42)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
```
|
||||
- func:函数关键字,任何一个函数都有func关键字开始声明。
|
||||
- function_name:函数名称,参数列表和返回值类型构成了函数签名。
|
||||
- parameter list:参数列表,参数列表指定的是参数类型、顺序、及参数个数。参数是可选的,也就是说函数也可以不包含参数。
|
||||
- return_types:返回类型,函数返回一列值。`return_types`是该列值的数据类型。有些功能不需要返回值,这种情况下return_types不是必须的。
|
||||
- 函数体:函数定义的代码集合。
|
||||
- 注意:Go语言函数区别C++和和Java语言的地方是可以有多个返回值,多个返回值用小括号括起来,中间用逗号分隔。
|
||||
|
||||
代码展示:
|
||||
```go
|
||||
package main
|
||||
这比隐藏的错误处理更透明——编译器强制你处理每一个返回值。
|
||||
|
||||
import "fmt"
|
||||
### 基本定义与调用
|
||||
|
||||
```go
|
||||
func swap(x, y string) (string, string) {
|
||||
return y, x
|
||||
return y, x
|
||||
}
|
||||
|
||||
a, b := swap("Google", "Hello")
|
||||
fmt.Println(a, b) // Hello Google
|
||||
```
|
||||
|
||||
无返回值时省略返回类型;有返回值时必须写:
|
||||
|
||||
```go
|
||||
func max(a, b int) int {
|
||||
if a > b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func greet(name string) { // 无返回值
|
||||
fmt.Printf("Hello, %s!\n", name)
|
||||
}
|
||||
```
|
||||
|
||||
### 命名返回值
|
||||
|
||||
Go 允许给返回值命名,`return` 可以不写具体值(直接返回):
|
||||
|
||||
```go
|
||||
func Split(sum int) (x, y int) {
|
||||
x = sum * 4 / 9
|
||||
y = sum - x
|
||||
return // 空白 return,返回当前 x, y 的值
|
||||
}
|
||||
```
|
||||
|
||||
> [!tip] 💡 命名返回值的用途
|
||||
> 虽然不常用,但在复杂函数的"提前返回"场景中可以让代码更清晰——读者一眼就知道返回了什么。
|
||||
|
||||
### 变参
|
||||
|
||||
变参函数接受零个或多个同类型参数:
|
||||
|
||||
```go
|
||||
func Sum(nums ...int) int {
|
||||
total := 0
|
||||
for _, n := range nums {
|
||||
total += n
|
||||
}
|
||||
return total
|
||||
}
|
||||
|
||||
Sum(1, 2, 3) // 6
|
||||
Sum(1, 2, 3, 4, 5) // 15
|
||||
|
||||
// 将切片展开传入
|
||||
nums := []int{1, 2, 3, 4}
|
||||
Sum(nums...) // 10
|
||||
```
|
||||
|
||||
> [!warning] ⚠️ `...int` 底层是 `[]int`
|
||||
> 在函数内部,`nums` 就是一个 slice。用 `range nums` 遍历时要注意前面提到的闭包陷阱。
|
||||
|
||||
### 函数是一等公民
|
||||
|
||||
函数可以赋值给变量、作为参数传递:
|
||||
|
||||
```go
|
||||
// 函数变量
|
||||
getSquareRoot := func(x float64) float64 {
|
||||
return math.Sqrt(x)
|
||||
}
|
||||
fmt.Println(getSquareRoot(9)) // 3
|
||||
|
||||
// 函数作为参数(回调)
|
||||
type Calculator func(int, int) int
|
||||
|
||||
func apply(a, b int, op Calculator) int {
|
||||
return op(a, b)
|
||||
}
|
||||
|
||||
result := apply(3, 4, func(x, y int) int {
|
||||
return x + y
|
||||
}) // 7
|
||||
```
|
||||
|
||||
### 匿名函数与闭包
|
||||
|
||||
匿名函数是没有名称的函数,通常内联使用:
|
||||
|
||||
```go
|
||||
func() {
|
||||
fmt.Println("我是匿名函数")
|
||||
}() // 末尾 () 立即执行
|
||||
```
|
||||
|
||||
**闭包 = 匿名函数 + 捕获的外部变量**。闭包会"记住"它创建时的环境变量:
|
||||
|
||||
```go
|
||||
func counter() func() int {
|
||||
i := 0
|
||||
return func() int {
|
||||
i++
|
||||
return i
|
||||
}
|
||||
}
|
||||
|
||||
next := counter()
|
||||
fmt.Println(next()) // 1
|
||||
fmt.Println(next()) // 2
|
||||
fmt.Println(next()) // 3
|
||||
|
||||
next2 := counter() // 全新的计数器
|
||||
fmt.Println(next2()) // 1 — 独立于 next
|
||||
```
|
||||
|
||||
> [!note] 📝 每次调用 counter() 都创建新的闭包环境
|
||||
> `next` 和 `next2` 各自维护独立的 `i` 变量,互不影响。这是闭包的核心特性。
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
A["counter()"] --> B["i = 0"]
|
||||
B --> C["返回闭包 → next"]
|
||||
C --> D["next() 调用: i→1"]
|
||||
D --> E["next() 调用: i→2"]
|
||||
A --> F["counter() 再次调用"]
|
||||
F --> G["i = 0 新变量"]
|
||||
G --> H["返回闭包 → next2"]
|
||||
H --> I["next2() 调用: i→1"]
|
||||
```
|
||||
|
||||
> [!warning] ⚠️ 闭包捕获的是引用
|
||||
> 如果外部变量在闭包外被修改,闭包看到的也是修改后的值。配合 `for range` 使用时要特别注意(见「循环」章节)。
|
||||
|
||||
### 值传递:Go 只有值传递
|
||||
|
||||
Go 的所有参数传递都是**值传递**——参数被拷贝一份传给函数。但对于 slice、map、chan 这些引用类型,拷贝的是内部的指针,所以修改内容会影响原数据。
|
||||
|
||||
```go
|
||||
func addOne(x int) {
|
||||
x++ // 只修改了副本
|
||||
}
|
||||
|
||||
func main() {
|
||||
a, b := swap("Google", "Hello")
|
||||
fmt.Println(a, b)
|
||||
a := 5
|
||||
addOne(a)
|
||||
fmt.Println(a) // 5 — 没变!
|
||||
}
|
||||
```
|
||||
运行结果:
|
||||
```
|
||||
Hello Google
|
||||
```
|
||||
|
||||
## 函数调用
|
||||
当创建函数时,你定义了函数需要做什么,通过调用该函数来执行指定任务。
|
||||
调用函数,向函数传递参数,并返回值,例如:
|
||||
```go
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
func main() {
|
||||
/* 定义局部变量 */
|
||||
var a int = 100
|
||||
var b int = 200
|
||||
var ret int
|
||||
|
||||
/* 调用函数并返回最大值 */
|
||||
ret = max(a, b)
|
||||
|
||||
fmt.Printf( "最大值是 : %d\n", ret )
|
||||
}
|
||||
|
||||
/* 函数定义:返回两个数的最大值 */
|
||||
func max(num1, num2 int) int {
|
||||
/* 定义局部变量 */
|
||||
var result int
|
||||
|
||||
if num1 > num2 {
|
||||
result = num1
|
||||
} else {
|
||||
result = num2
|
||||
}
|
||||
return result
|
||||
}
|
||||
```
|
||||
运行结果:
|
||||
```
|
||||
最大值是 : 200
|
||||
```
|
||||
|
||||
## 参数传递
|
||||
在Go语言中函数的参数传递都是值传递,不存在引用传递(区别于c++)
|
||||
**注意:使用值传递的时候,虽然会改变形参的值,但并不会改变函数外变量即实参的值**
|
||||
代码展示:
|
||||
```go
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
func main() {
|
||||
/* 定义局部变量 */
|
||||
var a int = 100
|
||||
|
||||
fmt.Printf("自增前 a 的值为 : %d\n", a )
|
||||
|
||||
add(a)
|
||||
|
||||
fmt.Printf("自增后 a 的值 : %d\n", a )
|
||||
}
|
||||
|
||||
/* 函数定义:自增1 */
|
||||
func add(a int) {
|
||||
a++;
|
||||
fmt.Printf("add里a的值:%d\n", a)
|
||||
}
|
||||
```
|
||||
运行结果:
|
||||
```
|
||||
自增前 a 的值为 : 100
|
||||
add里a的值:101
|
||||
自增后 a 的值 : 100
|
||||
```
|
||||
|
||||
## 函数用法
|
||||
|
||||
### 函数变量
|
||||
一切皆变量,Go语言里什么都可以当作变量来使用,当然函数也不例外,函数可以作为函数变量。Go语言可以很灵活的创建函数,并作为另外一个函数的实参。以下实例中我们在定义的函数中初始化一个变量,该函数仅仅是为了使用内置函数
|
||||
代码展示:
|
||||
```go
|
||||
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"
|
||||
|
||||
// 声明一个函数类型
|
||||
type fc func(int) int
|
||||
|
||||
func main() {
|
||||
CallBack(1, callBack)//执行函数---CallBack
|
||||
}
|
||||
|
||||
func CallBack(x int, f fc) { //定义了一个函数 testCallBack
|
||||
f(x) //由于传进来的是callBack函数,该函数执行需要传入一个int类型参数,因此传入x
|
||||
}
|
||||
|
||||
func callBack(x int) int {
|
||||
fmt.Printf("我是回调,x:%d\n", x)
|
||||
return x
|
||||
}
|
||||
```
|
||||
运行结果:
|
||||
```
|
||||
我是回调,x:1
|
||||
```
|
||||
|
||||
### 函数闭包
|
||||
所谓闭包,就是匿名函数,Go语言支持匿名函数调用。匿名函数就是一个"内联"语句或表达式。匿名函数的优越性在于可以直接使用函数内的变量,不必申明,这样有时候可以使代码更简单,增强代码的可读性。
|
||||
代码展示:
|
||||
```go
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
func getNumber() func() int {
|
||||
i:=0
|
||||
return func() int {
|
||||
i+=1
|
||||
return i
|
||||
}
|
||||
func addOnePtr(x *int) {
|
||||
*x++
|
||||
}
|
||||
|
||||
func main(){
|
||||
/* nextNumber 为一个函数,函数中 i 为 0 */
|
||||
nextNumber := getNumber()
|
||||
|
||||
/* 调用 nextNumber 函数,i 变量自增 1 并返回 */
|
||||
fmt.Println(nextNumber())
|
||||
fmt.Println(nextNumber())
|
||||
fmt.Println(nextNumber())
|
||||
|
||||
/* 创建新的函数 nextNumber1,并查看结果 */
|
||||
nextNumber1 := getNumber()
|
||||
fmt.Println(nextNumber1())
|
||||
fmt.Println(nextNumber1())
|
||||
}
|
||||
a := 5
|
||||
addOnePtr(&a)
|
||||
fmt.Println(a) // 6 ✅
|
||||
```
|
||||
运行结果:
|
||||
```
|
||||
1
|
||||
2
|
||||
3
|
||||
1
|
||||
2
|
||||
```
|
||||
> **注意:多次调用同一个函数`nextNumber`,其中的i变量是同一个,初始值为0,每调用一次,i自增1**
|
||||
|
||||
代码解释:
|
||||
|
||||
首先,`getNumer`这个函数会返回一个闭包函数,我们先理解闭包是啥,题库里面我也写过的,闭包就是等于 匿名函数+捕获的变量,我们可以看到第7行-第10行,闭包函数里面捕获了i变量,因为i不是在闭包函数里面定义的,
|
||||
第15行代码,通过`getNumber`获取了一个闭包函数——`nextNumber`,18,19,20行分别执行一次`nextNumber`函数,每次i就会自增1,并且因为每次调用`i`都是固定的,结果会累计,所以会输出1,2,3。23行重新获取了一个闭包函数,`i`是独立于上面闭包函数的,所以重新输出1,2,3
|
||||
|
||||
|
||||
### 关键要点总结
|
||||
|
||||
| 特性 | 说明 |
|
||||
|------|------|
|
||||
| 多返回值 | Go 独有,广泛用于错误处理 |
|
||||
| 变参 `...T` | 底层是 `[]T`,可用 `...` 展开 slice |
|
||||
| 闭包 | 匿名函数捕获外部变量,形成独立状态 |
|
||||
| 值传递 | 所有参数都是值拷贝,引用类型的"引用"本质是指针拷贝 |
|
||||
|
||||
Reference in New Issue
Block a user