Statement Basics
严格来说,Go语句是需要使用到分号(;)作为语句的终止符。可是,如我们所见的,只有少部分分号会出现在Go程序中。这是因为编译器将自动的在以标识数,数字字面量,字符字面量,字符串字面量,某些关键词(break, continue, fallthrough, return),自增或者自减(++ or --),或者右括号,中括号,大括号(), ], })结尾的非空行上插入分号。
有两种情况,我们是必须手动的插入分号,第一种是两个或者多条语句在同一行上。另一种是普通的for循环。
自动插入分号需要特别注意的一点是,左大括号不能单独一行
// Correct ✓
for i := 0; i < 5; i++ {
fmt.Println(i)
}
// WRONG! (This won't compile.) ✗
for i := 0; i < 5; i++
{
fmt.Println(i)
}
下面部分的代码不能通过编译,因为编译器会在++之后插入一个分号。相似的,如果我们有一个无限循环(for),而左大括号在下一步,编译器也会在for之后插入分号,代码同样不能被编译。
在其它语言的花括号的位置可以放在不同的地方,但在Go语言中却是统一的,部分原因是因为自动插入分号会限制花括号的位置,部分原因是因为使用gofmt程序对Go源代码进行标准化。实际上,Go标准库中的源代码都使用了gofmt,这就是为什么代码有统一的结构,即使代码是由不同的程序写的。
Syntax | Description/result |
---|---|
append(s, ...) | 向slice s中添加新的值,可以是一个或者多个单独的值,如果是另一个slice则需要使用到s1..., 返回添加元素后的s,或者新的slice(capacity不足) |
cap(x) | 返回slice x 或者channel的缓冲区容量. 如果是x为数组,则cap(x) == len(x). |
close(ch) | 关闭channel ch, 调用后不能在向channel中发送数据。但可以继续从channel中接收数据(e.g.,已经发送到还没有接收到的值). 当channel中没有更多值时,receivers将获得channel数据类型的零值。 |
complex(r,i) | 返回一个complex128, 实数部分为r, 虚数为i, 两者为float64类型。 |
copy(dst, src) copy(b, s) |
从slice中复制元素(可以与dst重复)到dst slice中。如果没有足够的空则,则被截断。可以复制一个字符串s的字节到b中([]byte), 返回复制的元素个数 |
delete(m, k) | 从map m中删除k对应的值,如k没有存在,则什么 也没不做。 |
imag(cx) | 返回complex128的虚数部分,类型为float64 |
len(x) | slice x的长度或者 channel中已进入排队的元素个数。或者数组的长度,或者map x中的元素个数,或者一个字符串x中的字节个数 |
make(T) make(T, n) make(T, n, m) |
创建一个slice, map,或者channel的引用. 如果给定了n,则为slice的长度和容量,或者一个map预计的元素个数。或者一个channel的缓存大小。只有slice,才可以使用n和m, n表示length, m表示容量。 |
new(T) | 创建类型T的一个值,并返回这个值的地址(指针) |
panic(x) | 抛出一个运行时异常 |
real(cx) | 返回一个complex128的实数部分, 这个值的类型为float64. |
recover() | 捕获一个运行时异常 |
Go支持++(increment)和--(decrement)操作符。这两者都是词尾操作符,即,它们必须在它们要应用的操作数之后,并且它们不能返回一个值(return i++).这些限些是为了防止操作数被用于表达式,这个意思就是它们不能用于模糊不清的上下文中——比如,我们不能在调用函数时,对参数使用这些操作符,或者写 i=i++也是不允许的。
赋值语句是使用 = 赋值操作符。当结合var一起使用时,则是创建一个变量并对这个变量赋值——比如var x = 5, 创建一个新的变量x, 类型为int,它的值为5(跟var x int = 5 or x :=5是相同的)。被赋值的变量,它的类型必须跟被赋的值的类型相同。如果 = 没有跟var一起,则变量必须已经存在。多个以逗号分隔的变量可以同时用一个 = 进行赋值, 并且我们可以使用blank identifier(_), 它可以被赋于任何类型,同时弃用这个值。多个赋值可以用于交换两个值,而不需要使用到临时变量—a, b = b, a.
短的变量声明符(:=)用于声明和赋值。多个逗分分割的变量的赋值,跟 = 操作一样。但不同的是,左边的变量里必须有一个是新声明的变量. 如果一个变量已经存在,则它只是被赋值,而不是创建一个新的变量——除非:=是在一个新的作用域,比如if或for语句中的初始化语句。
a, b, c := 2, 3, 5
for a := 7; a < 8; a++ { // a is unintentionally shadowing the outer a
b := 11 // b is unintentionally shadowing the outer b
c = 13 // c is the intended outer c ✓
fmt.Printf("inner: a→%d b→%d c→%d\n", a, b, c)
}
fmt.Printf("outer: a→%d b→%d c→%d\n", a, b, c)
/*
inner: a→7 b→11 c→13
outer: a→2 b→3 c→13
*/
以上这段代码展示了如何使用:=操作符创建"shadow"变量. 在这段代码里,for循环的内部a和b变量遮盖了外部作域用的a,b变量。虽然合法,但这肯定也是一个编程错误。相反的,c变量的做法是正确的。变量遮盖另一个变量非常方便,但一定要注意因此而引发的问题。
函数的return语句,它可以有一个或者多个命名的return values,而return关键词后指定却用没有指定返回的值。在这种情况下,返回的值将是已命名的return value,它们在进入函数时初始化为零值,并且也可以在函数体内通过赋值语句改变这些值。
func shadow() (err error) { // THIS FUNCTION WILL NOT COMPILE!
x, err := check1() // x is created; err is assigned to
if err != nil {
return // err correctly returned
}
if y, err := check2(x); err != nil { // y and inner err are created
return // inner err shadows outer err so nil is wrongly returned!
} else {
fmt.Println(y)
}
return // nil returned
}
在shadow()函数的第一条语句中创建了x变量, 并且对x进行赋值。但err变量只是简单的赋值,因为它已经在shadow()函数的return value中声明。所以如果 err为非nil. 则它会正确的返回。
if的条件之前是一个可选的语句,if开始的是一个新的作用域。的以y和err变量都是新创建的变量,后者遮盖了return value. 如果err为非nil, 则外部的err将会被返回(因为没有指定return err).
幸运的是,我们不需要担心这个问题。当返回值是一个空值,而返回的变量又被遮盖,则Go的编译器会停止编译,同时给出错误信息,所以这个函数不能被编译。
一个简单的解决方案是在函数的开始声明变量(e.g., var x,y int or x, y := 0,0),然后在调用check1()和check2()时,将:=改变为=。
另一个解决方案是,不使用return value. 它强迫我们返回一个明确的值,所以在这种情况下,前两个返回语句都是return err(返回两个不同的err, 但不管是外部还是if中新创建的内部值都是正确的). 而最后的一条语句,将变为return nil.
Type Conversions
Go提供了不同但可兼容类型之间的转换,这些转换是非常有用也安全。对于非数字类型的转换不会出现精度损失的问题,但对于数字类型之间的转换会有精度损失或其它影响。比如我们有x := uint16(65000), 然后使用y := int16(x),同于x超出了int16的范围,y的值不是预期的65000, 而是-536。
以下是类型转换的语法:
resultOfType := Type(expression)
对于数字类型来说,本质上我们可以将任意的整型或者浮点类型转换成另一种整型或者浮点类型(如果目标类型比源类型的范围要小,则出现精度损失). 这也可以应用于complex128与complex64. 我们已经在第2.3节中讲过。
一个字符串可以转换为[]byte(字符串是以UTF-8字节表示的)或者一个[]rune(Unicode字符码), 而[]byte和[]rune也可以转换一个字符串。单个字符是rune(i.e int32)可以转换为单个字符串。字符串与字符的转换在第3单讲过(Table 3.2, Table 3.8, Table3.9).
让我们看一个小例子,它从一个简单的自定义类型开始
type StringSlice []string
这个类型可以有自定义的方法StringSlice.String(), 在此没有详细列出这个方法,它返回string slice的字符串表示.
fancy := StringSlice{"Lithium", "Sodium", "Potassium", "Rubidium"}
fmt.Println(fancy)
plain := []string(fancy)
fmt.Println(plain)
/*
StringSlice{"Lithium", "Sodium", "Potassium", "Rubidium"}
[Lithium Sodium Potassium Rubidium]
*/
变量fancy使用StringSlice类型的StringSlice.String()方法打印,但是当我们将它转换为纯字符串slice plain后。它的输出跟其它[]string一样。
如果一个表达式,它的潜在类型是Type(如上面的这个小例子) 或者表达式是一个没有指明类型的常量(const limit = 512可以表示任意的数字类型), 或者Type是一个接口类型, 而表达式实现了这个Type接口,则可以表达式转换为此Type类型。
类型断言(Type Assertions)
一个类型的方法集是此类型的值可以调用的所有方法——这个方法集可以为空。interface{}类型用于表示一个空接口,也就是说,它的值可以调用零个方法。在Go中interface{}可以表式任何类型,可以通过type swith,type assertions,以及reflect package, 将一个interface{}的值转换它的实际类型。
当我们处理的数据来自于外部时(external source), 或者当我们想要创建一个泛型函数(generic functions), 或者在面向对像编程时,我们会使用到interface{}类型(或者自定义接口类型)的变量. 为了访问这个变量潜在的值(underlying), 其中的一种方法是使用类型断言,以下是它的语法.
resultOfType, boolean := expression.(Type) // Checked
resultOfType := expression.(Type) // Unchecked; panic() on failure
type assertion会检查expression是不是Type类型的值,如果是则返回这个代表Type类型的值和true, 如果断言失败,则返回这个Type的零值和false. 第二种语法则返回此类型,如果断言失败,则调用内置的panic()方法终止此程序的运行(如果panic没有被捕获的情况下).
下面是一个小的例子
var i interface{} = 99
var s interface{} = []string{"left", "right"}
j := i.(int) // j is of type int (or a panic() has occurred)
fmt.Printf("%T→%d\n", j, j)
if i, ok := i.(int); ok {
fmt.Printf("%T→%d\n", i, i) // i is a shadow variable of type int
}
if s, ok := s.([]string); ok {
fmt.Printf("%T→%q\n", s, s) // s is a shadow variable of type []string
}
在做类型断言时,常见的做法是将结果的变量名取跟原变量名一样的名字。
需要注意的是,我们打印原始的i和s变量(都是interface{}),它们都会以int和[]string的形式打印。这是因为fmt package中的打印函数在面对interface{}类型时,会智能的打印实际潜在的值。