If
In Go a simple if looks like this:
if x > 0 {
return y
}
Mandatory braces encourage writing simple if statements on multiple lines. It's good style to do so anyway, especially when the body contains a control statement such as a return or break.
Since if and switch accept an initialization statement, it's common to see one used to set up a local variable.
if err := file.Chmod(0664); err != nil {
log.Print(err)
return err
}
In the Go libraries, you'll find that when an if statement doesn't flow into the next statement—that is, the body ends in break, continue, goto, or return—the unnecessary else is omitted.
f, err := os.Open(name)
if err != nil {
return err
}
codeUsing(f)
This is an example of a common situation where code must guard against a sequence of error conditions. The code reads well if the successful flow of control runs down the page, eliminating error cases as they arise. Since error cases tend to end in return statements, the resulting code needs no else statements.
f, err := os.Open(name)
if err != nil {
return err
}
d, err := f.Stat()
if err != nil {
f.Close()
return err
}
codeUsing(f, d)
這段文字主要討論了Go語(yǔ)言中if語(yǔ)句的使用和風(fēng)格。
1、簡(jiǎn)單的if語(yǔ)句:
在Go中,一個(gè)簡(jiǎn)單的if語(yǔ)句看起來(lái)如下所示:
if x > 0 {
return y
}
強(qiáng)制使用大括號(hào)鼓勵(lì)開發(fā)者將簡(jiǎn)單的if語(yǔ)句分為多行來(lái)編寫。尤其當(dāng)if語(yǔ)句的主體包含如return或break這樣的控制語(yǔ)句時(shí),多行編寫是一個(gè)好的風(fēng)格。
2、初始化語(yǔ)句:
由于if和switch都接受一個(gè)初始化語(yǔ)句,所以常??吹狡溆糜谠O(shè)置局部變量。
if err := file.Chmod(0664); err != nil {
log.Print(err)
return err
}
3、省略不必要的else:
在Go的庫(kù)中,你會(huì)發(fā)現(xiàn)當(dāng)一個(gè)if語(yǔ)句的主體并不流入到下一個(gè)語(yǔ)句時(shí)(即,主體以break、continue、goto或return結(jié)束),那么不必要的 else 會(huì)被省略。
f, err := os.Open(name)
if err != nil {
return err
}
codeUsing(f)
4、處理錯(cuò)誤情況:
這是一個(gè)常見的情況,代碼必須針對(duì)一系列的錯(cuò)誤情況進(jìn)行防護(hù)。如果成功的控制流順序地從上往下運(yùn)行,并隨著錯(cuò)誤情況的出現(xiàn)而消除它們,那么代碼的可讀性會(huì)更好。因?yàn)殄e(cuò)誤情況往往會(huì)以return語(yǔ)句結(jié)束,所以得到的代碼不需要else語(yǔ)句。
f, err := os.Open(name)
if err != nil {
return err
}
d, err := f.Stat()
if err != nil {
f.Close()
return err
}
codeUsing(f, d)
總之,這段話描述了在Go中使用if語(yǔ)句的慣用法和風(fēng)格,強(qiáng)調(diào)了代碼的清晰性和錯(cuò)誤處理的重要性。
Redeclaration and reassignment
An aside: The last example in the previous section demonstrates a detail of how the := short declaration form works. The declaration that calls os.Open reads,
f, err := os.Open(name)
This statement declares two variables, f and err. A few lines later, the call to f.Stat reads,
d, err := f.Stat()
which looks as if it declares d and err. Notice, though, that err appears in both statements. This duplication is legal: err is declared by the first statement, but only re-assigned in the second. This means that the call to f.Stat uses the existing err variable declared above, and just gives it a new value.
In a := declaration a variable v may appear even if it has already been declared, provided:
this declaration is in the same scope as the existing declaration of v (if v is already declared in an outer scope, the declaration will create a new variable §),
the corresponding value in the initialization is assignable to v, and
there is at least one other variable that is created by the declaration.
This unusual property is pure pragmatism, making it easy to use a single err value, for example, in a long if-else chain. You'll see it used often.
§ It's worth noting here that in Go the scope of function parameters and return values is the same as the function body, even though they appear lexically outside the braces that enclose the body.
這段話討論了Go語(yǔ)言中的變量重聲明和重新賦值的概念和規(guī)則。我將為您分點(diǎn)解釋:
1、重聲明與重新賦值:
文中給出了一個(gè)關(guān)于os.Open和f.Stat的示例,其中err變量在兩個(gè)語(yǔ)句中都出現(xiàn)了。這種做法在Go中是允許的。在第一個(gè)語(yǔ)句中,err被聲明,而在第二個(gè)語(yǔ)句中,err只是被重新賦值,而不是重新聲明。
f, err := os.Open(name) // 聲明 f 和 err
...
d, err := f.Stat() // 聲明 d, 但只是重新賦值 err
2、:=短聲明的詳細(xì)規(guī)則:
(1)在:=聲明中,即使變量v已經(jīng)被聲明過,它仍然可以再次出現(xiàn)在新的:=聲明中,但必須滿足以下條件:
- 這次聲明和已有的v聲明在相同的作用域內(nèi)。如果v在外層作用域已經(jīng)被聲明過,那么這次聲明會(huì)創(chuàng)建一個(gè)新的變量。
- 初始化中與v對(duì)應(yīng)的值可以賦給v。
- 聲明中至少有一個(gè)其它的變量是新創(chuàng)建的。
3、為何允許這種特性:
這種特性是基于實(shí)用性考慮的。特別是當(dāng)在長(zhǎng)的if-else鏈中使用一個(gè)單一的err值時(shí),這種特性很有用,可以簡(jiǎn)化代碼。您在實(shí)際的Go代碼中會(huì)經(jīng)??吹竭@種用法。
4、函數(shù)參數(shù)和返回值的作用域:
文章還指出,雖然函數(shù)參數(shù)和返回值從字面上看似乎在函數(shù)體的大括號(hào)外部,但它們的作用域?qū)嶋H上和函數(shù)體是相同的。
簡(jiǎn)而言之,這段話說明了Go中關(guān)于變量重聲明和重新賦值的規(guī)則,以及為什么這些規(guī)則存在,它們?nèi)绾魏?jiǎn)化代碼,并提醒讀者注意函數(shù)參數(shù)和返回值的作用域。
For
The Go for loop is similar to—but not the same as—C's. It unifies for and while and there is no do-while. There are three forms, only one of which has semicolons.
// Like a C for
for init; condition; post { }
// Like a C while
for condition { }
// Like a C for(;;)
for { }
hort declarations make it easy to declare the index variable right in the loop.
sum := 0
for i := 0; i < 10; i++ {
sum += i
}
If you're looping over an array, slice, string, or map, or reading from a channel, a range clause can manage the loop.
for key, value := range oldMap {
newMap[key] = value
}
If you only need the first item in the range (the key or index), drop the second:
for key := range m {
if key.expired() {
delete(m, key)
}
}
If you only need the second item in the range (the value), use the blank identifier, an underscore, to discard the first:
sum := 0
for _, value := range array {
sum += value
}
For strings, the range does more work for you, breaking out individual Unicode code points by parsing the UTF-8. Erroneous encodings consume one byte and produce the replacement rune U+FFFD. (The name (with associated builtin type) rune is Go terminology for a single Unicode code point. See the language specification for details.) The loop
for pos, char := range "日本\x80語(yǔ)" { // \x80 is an illegal UTF-8 encoding
fmt.Printf("character %#U starts at byte position %d\n", char, pos)
}
prints
character U+65E5 '日' starts at byte position 0
character U+672C '本' starts at byte position 3
character U+FFFD '?' starts at byte position 6
character U+8A9E '語(yǔ)' starts at byte position 7
Finally, Go has no comma operator and ++ and -- are statements not expressions. Thus if you want to run multiple variables in a for you should use parallel assignment (although that precludes ++ and --).
// Reverse a
for i, j := 0, len(a)-1; i < j; i, j = i+1, j-1 {
a[i], a[j] = a[j], a[i]
}
這段話主要解釋了Go中for循環(huán)的使用和與C語(yǔ)言中for循環(huán)的一些區(qū)別。下面我將逐點(diǎn)為您解釋。
1、Go的for循環(huán)基本形式:
(1)Go有三種for循環(huán)形式:
與C語(yǔ)言中的for很像,包含初始化、條件和后置語(yǔ)句。
for init; condition; post { }
類似于C語(yǔ)言中的while,只有一個(gè)條件。for condition { }
無(wú)限循環(huán),與C語(yǔ)言中的for(;;)類似。for { }
2、短變量聲明在循環(huán)中的應(yīng)用:
在循環(huán)中,您可以使用短變量聲明(:=)直接在循環(huán)中聲明索引變量。
sum := 0
for i := 0; i < 10; i++ {
sum += i
}
3、使用range遍歷:
- 當(dāng)您需要遍歷數(shù)組、切片、字符串、映射或從通道中讀取時(shí),可以使用range子句。
for key, value := range oldMap {
newMap[key] = value
}
- 如果只需要range的第一個(gè)項(xiàng)(即鍵或索引),則可以省略第二個(gè)。
for key := range m {
if key.expired() {
delete(m, key)
}
}
- 如果只需要range的第二個(gè)項(xiàng)(即值),則使用空白標(biāo)識(shí)符_來(lái)丟棄第一個(gè)。
sum := 0
for _, value := range array {
sum += value
}
4、range與字符串:
對(duì)于字符串,range會(huì)為您完成更多的工作,它會(huì)解析UTF-8字符并為您提取單個(gè)的Unicode碼點(diǎn)。如果有錯(cuò)誤的編碼,它會(huì)使用替代字符U+FFFD。
例如,字符串"日本\x80語(yǔ)"中的\x80是一個(gè)非法的UTF-8編碼,使用range遍歷這個(gè)字符串時(shí),該非法編碼會(huì)被替換為U+FFFD。
5、++和--在Go中的特性:
Go中沒有逗號(hào)運(yùn)算符,且++和--在Go中是語(yǔ)句而不是表達(dá)式。因此,如果您想在for循環(huán)中同時(shí)運(yùn)行多個(gè)變量,您應(yīng)該使用并行賦值。但是這種方式不支持++和--。
for i, j := 0, len(a)-1; i < j; i, j = i+1, j-1 {
a[i], a[j] = a[j], a[i]
}
總的來(lái)說,這段話詳細(xì)介紹了Go語(yǔ)言中for循環(huán)的各種用法和特性,并與C語(yǔ)言中的for循環(huán)進(jìn)行了比較。
Switch
Go's switch is more general than C's. The expressions need not be constants or even integers, the cases are evaluated top to bottom until a match is found, and if the switch has no expression it switches on true. It's therefore possible—and idiomatic—to write an if-else-if-else chain as a switch.
func unhex(c byte) byte {
switch {
case '0' <= c && c <= '9':
return c - '0'
case 'a' <= c && c <= 'f':
return c - 'a' + 10
case 'A' <= c && c <= 'F':
return c - 'A' + 10
}
return 0
}
There is no automatic fall through, but cases can be presented in comma-separated lists.
func shouldEscape(c byte) bool {
switch c {
case ' ', '?', '&', '=', '#', '+', '%':
return true
}
return false
}
Although they are not nearly as common in Go as some other C-like languages, break statements can be used to terminate a switch early. Sometimes, though, it's necessary to break out of a surrounding loop, not the switch, and in Go that can be accomplished by putting a label on the loop and "breaking" to that label. This example shows both uses.
Loop:
for n := 0; n < len(src); n += size {
switch {
case src[n] < sizeOne:
if validateOnly {
break
}
size = 1
update(src[n])
case src[n] < sizeTwo:
if n+1 >= len(src) {
err = errShortInput
break Loop
}
if validateOnly {
break
}
size = 2
update(src[n] + src[n+1]<<shift)
}
}
Of course, the continue statement also accepts an optional label but it applies only to loops.
To close this section, here's a comparison routine for byte slices that uses two switch statements:
// Compare returns an integer comparing the two byte slices,
// lexicographically.
// The result will be 0 if a == b, -1 if a < b, and +1 if a > b
func Compare(a, b []byte) int {
for i := 0; i < len(a) && i < len(b); i++ {
switch {
case a[i] > b[i]:
return 1
case a[i] < b[i]:
return -1
}
}
switch {
case len(a) > len(b):
return 1
case len(a) < len(b):
return -1
}
return 0
}
這段文本詳細(xì)描述了 Go 語(yǔ)言中 switch 語(yǔ)句的一些特性和用法。讓我們一步一步解釋:
1、更為通用的 switch:
Go 的 switch 語(yǔ)句比 C 語(yǔ)言更為通用。其表達(dá)式不必是常量或整數(shù)。
case 從上到下逐個(gè)評(píng)估,直到找到匹配的項(xiàng)。
如果 switch 沒有表達(dá)式,它會(huì)默認(rèn)為 true。這使得我們可以將 if-else-if-else 鏈條寫為一個(gè) switch 語(yǔ)句。
例如,unhex 函數(shù)根據(jù)輸入字節(jié)(代表十六進(jìn)制的字符)返回其十進(jìn)制值。
2、沒有自動(dòng)的 fallthrough:
func shouldEscape(c byte) bool {
switch c {
case ' ', '?', '&', '=', '#', '+', '%':
return true
}
return false
}
在大多數(shù)的 C-like 語(yǔ)言中,switch 的一個(gè) case 執(zhí)行完后會(huì)自動(dòng)執(zhí)行下一個(gè) case,除非使用 break 中斷。但在 Go 中不是這樣的。Go 執(zhí)行一個(gè)case則結(jié)束不繼續(xù)執(zhí)行。
然而,可以使用逗號(hào)分隔的列表來(lái)表示多個(gè)值。例如,shouldEscape 函數(shù)檢查一個(gè)字節(jié)是否應(yīng)該被轉(zhuǎn)義。
3、break 的使用:
Loop:
for n := 0; n < len(src); n += size {
switch {
case src[n] < sizeOne:
if validateOnly {
break
}
size = 1
update(src[n])
case src[n] < sizeTwo:
if n+1 >= len(src) {
err = errShortInput
break Loop
}
if validateOnly {
break
}
size = 2
update(src[n] + src[n+1]<<shift)
}
}
在 Go 中,可以使用 break 語(yǔ)句提前結(jié)束一個(gè) switch。
有時(shí),你可能需要跳出外部的循環(huán)而不是 switch。在 Go 中,這可以通過給循環(huán)添加一個(gè)標(biāo)簽,并 "break" 到那個(gè)標(biāo)簽來(lái)實(shí)現(xiàn)。
上面的例子中展示了兩種用法:在一個(gè) switch 內(nèi)部提前跳出和在一個(gè) switch 內(nèi)部跳出外部循環(huán)。
4、帶標(biāo)簽的 continue:
continue 語(yǔ)句也接受一個(gè)可選的標(biāo)簽,但它只適用于循環(huán)。
5、比較字節(jié)切片的例子:
func Compare(a, b []byte) int {
for i := 0; i < len(a) && i < len(b); i++ {
switch {
case a[i] > b[i]:
return 1
case a[i] < b[i]:
return -1
}
}
switch {
case len(a) > len(b):
return 1
case len(a) < len(b):
return -1
}
return 0
}
最后,文中給出了一個(gè) Compare 函數(shù),它用于比較兩個(gè)字節(jié)切片。
這個(gè)函數(shù)用兩個(gè) switch 語(yǔ)句進(jìn)行字節(jié)切片的比較,并根據(jù)詞典序返回 -1、0 或 1。
總之,文本展示了 Go 中 switch 語(yǔ)句的強(qiáng)大和靈活性,并通過實(shí)例說明了如何在實(shí)際情境中使用它。
Type switch
A switch can also be used to discover the dynamic type of an interface variable. Such a type switch uses the syntax of a type assertion with the keyword type inside the parentheses. If the switch declares a variable in the expression, the variable will have the corresponding type in each clause. It's also idiomatic to reuse the name in such cases, in effect declaring a new variable with the same name but a different type in each case.
var t interface{}
t = functionOfSomeType()
switch t := t.(type) {
default:
fmt.Printf("unexpected type %T\n", t) // %T prints whatever type t has
case bool:
fmt.Printf("boolean %t\n", t) // t has type bool
case int:
fmt.Printf("integer %d\n", t) // t has type int
case *bool:
fmt.Printf("pointer to boolean %t\n", *t) // t has type *bool
case *int:
fmt.Printf("pointer to integer %d\n", *t) // t has type *int
}
這段話描述了如何使用switch來(lái)確定接口變量的動(dòng)態(tài)類型,這種操作被稱為類型開關(guān)(type switch)。下面我為您逐點(diǎn)解釋。
1、類型開關(guān)的基本概念:
當(dāng)我們有一個(gè)接口類型的變量時(shí),這個(gè)變量可以持有很多其他類型的值。但有時(shí)我們可能想知道它當(dāng)前持有的值具體是什么類型。為了完成這一操作,Go提供了類型開關(guān)。
2、語(yǔ)法:
類型開關(guān)的語(yǔ)法與類型斷言相似,但在括號(hào)內(nèi)使用type關(guān)鍵字。
3、聲明變量:
如果switch在其表達(dá)式中聲明了變量,那么在每個(gè)case子句中,這個(gè)變量都將具有相應(yīng)的類型。習(xí)慣上,我們?cè)谶@種情況下重用變量名,實(shí)際上是在每個(gè)case中使用相同的名字聲明一個(gè)不同類型的新變量。
示例代碼解釋:
var t interface{}
t = functionOfSomeType()
switch t := t.(type) {
var t interface{}: 聲明一個(gè)名為t的空接口類型變量??战涌诳梢匀菁{任何類型的值。
t = functionOfSomeType(): 假設(shè)functionOfSomeType是某個(gè)返回各種類型值的函數(shù),我們將其結(jié)果賦給t。
switch t := t.(type): 這是一個(gè)類型開關(guān)的開始。它檢查t的動(dòng)態(tài)類型。
default:
fmt.Printf("unexpected type %T\n", t) // %T prints whatever type t has
default子句處理t不匹配任何其他case子句中的類型的情況。
case bool:
fmt.Printf("boolean %t\n", t) // t has type bool
這個(gè)case子句處理當(dāng)t是bool類型的情況。在這個(gè)子句內(nèi),變量t被認(rèn)為是bool類型。
其他case子句與此類似,每個(gè)case都處理t可能的一個(gè)類型,并在該case內(nèi)部,t被認(rèn)為是那個(gè)特定的類型。
這種類型開關(guān)特別有用,因?yàn)樗试S我們?cè)谕粋€(gè)switch語(yǔ)句中處理不同的類型,并為每種類型提供專門的處理邏輯。
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 passed by address.
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 the Write method on files from package os is:
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.
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.
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:
for i := 0; i < len(b); {
x, i = nextInt(b, i)
fmt.Println(x)
}
這部分描述了 Go 語(yǔ)言的函數(shù)中一項(xiàng)獨(dú)特的特性:可以返回多個(gè)值。以下是對(duì)每個(gè)部分的詳細(xì)解釋。
1、多個(gè)返回值:
Go 允許函數(shù)和方法返回多個(gè)值,這與其他許多編程語(yǔ)言不同。這個(gè)特性可以優(yōu)化某些 C 語(yǔ)言中笨拙的慣用法,例如使用 -1 表示 EOF 或通過地址修改傳入的參數(shù)。
在 C 語(yǔ)言中,寫入錯(cuò)誤是通過負(fù)的計(jì)數(shù)來(lái)表示的,并將錯(cuò)誤代碼保存在易失的位置。而在 Go 中,寫入操作可以返回寫入的字節(jié)數(shù)以及一個(gè)錯(cuò)誤。這意味著你可以得到“是的,你寫入了一些字節(jié),但并不是所有的字節(jié),因?yàn)槟闾顫M了設(shè)備”。
2、例子:
func (file *File) Write(b []byte) (n int, err error)
文檔中給出了 os 包中文件的 Write 方法的一個(gè)例子,該方法返回兩個(gè)值:寫入的字節(jié)數(shù)和一個(gè)錯(cuò)誤。如果寫入的字節(jié)數(shù)不等于輸入切片的長(zhǎng)度,那么將返回一個(gè)非零的錯(cuò)誤。這是一個(gè)常見的編碼風(fēng)格。
3、引用參數(shù)模擬:
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
}
for i := 0; i < len(b); {
x, i = nextInt(b, i)
fmt.Println(x)
}
在 C 語(yǔ)言中,你可能需要通過指針傳遞一個(gè)返回值,以模擬一個(gè)引用參數(shù)。而在 Go 中,你可以直接返回該值。文檔提供了一個(gè)函數(shù) nextInt,它從字節(jié)切片的某個(gè)位置提取一個(gè)數(shù)字,并返回該數(shù)字及其后的位置。這樣,你可以避免使用指針或修改原始切片。
4、如何使用:
文檔的最后部分給出了如何使用 nextInt 函數(shù)來(lái)掃描輸入切片 b 中的數(shù)字的示例。你可以在一個(gè)循環(huán)中調(diào)用 nextInt,并持續(xù)更新索引 i,直到你處理完整個(gè)切片。
總的來(lái)說,這部分強(qiáng)調(diào)了 Go 語(yǔ)言中函數(shù)可以返回多個(gè)值的優(yōu)勢(shì),并通過實(shí)際的代碼示例展示了如何使用這一特性。
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.
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.
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:
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
}
這段文字講述了 Go 語(yǔ)言中函數(shù)的返回結(jié)果參數(shù)可以被命名的特性,并且這些命名的結(jié)果參數(shù)可以像普通的變量一樣使用。以下是對(duì)這部分的詳細(xì)解釋:
1、命名的返回結(jié)果參數(shù):
在 Go 中,函數(shù)的返回值可以被命名,就像函數(shù)的輸入?yún)?shù)一樣。這些被命名的返回值在函數(shù)開始執(zhí)行時(shí)會(huì)被初始化為其類型的零值。如果函數(shù)執(zhí)行了一個(gè)沒有任何參數(shù)的 return 語(yǔ)句,那么這些命名的返回值參數(shù)的當(dāng)前值會(huì)被用作函數(shù)的返回值。
2、優(yōu)勢(shì):
這些命名并不是必須的,但它們可以使代碼更簡(jiǎn)短、更清晰。事實(shí)上,這些命名本身就像文檔,幫助讀者理解每個(gè)返回值的意義。例如,在 nextInt 函數(shù)中,通過命名返回值,我們可以很清楚地知道每個(gè)返回的整數(shù)是什么。
3、簡(jiǎn)化和明確:
由于命名的結(jié)果已經(jīng)初始化并與一個(gè)沒有裝飾的 return 綁定,因此它們可以使代碼更簡(jiǎn)單,也更易于理解。例如,ReadFull 函數(shù)的版本就很好地利用了這個(gè)特性。在這個(gè)函數(shù)中,n 和 err 是命名的返回值。在函數(shù)體內(nèi),不需要明確指定要返回的值,因?yàn)樵趫?zhí)行 return 時(shí),n 和 err 的當(dāng)前值會(huì)自動(dòng)被返回。
簡(jiǎn)而言之,命名的返回參數(shù)可以提高代碼的可讀性,并簡(jiǎn)化 return 語(yǔ)句的編寫,因?yàn)槟悴辉傩枰鞔_指定返回哪些值。
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.
// 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.
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.
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:
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:
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: b
in b
entering: a
in a
leaving: a
leaving: 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.
這段文本詳細(xì)介紹了 Go 語(yǔ)言中的 defer 語(yǔ)句,它的工作原理以及如何使用它。以下是對(duì)這部分內(nèi)容的詳細(xì)解釋:
基本概念:
defer: 在 Go 中, defer 語(yǔ)句被用于安排一個(gè)函數(shù)調(diào)用(被稱為延遲函數(shù))在包含 defer 的函數(shù)返回前立即執(zhí)行。
1、為何使用 defer:
defer 常用于處理必須在函數(shù)結(jié)束時(shí)執(zhí)行的操作,無(wú)論函數(shù)因哪個(gè)路徑返回。典型的例子是釋放資源,例如解鎖互斥鎖或關(guān)閉文件。
例子:Contents 函數(shù):
這個(gè)函數(shù)讀取一個(gè)文件的內(nèi)容并返回它。文件被打開后,使用 defer f.Close() 確保文件在函數(shù)返回時(shí)被關(guān)閉。
使用 defer 的好處是:1) 你永遠(yuǎn)不會(huì)忘記關(guān)閉文件;2) Close 函數(shù)的調(diào)用靠近 Open,使代碼更清晰。
2、defer 和參數(shù):
func TestDefer(t *testing.T) {
var i int = 100
defer func() {
fmt.Println(i)
}()
i = i + 1
return
}
func TestDefer1(t *testing.T) {
var i int = 100
defer func(i int) {
fmt.Println(i)
}(i)
i = i + 1
return
}
延遲函數(shù)的參數(shù)(包括接收者,如果函數(shù)是方法)在 defer 語(yǔ)句執(zhí)行時(shí)被求值,而不是在延遲函數(shù)調(diào)用時(shí)。當(dāng)你寫下一個(gè) defer 語(yǔ)句時(shí),該語(yǔ)句中的任何參數(shù)都會(huì)立即被計(jì)算和固定,不會(huì)等到實(shí)際延遲函數(shù)被調(diào)用時(shí)才計(jì)算。
延遲的函數(shù)按照 LIFO(后進(jìn)先出)的順序執(zhí)行。
跟蹤函數(shù)執(zhí)行:
3、通過使用 trace 和 untrace 函數(shù),可以跟蹤函數(shù)何時(shí)進(jìn)入和退出。另一個(gè)例子中,trace 和 un 通過返回值和參數(shù)進(jìn)行了優(yōu)化,以更明確地表示函數(shù)的進(jìn)入和退出。
總結(jié):
對(duì)于習(xí)慣于其他語(yǔ)言中基于塊級(jí)資源管理的程序員,defer 可能看起來(lái)有些奇怪。但其實(shí),defer 最有趣和強(qiáng)大的應(yīng)用恰恰是因?yàn)樗诤瘮?shù)而不是基于塊。
簡(jiǎn)而言之,defer 在 Go 中是一個(gè)強(qiáng)大的工具,用于確保在函數(shù)返回前執(zhí)行某些操作,特別是資源的釋放。這種機(jī)制提供了一個(gè)清晰、簡(jiǎn)潔的方法來(lái)處理資源管理和其他需要在函數(shù)退出時(shí)執(zhí)行的任務(wù)。