声明

image-20231111162657319

  1. 关键字 func
    • Go 函数声明必须以关键字 func 开始
  2. 函数名
    • 同一个 Go 包中,函数名应该是唯一
    • 遵循 Go 标识符的导出规则
  3. 参数列表
    • Go 函数支持变长参数,即一个形式参数对应多个实际参数
  4. 返回值列表
    • 支持具名返回值,比较少用
  5. 函数体
    • 可选,如果没有函数体,说明该函数可能在 Go 语言之外实现的
    • 可能使用汇编语言,然后通过链接器将实现与声明中的函数名链接在一起

类比

函数声明等价转换为变量声明的形式 - 声明一个类型为函数类型的变量

image-20231111163953587

Key Value
变量名 函数名
函数签名 参数列表 + 返回值列表
函数类型 func + 参数列表 + 返回值列表
func + 函数签名
  1. 函数签名:决定两个函数类型是否相同
  2. 在表述函数类型时,通常会省略函数签名参数列表中的参数名,以及返回值列表中的返回值变量名
1
2
// 函数类型,只关注入参和出参
func (io.Writer, string, ...interface{}) (int, error)

相同的函数签名,相同的函数类型

1
2
3
4
5
6
7
8
func (a, b int) (res []string, err error)
func (c, d int) (r []string, e error)

// 函数签名
(int, int) ([]string, error)

// 函数类型
func(int, int) ([]string, error)

每个函数声明所定义的函数,仅仅是对应函数类型的一个实例

字面量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package main

import "fmt"

type T struct {
}

func main() {
s := T{} // 复合类型字面量
f := func() {} // 函数类型字面量,由函数类型和函数体组成,也称为匿名函数

fmt.Printf("%T\n", s) // main.T
fmt.Printf("%T\n", f) // func()
}

在 Go 中,在大部分时候,还是会通过函数声明来声明一个特定函数类型的实例

1
2
3
4
// 本质上是声明了一个函数类型为 func(int, int) int 的变量 sum
func sum(a, b int) int {
return a + b
}

参数

  1. 函数列表中的参数,是函数声明的,用于函数体实现的局部变量
  2. 函数声明阶段,称为形式参数;在函数调用阶段,传入的参数称为实际参数
  3. 在实际调用函数时,实际参数会传递给函数,并且与形式参数逐一绑定
    • 编译器会根据各个形式参数的类型数量,来检查实际参数的类型和数量,是否匹配
    • 如果不匹配,编译器会报错

image-20231111170620385

值传递

函数参数传递采用的是值传递的方式,将实际参数在内存中的表示,逐位拷贝(Bitwise Copy)到形式参数

类型 拷贝 描述
intarraystruct 拷贝 内存表示为数据本身,值传递开销与数据大小正比
stringslicemap 拷贝 内存表示为数据的描述符,值传递开销固定

例外

简单的值传递不能满足需求,编译器需要介入

接口类型

编译器将传递的实际参数赋值给对应的接口类型形式参数

变长参数

变长参数是通过切片来实现的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main

import "fmt"

func myAppend(sl []int, elems ...int) []int {
fmt.Printf("%T\n", elems) // []int

if len(sl) == 0 {
return sl
}

sl = append(sl, elems...)
return sl
}

func main() {
sl := []int{1, 2, 3}
sl = myAppend(sl)
fmt.Println(sl) // [1 2 3]

sl = myAppend(sl, 4, 5, 6)
fmt.Println(sl) // [1 2 3 4 5 6]
}

多返回值

Go 的错误处理机制很大程度上是建立在多返回值的机制上

1
2
3
4
func foo()
func foo() error
func foo() (int, string, error)
func foo() (i int, s string, e error) // 具名返回值

具名返回值

  1. 支持具名返回值(Named return value),可以像函数体中声明的局部变量一样,在函数体内使用
  2. 具名返回值仅应用于特定场景
    • 在函数中使用 defer,并且还在 defer 函数中修改包裹函数的返回值
    • 当函数返回值较多时,使用具名返回值可以增强可读性
time/format.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
func parseNanoseconds(value string, nbytes int) (ns int, rangeErrString string, err error) {
if value[0] != '.' {
err = errBad
return
}
if ns, err = atoi(value[1:nbytes]); err != nil {
return
}
if ns < 0 || 1e9 <= ns {
rangeErrString = "fractional second"
return
}
// We need nanoseconds, which means scaling by the number
// of missing digits in the format, maximum length 10. If it's
// longer than 10, we won't scale.
scaleDigits := 10 - nbytes
for i := 0; i < scaleDigits; i++ {
ns *= 10
}
return
}

一等公民

具有极大的灵活性

  1. 如果一门编程语言对某种语言元素的创建和使用没有限制,可以像对待一样对待这种语言元素
  2. 可以存储在变量中,可以作为参数传递给函数,可以在函数内部创建并作为返回值从函数返回

特征

变量

1
2
3
4
5
6
7
8
9
10
11
var (
// 创建匿名函数并赋值给 myFprintf 变量
myFprintf = func(w io.Writer, format string, args ...interface{}) (int, error) {
return fmt.Fprintf(w, format, args...)
}
)

func main() {
fmt.Printf("%T\n", myFprintf) // func(io.Writer, string, ...interface {}) (int, error)
_, _ = myFprintf(os.Stdout, "Hello, %s!\n", "Go") // Hello, Go!
}

返回值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main

func setup(task string) func() {
println("init", task)

// 函数内创建匿名函数,并作为返回值返回
return func() {
println("clean up", task) // 闭包,引用了包裹函数的变量 task
}
}

func main() {
// 常用于单元测试
cleanup := setup("opa")
defer cleanup()

println("do something")
}

// Output:
// init opa
// do something
// clean up opa
  1. 闭包本质上是一个匿名函数(函数字面量),可以引用它的包裹函数中定义的变量
  2. 这些变量在包裹函数匿名函数之间共享
  3. Go 的闭包特性是建立在函数是一等公民的基础上

参数

time/sleep.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// AfterFunc waits for the duration to elapse and then calls f
// in its own goroutine. It returns a Timer that can
// be used to cancel the call using its Stop method.
func AfterFunc(d Duration, f func()) *Timer {
t := &Timer{
r: runtimeTimer{
when: when(d),
f: goFunc,
arg: f,
},
}
startTimer(&t.r)
return t
}

类型

每个函数声明定义的函数,仅仅只是对应的函数类型的一个实例

基于函数类型来自定义类型

net/http/server.go
1
2
3
4
5
// The HandlerFunc type is an adapter to allow the use of
// ordinary functions as HTTP handlers. If f is a function
// with the appropriate signature, HandlerFunc(f) is a
// Handler that calls f.
type HandlerFunc func(ResponseWriter, *Request)
sort/genzfunc.go
1
type visitFunc func(ast.Node) ast.Visitor

应用

函数类型

函数是一等公民,拥有对应的类型,可以被显式转型(前提是底层类型要一致)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// greeting 是一个函数,它的类型是 func(http.ResponseWriter, *http.Request)
func greeting(w http.ResponseWriter, r *http.Request) {
_, _ = fmt.Fprintf(w, "Hello World!")
}

func main() {
// 能够通过编译器检查,主要是因为 HandlerFunc 的底层类型与 greeting 函数的类型是一致的
fmt.Printf("%T\n", greeting) // func(http.ResponseWriter, *http.Request)

// cannot use greeting (type func(http.ResponseWriter, *http.Request)) as type http.Handler in argument to http.ListenAndServe:
// func(http.ResponseWriter, *http.Request) does not implement http.Handler (missing ServeHTTP method)
//_ = http.ListenAndServe(":18088", greeting)

// 将 greeting 函数显式转型为 http.HandlerFunc 类型(实现了 Handler 接口)
_ = http.ListenAndServe(":18088", http.HandlerFunc(greeting))
}
net/http/server.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
func ListenAndServe(addr string, handler Handler) error {
server := &Server{Addr: addr, Handler: handler}
return server.ListenAndServe()
}

type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}

// 基于函数类型定义的新类型,其底层类型为函数类型 func(ResponseWriter, *Request)
type HandlerFunc func(ResponseWriter, *Request)

// HandlerFunc 实现了 Handler 接口
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}

闭包

简化函数调用,闭包是函数内部创建的匿名函数,可以访问包裹函数参数局部变量

1
2
3
4
5
6
7
8
9
func times(x, y int) int {
return x * y
}

func main() {
times(2, 5)
times(3, 5)
times(4, 5)
}

通过闭包简化函数调用,减少参数的重复输入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
func times(x, y int) int {
return x * y
}

func partialTimes(x int) func(int) int {
// 匿名函数
return func(y int) int {
return times(x, y) // 闭包,使用了包裹函数的参数
}
}

func main() {
timesFive := partialTimes(5)

println(timesFive(2)) // 10
println(timesFive(3)) // 15
println(timesFive(4)) // 20
}