# 函数

> Go语言的函数，很像JavaScript语言的函数

```go
func 函数名称 (形参1, 形参2) (返回值类型列表) {
  函数体
  retrun 返回值列表
}
```

## 函数的声明

### 函数的命名

* 函数名也是标识符，遵循标识符的命名规范
* 首字母小写，只能被本包使用
* 首字母大写，可以被本包以及其他包使用

### 重载：不支持

```go
// 以下代码会报错
// 编译器会认为下面的两个方法定义是重复的
func test() {}

func test(a string) {}
```

### 无参函数

```go
func test() {}
```

### 有参函数：参数类型一致

```go
func test(x, y float32) {}
```

### 有参函数：类型不一致

```go
func test(x string, y int) {}
```

### 有参函数：可变参数

```go
func test(names...int) {
  // 可变参数在方法内会被转换为切片，可以对切片进行遍历取值
}
```

### 无返回值函数

如果是无返回函数，返回值类型不填

```go
func test() {
}
// 调用函数
test()
```

### 单返回值函数

如果只有一个返回值，可以省略括号

```go
func test() string {
  return "hello"
}
// 调用函数
var s = test()
```

### 多返回值函数

函数可以有多个返回值，多个返回值返回使用逗号隔开

```go
func test() (string, int) {
  return "hello", 20
}
// 调用函数
var s, i = test()
```

### 带有变量名的返回值

```go
func namedRetValues() (a, b int) {
    a = 1
    b = 2
    return
}
// 直接返回a 和 b
```

## 函数的调用

### 调用多返回值函数

```go
var s, i = test()
```

### 忽略多返回值函数返回值

```go
// 调用函数，忽略部分返回值
var s, _ = test()
```

## 函数参数的值传递与引用传递

### 基本类型形参是值传递

```go
// 交换两个数字
func exchangeNum(num1 int, num2 int) {
	var t int
	t = num1
	num1 = num2
	num2 = t
}

func main() {
	var i = 10
	var j = 20
	exchangeNum(i, j)
	fmt.Printf("i = %d, j = %d", i, j)  // 结果为 i = 10, j = 20
}
```

使用指针可以做到引用传递：

```go
// 交换两个数字
// 参数类型为指针
func exchangeNum(num1Ptr *int, num2Ptr *int) {
	var t int
	t = *num1Ptr
	*num1Ptr = *num2Ptr
	*num2Ptr = t
}

func main() {
	var i = 10
	var j = 20
	exchangeNum(&i, &j)
	fmt.Printf("i = %d, j = %d", i, j) // 结果为 i = 20, j = 10
}
```

### 引用类型形参是引用传递

* 指针
* 切片
* map
* 函数
* channel

等数据类型。

## 匿名函数

### 定义并直接调用匿名函数

```go
func(data int) {
    fmt.Println("hello", data)
}(100)
```

### 变量函数

```go
// 将匿名函数体保存到f()中
f := func(data int) {
    fmt.Println("hello", data)
}
// 使用f()调用
f(100)
```

### 变量函数传递

```go
package main
import (
    "fmt"
)
// 遍历切片的每个元素, 通过给定函数进行元素访问
func visit(list []int, f func(int)) {
    for _, v := range list {
        f(v)
    }
}
func main() {
    // 使用匿名函数打印切片内容
    visit([]int{1, 2, 3, 4}, func(v int) {
        fmt.Println(v)
    })
}
```

### 匿名函数应用技巧

```go
func main() {
	var skill = map[string]func(){
		"fire": func() {
			fmt.Println("chicken fire")
		},
		"run": func() {
			fmt.Println("soldier run")
		},
		"fly": func() {
			fmt.Println("angel fly")
		},
	}
	skill["fire"]()
}
```

## 闭包（Closure）

闭包就是能够读取其他函数内部变量的函数，在其他语言中，也称为Lambda表达式。在Golang中，闭包组成如下：

```go
匿名函数 + 引用环境 = 闭包
```

下面是一个闭包累加器：

```go
package main

import "fmt"

// 定义一个名为getSum的函数，无参，返回值是一个func (int) int 的函数
func getSum() func (int) int {
	var sum = 0
	return func(num int) int { // 这个拥有sum变量的匿名函数就是闭包
		sum = sum + num // sum变量的作用域被扩大，将会被一直保存在内存中，所以要适当使用闭包
		fmt.Println(sum)
		return sum
	}
}

func main() {
	sumF := getSum()
	sumF(1) // 1. 入门
	sumF(2) // 3
	sumF(3) // 6
}
```

结果：

```shell
$ go run main.go 
1
3
6
```

## defer 延迟执行语句

类似于Java的Finally语句，通常用于标记关闭资源的语句，让其使用完毕后自动关闭：

```go
package main
import (
	"fmt"
)
func main() {
	testDefer()
}
func printReturn() int {
	fmt.Println("defer return")
	return 0
}

func testDefer() int {
	fmt.Println("defer begin")
	var num1 int = 30
	var num2 int = 60

	// 将defer放入延迟调用栈，这是一个新的栈空间
	// 涉及的变量如果是基本类型，则会进行值传递，如果是引用类型则是传递引用
  // 因为num1和num2是基本类型，所以打印30和60
	defer fmt.Println(num1) // 先放入的是栈底，最后调用
	defer fmt.Println(num2) // 后放入的是栈顶，最先调用

	num1 += 10
	num2 += 10

	fmt.Println("num1 和 num2:", num1, num2)

	return printReturn() // defer 在return语句之后运行
}
```

执行结果如下：

```shell
$ go run main.go 
defer begin
num1 和 num2: 40 70
defer return
60
30
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://yangsx95.gitbook.io/notes/programming-language/golang/han-shu.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
