go语言中函数与方法介绍


本文摘自php中文网,作者尚,侵删。

如果你遇到没有函数体的函数声明,表示该函数不是以Go实现的。

1

2

package math

func Sin(x float64) float //implemented in assembly language

如果为函数的每一个返回值都设置变量名,则会以相应的零值初始化,且在该函数的return语句中省略操作数,这种用法称之为 bare return。

go中的错误处理,习惯上是先进行一系列的初始化检查,将处理失败逻辑的代码先行处理,然后才是函数的实际逻辑,这样使得代码更简洁,避免过多的层级结构。

函数定义时,可以使用函数类型作为参数,也可以作为返回类型,是不是有点类似委托,从而实现闭包。此外还有匿名函数,是不是类似于lambda表达式。strings.Map 函数可以拿来试验。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

func squares() func() int {

    var x int

    return func() int {

        x++

        return x * x

    }

}

func main() {

    f := squares()

    fmt.Println(f()) // "1"

    fmt.Println(f()) // "4"

    fmt.Println(f()) // "9"

    fmt.Println(f()) // "16"

}

匿名函数和squares中,存在变量引用。这就是函数值属于引用类型和函数值不可比较的原因。Go使用闭包(closures)技术实现函数值,Go程序员也把函数值叫做闭包。

注意golang圣经中匿名函数一节中的例子程序。

go语言的可变参函数非常好用,你可以传递多个同类型参数,也可以直接传入一个该类型的切片(注意传入切片时要使用...标记,我想应该是为了同切片参数区分吧,毕竟两者还是有些不同的),如果想要使用不同类型的变参,那么使用万能的 interfac{} ,函数体内像解析切片一样解析这个变参就好了。

直到包含该defer语句的函数执行完毕时,defer后的函数才会被执行,不论包含defer语句的函数是通过return正常结束,还是由于panic导致的异常结束。你可以在一个函数中执行多条defer语句,它们的执行顺序与声明顺序相反

1

2

3

4

5

6

7

var mu sync.Mutex

var m = make(map[string]int)

func lookup(key string) int {

    mu.Lock()

    defer mu.Unlock()

    return m[key]

}

调试复杂程序时,defer机制也常被用于记录何时进入和退出函数。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

func bigSlowOperation() {

    defer trace("bigSlowOperation")() // don't forget the

    extra parentheses

    // ...lots of work…

    time.Sleep(10 * time.Second) // simulate slow

    operation by sleeping

}

func trace(msg string) func() {

    start := time.Now()

    log.Printf("enter %s", msg)

    return func() {

        log.Printf("exit %s (%s)", msg,time.Since(start))

    }

}

我们只需要首先命名double的返回值,再增加defer语句,我们就可以在double每次被调用时,输出参数以及返回值。

1

2

3

4

5

6

7

func double(x int) (result int) {

    defer func() { fmt.Printf("double(%d) = %d\n", x,result) }()

    return x + x

}

_ = double(4)

// Output:

// "double(4) = 8"

为了方便诊断问题,runtime包允许程序员输出堆栈信息。在下面的例子中,我们通过在main函数中延迟调用printStack输出堆栈信息。

1

2

3

4

5

6

7

8

9

10

gopl.io/ch5/defer2

func main() {

    defer printStack()

    f(3)

}

func printStack() {

    var buf [4096]byte

    n := runtime.Stack(buf[:], false)

    os.Stdout.Write(buf[:n])

}

不能为一个结构体定义同名的字段名和方法名,有点奇怪。

函数指针:go里其实也是有函数指针的,下面用go语言实现表驱动模式。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

package main

 

import (

    "fmt"

)

 

func add(a int, b int) int {

    return a + b

}

 

func sub(a int, b int) int {

    return a - b

}

 

func main() {

    fm := make(map[int]func(int, int) int)

    fm[1001] = add

    fm[1002] = sub

    protocol := 2001

    i := 1

    j := 2

    if func_handle, ok := fm[protocol]; ok {

        println(func_handle(i, j))

    } else {

        fmt.Printf("protocol: %d not register!", protocol)

    }  

}

返回局部变量指针:

不同于 C 语言,GO 的函数可以返回局部变化指针,且编译器会通过逃逸分析(escape analysis)来决定是否在堆上分配内存。

编译时可以通过 -gcflags "-l -m" 参数来禁用函数内联,函数内联会对内存分配有一些影响,具体不清楚。

函数参数没有所谓的引用传递,都是值传递的,区别只是传递的是拷贝对象还是指针而已。在 C 语言中,一般推荐传递指针参数来避免复制对象提升效率。

但在 go 中,被复制的指针会延长目标对象的生命周期,还可能导致它被分配到堆上,则性能消耗要加上堆内存分配和垃圾回收的成本,而在栈上复制小对象其实非常快,所以如果不是特别大的对象或确实需要修改原对象,一般不需要传指针参数。在并发编程中,也提倡使用不可变对象(只读或复制),可以消除数据同步的麻烦。

如下就会在堆上分配内存,编译时通过 -gcflags "-m" 可查看汇编代码:

1

2

3

4

5

6

7

8

9

10

11

func test(p *int) {

    go func() {

        println(p)

    }()

}

 

func main() {

    x := 100

    p := &x

    test(p)

}

使用传出参数,推荐使用返回值,也可以使用二级指针:

1

2

3

4

5

6

7

8

9

10

func test(p **int) {

    x := 100

    *p = &x

}

 

func main() {

    var p *int

    test(&p)

    println(*p)

}

更多go语言知识请关注PHP中文网go语言教程栏目。

以上就是go语言中函数与方法介绍的详细内容,更多文章请关注木庄网络博客!!

相关阅读 >>

Go语言int64如何转string

go - 函数

Go语言指针数组

Go语言 类型转换表示什么

Go语言中如何导入包

Go语言最适合做什么

Go语言中函数与方法介绍

Go语言中包导入的一些问题

第一个 go 程序

简介

更多相关阅读请进入《Go语言》频道 >>




打赏

取消

感谢您的支持,我会继续努力的!

扫码支持
扫码打赏,您说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦

分享从这里开始,精彩与您同在

评论

管理员已关闭评论功能...