[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篇