[Effective Go 中文翻译]函数篇

Posted 凌星An

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[Effective Go 中文翻译]函数篇相关的知识,希望对你有一定的参考价值。

Functions

Multiple return values 多个返回值

Go中的函数和方法最不寻常的特点就是可以返回多个变量。这种形式可以改善C编程中一些笨拙的编程习惯,返回错误-1表示EOF ,传递变量地址来修改变量。

在 C 语言中,写入错误由负数错误码表示,不易察觉。在 Go 中,Write 可以返回一个计数和一个错误:设备可能指写入了部分数据 。

os包内的Write 的方法签名为:

func (file *File) Write(b []byte) (n int, err error)

当写入的字节数n!=len(b) 时,会返回一个非nil的错误error ;这是一种非常常见的风格。

这种语法,避免了传递指针来模拟引用参数作为返回值的需要。 这里有一个简单的函数,它从字节切片中的某个位置抓取一个数字,返回该数字和下一个位置。

func nextInt(b []byte, i int) (int, int) 
    for ; i < len(b) && !isDigit(b[i]); i++ 
    
    x := 0
    for ; i < len(b) && isDigit(b[i]); i++ 
        x = x*10 + int(b[i]) - '0'
    
    return x, i

你可以这样来遍历slice中的数据
eg:

for i := 0; i < len(b); 
        x, i = nextInt(b, i)
        fmt.Println(x)
    

Named result parameters 命名返回值

Go中函数的返回值,可以进行命名,像常规变量和函数参数一样进行使用。函数执行的时候,命名的返回值会被初始化为对应类型的默认值。 当执行到return语句时,当前的命名返回值变量,会作为返回值返回。

这种命名不是强制的,但它可以使代码更加清晰。
上面的nextInt可以改写为:
eg:

func nextInt(b []byte, pos int) (value, nextPos int) 

命名返回值会被初始化,且与简单的return语句关联,这种风格是很简单而且易于理解的。io.ReadFull 中就使用了这种形式。

func ReadFull(r Reader, buf []byte) (n int, err error) 
    for len(buf) > 0 && err == nil 
        var nr int
        nr, err = r.Read(buf)
        n += nr
        buf = buf[nr:]
    
    return

Defer

Go中的defer语句 会安排 延迟函数 在函数执行return前进行调用。这是一种不常用但是有效的 解决函数任何情况下退出都要释放申请的资源的方式,
下面是释放锁或者关闭文件的一个经典案例。
eg:

// Contents returns the file's contents as a string.
func Contents(filename string) (string, error) 
    f, err := os.Open(filename)
    if err != nil 
        return "", err
    
    defer f.Close()  // f.Close will run when we're finished.

    var result []byte
    buf := make([]byte, 100)
    for 
        n, err := f.Read(buf[0:])
        result = append(result, buf[0:n]...) // append is discussed later.
        if err != nil 
            if err == io.EOF 
                break
            
            return "", err  // f will be closed if we return here.
        
    
    return string(result), nil // f will be closed if we return here.

像上面Close那样延迟调用有两个优势: 1.它确保你不会忘记关闭文件,这个错误在增加新的return 时是很容易出现的。 2.在open后使用close,可以是代码更加清晰。

延迟函数的参数 或者是方法的接收者 在执行defer的时候被确定,而不是在函数执行时确定。所以不要担心函数执行时,改变延迟函数的参数。同样,这也意味着一个defer调用可以执行多个延迟函数。

eg:

for i := 0; i < 5; i++ 
    defer fmt.Printf("%d ", i)

延迟函数是以LIFO(后进先出)的顺序执行的。所以上面的函数返回时打印4 3 2 1 0
演示:

package main

import "fmt"
func fun()
	fmt.Println("fun start")
   for i := 0; i < 5; i++ 
    defer fmt.Printf("%d ", i)
   
	fmt.Println("fun end")
	return 

func main() 
	fun()
	fmt.Println("main ...")

结果:

fun start
fun end
4 3 2 1 0 main …

一个更加合理的例子是trace函数的执行。我们编写了几个简单的路径
eg:

func trace(s string)    fmt.Println("entering:", s) 
func untrace(s string)  fmt.Println("leaving:", s) 

// Use them like this:
func a() 
    trace("a")
    defer untrace("a")
    // do something....

我们可以利用 执行defer语句时确定延迟函数参数 的特点 编写更好的代码。
eg:

func trace(s string) string 
    fmt.Println("entering:", s)
    return s


func un(s string) 
    fmt.Println("leaving:", s)


func a() 
    defer un(trace("a"))
    fmt.Println("in a")


func b() 
    defer un(trace("b"))
    fmt.Println("in b")
    a()


func main() 
    b()

结果:

entering: b
in b
entering: a
in a
leaving: a
leaving: b

对于一个习惯于其他语言模块化管理资源的程序员而言,defer看起来十分奇怪。但它确实是一种有趣且强大的应用 ,更加准确的描述它基于函数,而不是模块化。我们可以在使用panic和recover中,看到它的另一种可能。

以上是关于[Effective Go 中文翻译]函数篇的主要内容,如果未能解决你的问题,请参考以下文章

[Effective Go 中文翻译] Initialization篇

[Effective Go 中文翻译] 第一篇

[Effective JavaScript 笔记]第25条:使用bind方法提取具有确定接收者的方法

go语音之进阶篇方法面向过程和对象函数的区别

Effective Go(官方文档)笔记

Effective Go中文版(更新中)