写点什么

翻译: Effective Go (3)

用户头像
申屠鹏会
关注
发布于: 2020 年 04 月 30 日

控制结构



Go的控制结构和C类似,但还是有很多重点不一样。Go没有do或者while循环,只有一个更通用的for循环;switch相比也更灵活;if和switch可以像for循环一样,接受一个初始化语句;break和continue语句带有一个可选标签标志需要break或者continue的内容;新增了一个新的控制结构,包括一个类型选择和多路通信复用器——select;控制结构的语法相比也有细微的差别:没有小括号,并且主体必须用大括号分隔。



If



在Go中,一个简单的if应是这样:



if x > 0 {
return y
}




强制使用大括号鼓励将简单的if语句分成多行编写,这是一个很好的风格,尤其当主体语句中包括一个控制语句(例如return或break)。

由于if和switch接受一个初始化语句,因此通常可以用来声明局部变量。



if err := file.Chmod(0664); err != nil {
log.Print(err)
return err
}




在Go的库中,你会发现if语句当不会执行下一条语句时——即if主体结束于break,continue,goto或return时,将会省略不必要的else。



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




上面的就是一个常见情况的示例,这种情况下,代码必须防范一系列错误情况,如果控制流继续运行到底,说明错误已经被全部处理。由于错误的情况通常以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)




重复声明和重新赋值



上面的示例演示了 := 短变量声明的一些细节。调用os.Open的声明为:

f, err := os.Open(name)

这个语句声明了两个变量,f和err。几行后,f.Stat的调用:

d, err := f.Stat()

似乎又声明了d和err两个变量。但是请注意,虽然err出现在了两个地方,但是这样的重复是合法的:err在第一个语句中声明,第二个语句只是重新赋值而已。也就是说,调用f.Stat只是使用了先前声明的,已存在的err变量,重新进行了赋值。

在 := 声明中,可以出现已经声明的变量v,条件是:



  • 本声明和v的声明在同一作用域。(如果v在另一个作用域声明,那么本次声明会创建一个新变量。注)

  • 两次声明的类型相同

  • 本次的声明至少创建了一个新的变量



这个特性纯粹是实用主义,你会发现在if-else长链,经常看见只要简单的使用一个err变量。



注:值得注意的是,Go的函数入参、返回值的作用域和函数体相同,即使它们按词法出现在包围主体的括号之外。



For



Go的for循环和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 { }




短变量声明让索引变量声明变得轻松:



sum := 0
for i := 0; i < 10; i++ {
sum += i
}




如果要循环数组(array),切片(slice),字符串(string),映射(map),或读取一个管道(channel),range可以更高效:



for key, value := range oldMap {
newMap[key] = value
}




如果在range中只需要第一项,键(key)或者索引(index),只需要删除第二项:



for key := range m {
if key.expired() {
delete(m, key)
}
}




如果在range中只需要第二项,则需要使用空白标识符(下划线)丢弃第一项:



sum := 0
for _, value := range array {
sum += value
}




空白标示符有很多用法,可见下一章.

至于string,range做了更多的工作。通过解析UTF-8来分解单个Unicode码点,错误的编码会占用一个字节,并产生替换rune U+FFFD。(内建类型rune用来表示一个Unicode码点。有关详细信息可以参考语言规范)。下面的循环:



for pos, char := range "日本\x80語" { // \x80 is an illegal UTF-8 encoding
fmt.Printf("character %#U starts at byte position %d\n", char, pos)
}




会输出:



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




最后,Go没有逗号运算符,而 ++ 和 --为语句而不是表达式。因此,如果想在for循环中使用多个变量,你应该使用平行赋值。(尽管这排除了++和--)



// 反转 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的switch比C更为通用。其表达式不需要是常量或者整数,case会从上到下执行,直到有匹配,如果switch后面没有表达式,则匹配true,因此,可以将if-else-if-else长链写成一个switch,这也更符合Go的习惯。



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
}




switch不会自动执行所有case,相同条件的case可以通过逗号放在一起:



func shouldEscape(c byte) bool {
switch c {
case ' ', '?', '&', '=', '#', '+', '%':
return true
}
return false
}




break语句可以用来提前结束switch,但不像其他类C语言中那么常用。有时候不仅需要中断switch,还需要中断外层的循环,在Go中就可以通过在循环前放置一个标签,然后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)
}
}




当然,continue语句也可以接受一个可选的标签,不过仅用于循环。

本节的结束示例,是一个使用两个switch语句的字节切片(byte slices)的比较程序:



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



switch也可以用于确认接口的动态类型。类型选择使用小括号中内建的关键字type进行类型断言。若switch后面的表达式声明了变量,则该变量应具备某个子句中相应的类型。这种情况下,通常会重用变量名,但实际上会声明一个具有相同名称但类型不同的新变量。



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
}



发布于: 2020 年 04 月 30 日阅读数: 52
用户头像

申屠鹏会

关注

enjoy~ 2018.11.08 加入

https://xabc.site

评论

发布
暂无评论
翻译: Effective Go (3)