func
关键字和一个函数签名字面表示表示形式组成。一个函数签名由一个输入参数类型列表和一个输出结果类型列表组成。参数名称和结果名称可以出现函数签名的字面表示形式中,但是它们并不重要。
func
关键字可以出现在函数签名的字面形式中,也可以不出现。鉴于此,我们常常混淆使用函数类型(见下)和函数签名这两个概念。
func (a int, b string, c string) (x int, y int, z bool)
func (a int, b, c string) (x, y int, z bool)
func (x int, y, z string) (a, b int, c bool)
_
。上面的字面形式等价于:
func (_ int, _, _ string) (_, _ int, _ bool)
func (int, string, string) (int, int, bool) // 标准函数字面形式
func (a int, b string, c string) (int, int, bool)
func (x int, _ string, z string) (int, int, bool)
func (int, string, string) (x int, y int, z bool)
func (int, string, string) (a int, b int, _ bool)
()
括起来,即使此列表为空。如果一个函数类型一个结果列表为空,则它可以在函数类型的字面形式中被省略掉。当一个结果列表含有最多一个结果,则此结果列表的字面形式在它不包含结果名称的时候可以不用括号()
括起来。
// 这三个函数类型字面形式是等价的。
func () (x int)
func () (int)
func () int
// 这两个函数类型字面形式是等价的。
func (a int, b string) ()
func (a int, b string)
...
,以示这是一个变长参数。两个变长函数类型的例子:
func (values ...int64) (sum int64)
func (sep string, tokens ...string) string
nil
比较。(函数值将在本文最后一节介绍。)
func
关键字、一个函数名和一个函数签名字面形式组成。
func Double(n int) (result int)
// Sum返回所有输入实参的和。
func Sum(values ...int64) (sum int64) {
// values的类型为[]int64。
sum = 0
for _, v := range values {
sum += v
}
return
}
// Concat是一个低效的字符串拼接函数。
func Concat(sep string, tokens ...string) string {
// tokens的类型为[]string。
r := ""
for i, t := range tokens {
if i != 0 {
r += sep
}
r += t
}
return r
}
...T
,则此变长参数的类型实际为[]T
。
fmt
标准库包中的Print
、Println
和Printf
函数均为变长参数函数。它们的声明大致如下:
func Print(a ...interface{}) (n int, err error)
func Printf(format string, a ...interface{}) (n int, err error)
func Println(a ...interface{}) (n int, err error)
[]T
的变长形参:
[]T
的值(或者说此切片可以被隐式转换为类型[]T
)。此实参切片后必须跟随三个点...
。
T
的实参(或者说这些实参可以赋值给类型为T
的值)。这些实参将被添加入一个匿名的在运行时刻创建的类型为[]T
的切片中,然后此切片将被传递给此函数调用。
package main
import "fmt"
func Sum(values ...int64) (sum int64) {
sum = 0
for _, v := range values {
sum += v
}
return
}
func main() {
a0 := Sum()
a1 := Sum(2)
a3 := Sum(2, 3, 5)
// 上面三行和下面三行是等价的。
b0 := Sum([]int64{}...) // <=> Sum(nil...)
b1 := Sum([]int64{2}...)
b3 := Sum([]int64{2, 3, 5}...)
fmt.Println(a0, a1, a3) // 0 2 10
fmt.Println(b0, b1, b3) // 0 2 10
}
package main
import "fmt"
func Concat(sep string, tokens ...string) (r string) {
for i, t := range tokens {
if i != 0 {
r += sep
}
r += t
}
return
}
func main() {
tokens := []string{"Go", "C", "Rust"}
langsA := Concat(",", tokens...) // 风格1
langsB := Concat(",", "Go", "C","Rust") // 风格2
fmt.Println(langsA == langsB) // true
}
package main
// 这两个函数的声明见前面几例。
func Sum(values ...int64) (sum int64) {......}
func Concat(sep string, tokens ...string) string {......}
func main() {
// 下面两行报同样的错:实参数目太多了。
_ = Sum(2, []int64{3, 5}...)
_ = Concat(",", "Go", []string{"C", "Rust"}...)
}
func ()
的名称为init
的函数。
_
。这样声明的函数不可被调用。
unsafe
标准库包中的函数的调用都是在编译时刻估值的。另外,某些其它内置函数(比如len
和cap
等)的调用在所传实参满足一定的条件的时候也将在编译时刻估值。详见在编译时刻估值的函数调用。
.a
的文件中。一个使用Go汇编实现的函数依旧必须在一个*.go
文件中声明,但是它的声明必须不能含有函数体。换句话说,一个使用Go汇编实现的函数的声明中只含有它的原型。
return
语句只是其中一种。所以一个有返回值的函数的体内不一定需要一个return
语句。比如下面两个函数(它们均可编译通过):
func fa() int {
a:
goto a
}
func fb() bool {
for{}
}
recover
和copy
)的调用结果都是不可被舍弃的。调用结果不可被舍弃的函数是不可以被用做延迟调用函数和协程起始函数的,比如append
函数。
package main
func HalfAndNegative(n int) (int, int) {
return n/2, -n
}
func AddSub(a, b int) (int, int) {
return a+b, a-b
}
func Dummy(values ...int) {}
func main() {
// 这几行编译没问题。
AddSub(HalfAndNegative(6))
AddSub(AddSub(AddSub(7, 5)))
AddSub(AddSub(HalfAndNegative(6)))
Dummy(HalfAndNegative(6))
_, _ = AddSub(7, 5)
// 下面这几行编译不通过。
/*
_, _, _ = 6, AddSub(7, 5)
Dummy(AddSub(7, 5), 9)
Dummy(AddSub(7, 5), HalfAndNegative(6))
*/
}
nil
来表示。
init
函数不可被用做函数值。
package main
import "fmt"
func Double(n int) int {
return n + n
}
func Apply(n int, f func(int) int) int {
return f(n) // f的类型为"func(int) int"
}
func main() {
fmt.Printf("%T\n", Double) // func(int) int
// Double = nil // error: Double是不可修改的
var f func(n int) int // 默认值为nil
f = Double
g := Apply
fmt.Printf("%T\n", g) // func(int, func(int) int) int
fmt.Println(f(9)) // 18
fmt.Println(g(6, Double)) // 12
fmt.Println(Apply(6, f)) // 12
}
g(6, Double)
和Apply(6, f)
是等价的。
package main
import "fmt"
func main() {
// 此函数返回一个函数类型的结果,亦即闭包(closure)。
isMultipleOfX := func (x int) func(int) bool {
return func(n int) bool {
return n%x == 0
}
}
var isMultipleOf3 = isMultipleOfX(3)
var isMultipleOf5 = isMultipleOfX(5)
fmt.Println(isMultipleOf3(6)) // true
fmt.Println(isMultipleOf3(8)) // false
fmt.Println(isMultipleOf5(10)) // true
fmt.Println(isMultipleOf5(12)) // false
isMultipleOf15 := func(n int) bool {
return isMultipleOf3(n) && isMultipleOf5(n)
}
fmt.Println(isMultipleOf15(32)) // false
fmt.Println(isMultipleOf15(60)) // true
}
for-range
循环来遍历。
// K和V是特定类型
func(yield func() bool)
func(yield func(V) bool)
func(yield func(K, V) bool)
for-range
循环遍历这样的一个遍历器函数值时,这个遍历器函数值将被调用(一次)并被传入一个隐式创建的yield
回调函数。此yield
回调函数返回一个bool
结果。当它返回false
时,这个遍历器函数的调用应该(但并不强求一定)立即退出;否则(当此yield
回调函数返回true
时),这个遍历器函数应该继续执行,直到自然退出。
package main
import "fmt"
func Loop3(yield func() bool) {
for range 3 {
if (!yield()) {
return
}
}
}
func OneDigitNumbers(onValue func(int) bool) {
for i := range 10 {
if (!onValue(i)) {
return
}
}
}
func SquareLessThan50(onKeyValue func(int, int) bool) {
for i := range 8 {
if (!onKeyValue(i, i*i)) {
return
}
}
}
func main() {
var n = 0
for range Loop3 {
fmt.Print(n)
n++
}
fmt.Println()
// 输出:012
for i := range OneDigitNumbers {
fmt.Print(i)
}
fmt.Println()
// 输出:0123456789
for i, ii := range SquareLessThan50 {
fmt.Printf("%v:%v ", i, ii)
}
fmt.Println()
// 输出:0:0 1:1 2:4 3:9 4:16 5:25 6:36 7:49
}
for-range
循环和下面这些相应的函数调用是等价的:
func main() {
var n = 0
Loop3(func() bool {
fmt.Print(n)
n++
return true
})
fmt.Println()
OneDigitNumbers(func(i int) bool {
fmt.Print(i)
return true
})
fmt.Println()
SquareLessThan50(func(i, ii int) bool {
fmt.Printf("%v:%v ", i, ii)
return true
})
fmt.Println()
}
Go101.org网站内容包括Go编程各种相关知识(比如Go基础、Go优化、Go细节、Go实战、Go测验、Go工具等)。后续将不断有新的内容加入。敬请收藏关注期待。
本丛书微信公众号(联系方式一)名称为"Go 101"。二维码在网站首页。此公众号将时不时地发表一些Go语言相关的原创短文。各位如果感兴趣,可以搜索关注一下。
《Go语言101》系列丛书项目目前托管在Github上(联系方式二)。欢迎各位在此项目中通过提交bug和PR的方式来改进完善《Go语言101》丛书中的各篇文章。我们可以在项目目录下运行go run .
来浏览和确认各种改动。
本书的twitter帐号为@Golang_101(联系方式三)。玩推的Go友可以适当关注。
你或许对本书作者老貘开发的一些App感兴趣。
sync
标准库包sync/atomic
标准库包