流程语句是整个程序的逻辑依托,函数构成逻辑结构。
和 Python 一样,在 Go 中,函数的基本组成为 6 个部分:
- func 关键字
- 函数名
- 参数列表
- 返回值
- 函数体
- 返回语句
函数的涉及范围包括 5 个部分:
- 函数定义
- 函数调用
- 不定参数
- 多返回值
- 匿名函数与闭包
函数定义
package mymath
import "errors"
func Add (a int, b int) (ret int, err error) {
if a<0 || b<0 { // 假设这个函数只支持两个非负数组的加法
err = errors.New("Should be non-negative numbers")
return
}
return a+b, nil // 支持多重返回值
}
在 Python 中不需要对变量做显示的声明,在 Go 中则尽量的做到叙述的简练。如果参数列表中若干个相邻的参数类型相同,则可以在参数列表中省略前面变量的类型声明。
func Add(a, b int)(ret int, err error) {
//...
}
如果返回值列表中多个返回值的类型相同,也可以用同样的方式合并。
func Add(a, b int) int {
// ...
}
函数调用
函数调用的方式包括 2 步:
- 导入函数所在包
- 直接调用
import "mymath"
c := mymath.Add(1,2)
和 Python 一样,利用函数的多重返回值和错误处理机制,Go 写出的代码也很优雅美观。
在可见性方面,对比 Python 中使用下划线来表达私有和公有的情况,Go 使用的是首字母的大小写做区分:
- 小写字母开头的函数私有
- 大写字母开头的函数共有
不定参数
不定参数涉及到 3 个方面:
- 不定参数类型
- 不定参数传递
- 任意类型的不定参数
从不定参数类型来讲:
func myfunc(args ...int) {
for _, arg := range args {
fmt.Println(arg)
}
}
args ... 表示函数接受不定数量的参数,这些参数的类型全部是 int 。
在调用时,可以如下:
myfunc(2, 3, 4)
myfunc(1, 3, 7, 13)
... type 这样的类型本质上是一个数组切片,使用中只能作为函数的最后一个参数的参数类型存在。
它带来的好处体现在调用时,避免了在调用时编写冗余的代码。
// 简化
myfunc2([]int{1, 3, 7, 13})
// 成为
myfunc(1, 3, 7, 13)
传递不定参数
func myfunc(args ...int) {
// 按照原样传递
myfunc3(args...)
// 传递片段,实际上任意的 int slice 都可以传禁区
myfunc3(args[1:]...)
}
描述完长度的限制,下面是类型的限制
func Printf(format string, args ...interface{}) {
}
Python 在不定参数方面规则少,使用优雅,Go 作为静态语言为了能够尽量跟上这种简练的用法,增加了 1 条规则:
- 使用 interface{} 关键字
package main
import "fmt"
func MyPrintf(args ...interface{}) {
for _, arg := range args {
switch arg.(type) {
case int:
fmt.Println(arg, "")
case string:
fmt.Println(arg, "")
case int64:
fmt.Println(arg, "is an int64 value")
default:
fmt.Println(arg, 'is an unkown type')
}
}
}
func main() {
var v1 int = 1
var v2 int64 = 234
var v3 string = "hello"
var v4 float32 = 1.234
MyPrintf(v1, v2, v3, v4)
}
Python 和 Go 都有多值返回。Go 中可以给返回值命名,它在函数开始的时候会被自动初始化为空。在函数中执行不带任何参数的 return 语句时,会返回对应的返回值变量的值。
n, _ := f.Read(buf)
Python 和 Go 中都存在匿名函数和闭包。
匿名函数的特点有 3 个:
- 无函数名
- 简短
- 直接执行
# 匿名函数
func(a, b int, z float64) bool {
return a*b < int(z)
}
f := func(x, y int) int {
return x + y
}
func(ch chan int) {
ch <- ACK
}(replay_chan) // 花括号后直接跟参数列表表示函数调用
闭包的特点有 2 个:
- 一级对象,可以存储到变量中作为参数传递给其他函数
- 动态创建和返回
适用的应用场景有 3 个:
在下面的例子中,变量 a 指向了闭包函数,函数中引用了局部变量 i 和 j。i 被隔离,在闭包外不能被修改,改变 j 的值以后,再次调用 a,j 的结果修改成功。
package main
import (
"fmt"
)
func main() {
var j int = 5
a := func()(func()) {//前一个 func() 定义匿名函数,后一个 func() 表示返回值是一个匿名函数
var i int = 10 // 只有内部的匿名函数才能访问 i,其他途径无法访问。
return func() { // 里层新建的匿名函数,但是它可以使用外层匿名函数中定义的变量,整体称为闭包
fmt.Printf("i, j: %d, %d\n", i, j)
}
}()
a()
j *= 2
a()
}