[翻譯] effective go 之 Functions

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。

Functions

Multiple return values 返回多個值

One of Go's unusual features is that functions and methods can return multiple values. This form can be used to improve on a couple of clumsy idioms in C programs: in-band error returns (such as -1 for EOF) and modifying an argument.

Go的函數和方法可以同時返回多個值 這個特性可以改善C程式中的不太理想的多值返回形式 -- 參數傳入指標 通過這個指標來修改參數 另外再返回一個值 比如-1 或者EOF

In C, a write error is signaled by a negative count with the error code secreted away in a volatile location. In Go, Write can return a count and an error: “Yes, you wrote some bytes but not all of them because you filled the device”. The signature of File.Write in package os is:

舉個例子吧 C中的一個寫檔案的函數如下, 如果還未寫就被其它的中斷給打斷了 會返回EINTR 如果已經寫入了一部分資料 然後出現了錯誤 那麼就返回已經寫入的位元組數 你需要加一個條件判斷 來確實是否產生了錯誤

 #include <unistd.h> ssize_t write(int fd, const void *buf, size_t count);


但是Go可以直接返回已寫入的位元組數 以及相應的錯誤資訊:
func (file *File) Write(b []byte) (n int, err error)

and as the documentation says, it returns the number of bytes written and a non-nil error when n != len(b). This is a common style; see the section on error handling for more examples.

就像文檔中所描述的那樣 這個函數返回寫入的位元組數 如果寫入的位元組數和b不一致 err中就是相應的錯誤資訊


A similar approach obviates the need to pass a pointer to a return value to simulate a reference parameter. Here's a simple-minded function to grab a number from a position in a byte slice, returning the number and the next position.

類似的還有一個方法 不需要傳入指標來類比參數引用 下面這個函數從byte slice中取某個位置上的資料 並且返回改資料 和 它後繼的位置

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}

You could use it to scan the numbers in an input slice b like this:

可以使用上面的函數來遍曆slice:

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


Named result parameters 

The return or result "parameters" of a Go function can be given names and used as regular variables, just like the incoming parameters. When named, they are initialized to the zero values for their types when the function begins; if the function executes a return statement with no arguments, the current values of the result parameters are used as the returned values.

Go可以給傳回值定義名字 和輸入參數一樣使用 定義命名傳回值時 這些傳回值變數會初始化成相應類型的零值 如果函數的return語句沒有帶參數 命令傳回值變數的當前值會被當做傳回值返回

The names are not mandatory but they can make code shorter and clearer: they're documentation. If we name the results of nextInt it becomes obvious which returned int is which.

當然 我們可以不使用命名傳回值 不過使用命名傳回值 代碼會更加簡潔 如下面這個函式宣告 我們可以很明確地知道value 和 nextPost這兩個傳回值所代表的意思

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


Because named results are initialized and tied to an unadorned return, they can simplify as well as clarify. Here's a version of io.ReadFull that uses them well:

由於命名傳回值變數會自動被初始化 並且在return沒有帶參數的情況下 會以當前值返回 使用起來簡單明了 例如下面這個例子:

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's defer statement schedules a function call (the deferred function) to be run immediately before the function executing the defer returns. It's an unusual but effective way to deal with situations such as resources that must be released regardless of which path a function takes to return. The canonical examples are unlocking a mutex or closing a file.

Go的defer語句 會在函數返回前被調用 它很詭異 但是在某些場合下非常有效 比如 不管函數是怎麼樣執行的 它的執行路徑如何 在函數中擷取的資源 必須要被釋放 這時就可以加一個defer語句 這裡有一個經典的例子 釋放鎖 或者關閉檔案

// 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.}


Deferring a call to a function such as Close has two advantages. First, it guarantees that you will never forget to close the file, a mistake that's easy to make if you later edit the function to add a new return path. Second, it means that the close sits near the open, which is much clearer than placing it at the end of the function.

把例如像Close這樣的函數暫緩執行 可以帶來兩個好處 首先它保證你不會忘記關閉這個檔案 其次 你可以在開啟檔案後就立即使用defer來關閉檔案 相比於在函數結尾處關閉檔案 這樣的方式更加明了

The arguments to the deferred function (which include the receiver if the function is a method) are evaluated when the defer executes, not when the call executes. Besides avoiding worries about variables changing values as the function executes, this means that a single deferred call site can defer multiple function executions. Here's a silly example.

傳遞給defer函數的參數會在defer執行時對其進行計算(如果被defer的函數接受的是另一個函數的傳回值 那麼這個內層的函數也會在這個時候被執行) 而不是在那個被defer的函數執行的時候才計算出來 這樣既可以不必擔心傳遞給defer的函數的參數在過程中被改掉 也可以對多個函數使用defer:

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

Deferred functions are executed in LIFO order, so this code will cause 4 3 2 1 0 to be printed when the function returns. A more plausible example is a simple way to trace function execution through the program. We could write a couple of simple tracing routines like this:

被defer的函數的執行順序是後進先出 所以上面代碼啟動並執行結果就是 4 3 2 1 0 在看下面這個例子 使用trace函數來跟蹤函數的執行過程:

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....}

We can do better by exploiting the fact that arguments to deferred functions are evaluated when the defer executes. The tracing routine can set up the argument to the untracing routine. This example:

更進一步 我們可以利用defer函數的參數在defer執行的時候被初始化的特定來最佳化代碼 :

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()}

prints 程式運行結果:

entering: bin bentering: ain aleaving: aleaving: b


For programmers accustomed to block-level resource management from other languages, defer may seem peculiar, but its most interesting and powerful applications come precisely from the fact that it's not block-based but function-based. In the section on panic and recover we'll see another example of its possibilities.

對於習慣了其它語言中 比如python中的with方式的資源管理方法的程式員來說 defer看起來有點奇葩 但是他的魅力之處就在其是基於函數的 而不是基於塊 在後續的panic和recover章節中 我們還將看到更多defer的身影

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.