Skip to content

Latest commit

 

History

History
330 lines (244 loc) · 13.6 KB

06.ControlStructures.md

File metadata and controls

330 lines (244 loc) · 13.6 KB

Control structures

The control structures of Go are related to those of C but differ in important ways. There is no do or while loop, only a slightly generalized for; switch is more flexible; if and switch accept an optional initialization statement like that of for; break and continue statements take an optional label to identify what to break or continue; and there are new control structures including a type switch and a multiway communications multiplexer, select. The syntax is also slightly different: there are no parentheses and the bodies must always be brace-delimited.

Go 中的控制结构和 C 有一定的相关性,但在一些重要方面有所不同。Go 没有 do 或者 while 循环,只靠一个for走天下;switch则更加灵活;类似于forifswitch都接受一个可选的初始化语句;breakcontinue 可以跟一个可选标签来表示跳出和继续的对象;还有,Go 拥有新的控制结构,包扩 type switch和多路通信复用 select 。语法也有些许变化:没有圆括号、控制体必须要置于大括号之内。

If

In Go a simple if looks like this:

下面是 Go 中的一个简单的 if

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

强制使用大括号,以此鼓励编写多行简单的 if 语句。总之,这是一种良好的代码风格,尤其是 body 中包含诸如 return、break等控制结构的时候。

Since if and switch accept an initialization statement, it's common to see one used to set up a local variable.

正是因为 ifswitch都能使用初始化语句,所以经常会看到如下设置本地变量的用法。

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.

在 Go 的标准库中,你会发现,当一个 if 语句成为终结者的时候—意为:body 以 break,continue,goto,或return 结束,此时会省略已经没必要的else

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.

下面的示例涉及到一个非常普遍的情况,代码必须防范一连串的错误情况。如果语句成功则向下执行,遇到错误则处理错误,代码就具有高可读性。由于错误情况的处理往往以 return 语句结束,所以代码不需要else 语句。

f, err := os.Open(name)
if err != nil {
    return err
}
d, err := f.Stat()
if err != nil {
    f.Close()
    return err
}
codeUsing(f, d)

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,

题外话:上个示例展示了短变量声明 “:=” 的用法,即 os.Open 的调用声明,

f, err := os.Open(name)

This statement declares two variables, f and err. A few lines later, the call to f.Stat reads,

此句声明了两个变量,ferr,并在稍后调用了f.Stat,

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.

这句看起来像是声明了derr。注意看,虽然两个语句都出现了err,但这样做完全合法:第一个语句声明err,第二个语句仅仅重新赋值。意味着f.Stat调用使用之前声明的err变量,只是给予其一个新值。

In a := declaration a variable v may appear even if it has already been declared, provided:

:= 短变量声明中,变量v可以是已声明的,只需满足如下条件:

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

  • 已声明的 v 需和本次声明位于相同作用域(如果外层作用域已有 v 的声明,那么本次声明会创建一个新的变量)

  • 赋值需与 v 的类型一致

  • 至少要在本次声明中创建一个新的变量

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.

在长长的if-else链中使用唯一的 err 变得易如反掌,这种非比寻常的特性非常实用。你会经常看到这种用法。

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 中,函数参数和返回值的范围与函数体相同,即使它们在词法上位于函数体的大括号之外。

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.

Go 的 for 循环类似于 C 但又不同于 C。它统一了 for 和 while,并且没有 do-while 循环。for 循环有三种形式,只有其中一个需要分号。

// Like a C for
for init; condition; post { }

// Like a C while
for condition { }

// Like a C for(;;)
for { }

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

如需循环遍历 array,slice,string,or map,或者从一个 channel 读取数据,则可以使用range字句。

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:

在 range 时,若只需要第一项(the key or index),则省略第二项:

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
}

The blank identifier has many uses, as described in a later section.

空标识符有很多妙用,后面的章节会详细介绍。

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

对于字符串来讲,range 会替你做更多的工作—隐式地按 UTF-8 解码。错误的编码会消耗掉一个字节,并使用 U+FFFD 作为替代 rune。rune 是 Go 中的一个术语,它是一个内建类型,代表一个 Unicode 码点(详见 the language specification),循环

for pos, char := range "日本\x80語" { // \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 '語' 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 --).

最后,Go 没有逗号运算符,++ 和 -- 是语句而非表达式。因此,如果你想在 for 循环中对多个变量进行运算,那么你应该使用并行赋值(尽管这样没法使用 ++ 和 --)。

译注:C 语言中有逗号运算符,++ 和 -- 是表达式,因此可以这样写

for (int i = 0, j = len(a)-1; i < j; i++, j--) {
  //
}

注意下面 Go 语言 for 循环的条件 i, j = i+1, j-1,这是作者意之所在。

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

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.

Go 的 switch 在功能上要比 C 更加广泛。其表达式不拘于常量或者整数,分支自顶向下求值,直至遇到匹配项,而且如果 switch 没有表达式,则分支以 true 为条件进行求值。因此在 Go 中可以使用 switch 来代替 if-else-if-else 链,实际上使用 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.

Go 并不会自动 fallthrough 去执行下一个分支(译注:Go 中 switch 的 fallthrough 可以强制执行后面的case ),但是可以在 case 中使用逗号分隔多个匹配项。

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.

在 Go 中可以用 break 语句提前结束switch,尽管这在其它类 C 语言中没那么常见。然而有时候代码需要跳出外层循环而不是默认的 switch,这时就可以在循环的位置添加一个 label,并在 breake 时指定这个label 即可,下面的示例同时展示了这两种用法:

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.

当然,continue 语句也可以指定 label,但这仅仅在循环中适用。

To close this section, here's a comparison routine for byte slices that uses two switch statements:

我们用一个比较两个 slice 的例程来结束这一小节,这个例程使用了两个 switch:

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

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.

switch 还可以用来识别一个接口变量的动态类型。它使用类型断言的语法,将关键字 type 放在一对括号内。如果在表达式中声明了变量,那么这个变量就会拥有与每个 case 相对应的类型。这种情况下,重用变量名也是一种地道用法,事实上,虽然使用相同的名称声明新变量,但变量在每个 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
}