不支持嵌套(nested)、重载(overload)和默认参数(default parameter)。
func
定义函数,左大括号需要在行末,不能另起一行。unc test(x, y int, s string) (int, string) { // 类型相同的相邻参数可合并。
n := x + y // 多返回值必须用括号。
return n, fmt.Sprintf(s, n)
}
func test(fn func() int) int {
return fn()
}
type FormatFunc func(s string, x, y int) string // 定义函数类型。
func format(fn FormatFunc, s string, x, y int) string {
return fn(s, x, y)
}
func main() {
s1 := test(func() int { return 100 }) // 直接将匿名函数当参数。
s2 := format(func(s string, x, y int) string {
return fmt.Sprintf(s, x, y)
}, "%d, %d", 10, 20)
println(s1, s2)
}
有返回值的函数,必须有明确的终止语句,否则会引发编译错误。
变参本质上就是slice。只能有一个,且必须是最后一个。
func test(s string, n ...int) string {
var x int
for _, i := range n {
x += i
}
return fmt.Sprintf(s, x)
}
func main() {
println(test("sum: %d", 1, 2, 3))
}
使用slice做变参时,必须展开。
func main(){
s := []int{1,2,3}
println(test("sum: %d",s...))
}
结果输出:
sum: 6
不能用容器对象接受多返回值。只能用多个变量,或用_
忽略。
func test() (int, int) {
return 1, 2
}
func main() {
// s := make([]int, 2)
// s = test() // Error: multiple-value test() in single-value context
x, _ := test()
println(x)
}
多返回值可以直接作为其他函数调用的实参。
func test() (int, int) {
return 1, 2
}
func add(x, y int) int {
return x + y
}
func sum(n ...int) int {
var x int
for _, i := range n {
x += i
}
return x
}
func main() {
println(add(test()))
println(sum(test()))
}
func add(x, y int) (z int) {
z = x + y
return
}
func add(x, y int) (z int) {
{ // 不能在一个级别,引发 "z redeclared in this block" 错误。
var z = x + y
// return // Error: z is shadowed during return
return z // 必须显式返回。
}
}
func add(x, y int) (z int) {
defer func() {
z += 100
}()
z = x + y
return
}
func main() {
println(add(1, 2)) // 输出: 103
}
func add(x, y int) (z int) {
defer func() {
println(z) // 输出: 203
}()
z = x + y
return z + 200 // 执行顺序: (z = z + 200) -> (call defer) -> (ret)
}
func main() {
println(add(1, 2)) // 输出: 203
}
再次说明其顺序为: (z = z + 200) -> (call defer) -> (ret)
匿名函数可赋值给变量,作为结构字段,或者在channel里传送。
// --- function variable ---
fn := func() { println("Hello, World!") }
fn()
// --- function collection ---
fns := [](func(x int) int){
func(x int) int { return x + 1 },
func(x int) int { return x + 2 },
}
println(fns[0](100))
// --- function as field ---
d := struct {
fn func() string
}{
fn: func() string { return "Hello, World!" },
}
println(d.fn())
// --- channel of function ---
fc := make(chan func() string, 2)
fc <- func() string { return "Hello, World!" }
println((<-fc)()
package main
import "fmt"
func test() func() {
x := 100
fmt.Printf("x (%p) = %d\n", &x, x)
return func() {
fmt.Printf("x (%p) = %d\n", &x, x)
}
}
func main() {
f := test()
f()
}
输出结果:
x (0x115720cc) = 100
x (0x115720cc) = 100
在汇编层面,test实际返回的是FuncVal对象,其中包含了匿名函数地址、闭包对象指针。当调用匿名函数时,只需以某个寄存器传递该对象即可。
func test() error {
f, err := os.Create("test.txt")
if err != nil { return err }
defer f.Close() // 注册调用,而不是注册函数。必须提供参数,哪怕为空。
f.WriteString("Hello, World!")
return nil
}
package main
func test(x int) {
defer println("a")
defer println("b")
defer func() {
println(100 / x) // div0 异常未被捕获,逐步往外传递,最终终止进程。
}()
defer println("c")
}
func main() {
test(0)
}
输出结果:
c
b
a
panic: runtime error: integer divide by zero
注意: 在异常处并未停止,依然向外传递调用
package main
func test() {
x, y := 10, 20
defer func(i int) {
println("defer:", i, y) // y 闭包引用
}(x) // x 被复制
x += 10
y += 100
println("x =", x, "y =", y)
}
func main() {
test()
}
输出结果:
x = 20 y = 120
defer: 10 120
注意:x 被复制,指的是在push的时候复制了x值:10
var lock sync.Mutex
func test() {
lock.Lock()
lock.Unlock()
}
func testdefer() {
lock.Lock()
defer lock.Unlock()
}
func BenchmarkTest(b *testing.B) {
for i := 0; i < b.N; i++ {
test()
}
}
func BenchmarkTestDefer(b *testing.B) {
for i := 0; i < b.N; i++ {
testdefer()
}
}
输出结果:
BenchmarkTest" 50000000 43 ns/op
BenchmarkTestDefer 20000000 128 ns/op
func test(){
defer func() {
if err := recover(); err != nil {
println(err.(string)) // 将 interface{} 转型为具体类型。
}
}()
panic("panic error!")
}
由于panic、recover参数类型为interface{},因此可抛出任何类型对象。
func panic(v interface{})
func recover() interface{}
package main
import "fmt"
func test() {
defer func() {
fmt.Println(recover())
}()
defer func() {
panic("defer panic")
}()
panic("test panic")
}
func main() {
test()
}
输出结果:
defer panic
package main
import "fmt"
func test() {
defer recover() // 无效!
defer fmt.Println(recover()) // 无效!
defer func() {
func() {
println("defer inner")
recover() // ⽆效!
}()
}()
panic("test panic")
}
func main() {
test()
}
输出结果:
defer inner
<nil>
panic: test panic
func except() {
recover()
}
func test() {
defer except()
panic("test panic")
}
func test(x, y int) {
var z int
func() {
defer func() {
if recover() != nil { z = 0 }
}()
z = x / y
return
}()
println("x / y =", z)
}
type error interface {
Error() string
}
var ErrDivByZero = errors.New("division by zero")
func div(x, y int) (int, error) {
if y == 0 {
return 0, ErrDivByZero
}
return x / y, nil
}
func main() {
switch z, err := div(10, 0); err {
case nil:
println(z)
case ErrDivByZero:
panic(err)
}
}
Go没有像Java那样的异常机制,它不能抛出异常,而是使用了panic
和recover
机制。一定要记住,你应当把它作为最后的手段来使用,也就是说,你的代码中应当没有,或者很少有panic
的东西。这是个强大的工具,请明智地使用它。那么,我们应该如何使用它呢?
Panic
是一个内建函数,可以中断原有的控制流程,进入一个令人恐慌的流程中。当函数
F
调用panic
,函数F的执行被中断,但是F
中的延迟函数会正常执行,然后F返回到调用它的地方。在调用的地方,F
的行为就像调用了panic
。这一过程继续向上,直到发生panic
的goroutine
中所有调用的函数返回,此时程序退出。恐慌可以直接调用panic
产生。也可以由运行时错误产生,例如访问越界的数组。
Recover
是一个内建的函数,可以让进入令人恐慌的流程中的
goroutine
恢复过来。recover
仅在延迟函数中有效。在正常的执行过程中,调用recover
会返回nil
,并且没有其它任何效果。如果当前的goroutine
陷入恐慌,调用recover
可以捕获到panic
的输入值,并且恢复正常的执行。
main
函数和init
函数 Go里面有两个保留的函数:init
函数(能够应用于所有的package
)和main
函数(只能应用于package main
)。这两个函数在定义时不能有任何的参数和返回值。虽然一个package
里面可以写任意多个init
函数,但这无论是对于可读性还是以后的可维护性来说,我们都强烈建议用户在一个package
中每个文件只写一个init
函数。
Go程序会自动调用init()
和main()
,所以你不需要在任何地方调用这两个函数。每个package
中的init
函数都是可选的,但package main
就必须包含一个main
函数。
程序的初始化和执行都起始于main
包。如果main
包还导入了其它的包,那么就会在编译时将它们依次导入。有时一个包会被多个包同时导入,那么它只会被导入一次(例如很多包可能都会用到fmt
包,但它只会被导入一次,因为没有必要导入多次)。当一个包被导入时,如果该包还导入了其它的包,那么会先将其它包导入进来,然后再对这些包中的包级常量和变量进行初始化,接着执行init
函数(如果有的话),依次类推。等所有被导入的包都加载完毕了,就会开始对main
包中的包级常量和变量进行初始化,然后执行main
包中的init
函数(如果存在的话),最后执行main
函数。
只要一个函数的参数声明列表和结果声明列表中的数据类型的顺序和名称与某一个函数类型完全一致,前者就是后者的一个实现。请大家回顾上面的示例并深刻理解这句话。
var splice func(string, string) string // 等价于 var splice MyFunc
splice = myFunc
如此一来,我们就可以在这个变量之上实施调用动作了:
splice("1", "2")
实际上,这是一个调用表达式。它由代表函数的标识符(这里是splice)以及代表调用动作的、由圆括号包裹的参数值列表组成。
如果你觉得上面对splice变量声明和赋值有些啰嗦,那么可以这样来简化它:
var splice = func(part1 string, part2 string) string {
return part1 + part2
}
在这个示例中,我们直接使用了一个匿名函数来初始化splice变量。顾名思义,匿名函数就是不带名称的函数值。匿名函数直接由函数类型字面量和由花括号包裹的语句列表组成。注意,这里的函数类型字面量中的参数名称是不能被忽略的。
其实,我们还可以进一步简化——索性省去splice变量。既然我们可以在代表函数的变量上实施调用表达式,那么在匿名函数上肯定也是可行的。因为它们的本质是相同的。后者的示例如下:
var result = func(part1 string, part2 string) string {
return part1 + part2
}("1", "2")
可以看到,在这个匿名函数之后的即是代表调用动作的参数值列表。注意,这里的result变量的类型不是函数类型,而与后面的匿名函数的结果类型是相同的。
可以看到,Go在函数方面较C语言有很多有趣也很用的特性。例如多返回值,延迟函数等。灵活熟练使用这些特性,在开发过程中会带来很多便利。
- Go 学习笔记(雨痕)
- Go Web 编程
- Go 语言第一课
- Go 官网教程 Tour