Fork me on GitHub

Go语言开发-过程式编程-语句基础

5.1.语句基础

通常,Go语言的语法要求使用分号作为语句结束符。然而,正如我们所看到的那样,在实际编程中我们很少真正需要分号,这是因为编译器会自动在以标识符、数字常量、字符常量、字符串常量、某些关键字(break、continue、fallthrough、return)、递增或递减操作符(++或–)、或右括号、右中括号、右大括号结尾的非空白行结尾处添加分号。

必须手动添加分号的两个常见情况:一是当我们希望在同一行中同时包含两个或多个语句;或是在for循环中。

自动插入分号的一个重要结果是一个左大括号不能单独成一行。

上面右侧的代码片段不能通过编译,因为编译器会在++后面插入一个分号。同样,如果我们有一个无限循环(for),其左大括号下一行开始,编译器会自动在for语句后插入一个分号,同样代码不能通过编译。

大括号位置的美学通常引起无无止的争论,但在Go中不会。部分是因为自动插入分号限制了大括号可以放置的位置,另一部分是因为大部分Go语言开发者使用gofmt程序以标准化的方式格式化程序。事实上,所有Go语言标准库的源代码都是使用gofmt格式化的,这就是为什么所有的代码都具有如此一致的风格的原因,即使这些是有许多不同的开发人员共同开发的结果。

表5.1 内置函数

append(s, …) 如果给定的切片容量足够大,则将新的元素添加到该切片末尾;否则创建一个新的切片,其包含原始切片的元素和新添加的元素。
cap(x) 切片x的容量,或通道x的缓冲容量,或数组(或所指向数组)x的长度,参见len()。
close(ch) 关闭通道ch(但不能用于只接受数据的单向通道),不能再向通道中发送数据,但是可以继续从通道中接收数据(即那些已发送但还未接收的数据),当通道中不再有数据时,接收者将得到该通道类型的零值。
complex(r, i) 以给定的r(实部)和i(虚部)组成的complex128的值,其中r和i均为float64类型。
copy(dst, src)

copy(b, s)

将切片src中的元素复制(可能是重叠的)到切片dst中,如果dst容量不够则截断;或是将字符串的字节类型的s复制到[]byte类型的b中。
delete(m, k) 从映射m中删除键为k的元素,如果不存在,则安全的什么也不做。
imag(cx) 得到complex128类型的cx的虚部,其类型为float64
len(x) 切片x的长度,或通道x的缓冲区中已有元素的个数,或数组x的长度,映射x中元素的个数,或字符串x中字节的个数。
make(T)

make(T, n)

make(T, n, m)

一个对类型为T的切片、映射或通道的引用。如果给定n,则其就是切片的长度和容量,或是希望映射可以保存的元素个数,或是通道的缓冲区大小。对于切片来说,n和m可以用于指定其长度和容量。
new(T) 一个指向类型T的值的指针。
panic(x) 抛出一个值为x的可捕获的运行时异常。
real(cx) 得到complex128类型的cx的实部,其类型为float64。
recover() 捕获一个运行时异常。

Go语言支持表2.4列出的++(递增)和–(递减)操作符。它们都是后置操作符,也就是说,它们必须跟随在一个它们所作用的操作数后面且不会返回任何值。这样就可以防止这些操作符被用于表达式,同时也意味着它们不能用于语义模糊上下文中,例如,我们不能将它们用在函数调用的参数中,或i=i++这样的表达式中(尽管我们可以在C和C++中这样做,但其结果是未定义的)。

我们可以通过使用=赋值操作符来完成赋值操作。变量可以使用=和var关键字来创建和赋值,例如,var x =5创建了一个int型的变量x,其值为5。(使用var x int=5或x:=5也可以做到这一点)被赋值的变量的类型必须与所赋的值的类型相兼容。如果使用了=却没有使用var关键字,则其左边的变量必须是己存在的。多个逗号分隔的变量也可以被赋值,我们也可以将值赋值给空白标识符(-),它与任意类型兼容且会丢弃赋值给它的任何值。多重赋值可以更容易的交换两个变量的值而不需要显式地定义临时变量,例如a, b=b, a。

短变量声明操作符(:=)用于在一个语句中声明一个变量并同时将其赋值。多个以逗号分隔的变量与=操作符用法基本一样,但是必须至少有一个非空变量为未声明的变量。如果有一个变量己经存在,则该变量会被赋值为新的值,而不是新建一个变量,除非:=操作符位于作用域的开始处,如if或for语句中的初始化语句。

上面的代码片段演示了:=操作符是如何创建“影子”变量的。在for循环中,变量a和b覆盖了外部作用域中的变量,虽然合法,但几乎可以肯定的是这是一个编程错误。另外,由于只创建了一个c变量(在外部作用域中),因此它的用法是正确的,且其结果也如预期的那样。接下来我们将会看到,覆盖其他变量的变量有时候很实用,但是粗心地使用可能会引起问题。

我们将在本章后面讨论,我们可以在有一个或多个返回值的函数中编写return语句,而无需指定返回值。在这种情况下,返回值将是已命名的返回值,它们在函数入口处被初始化为其零值且可以在函数中修改它们。

在shadow()函数的第一个语句中,变量x被创建并被赋值,但是err变量只是简单的被赋值,因为它己经被声明为shadow()函数的返回值。这之所以能够正常运行,是因为:=操作符左侧必须至少创建一个非空变量。所以,如果err变量的值不为nil,该函数便会正常返回。

一个if语句会创建一个新的作用域。所以,变量y和err都被重新创建,后者是一个影子变量。如果err的值不为nil,则返回外部作用域中的err值(即err声明为shadow()函数的返回值),其值为nil,因为这是通过调用check1()函数赋值的,而在调用check2()函数时,被赋值的是err的影子变量,其不会影响外部的err值。

幸运的是,我们无需担心该函数的影子变量问题,因为Go编译器会在我们使用未指定返回值的return语句且有任意返回值被影子变量覆盖时给出一个错误消息并终止编译。所以,在实际中该函数无法通过编译。

一个简单的解决方法是在函数开始处声明变量(例如,var x, y int或x, y:=0, 0),调用check1()和check2()函数是使用=而不是:=。(参见自定义的americanise()函数)

另一个解决方法是使用一个未命名的返回值。这迫使我们返回一个显式的值,所以在这种情况下,前两个return语句都是return err(返回的是不同但都正确的err值),最后一个return语句为return nil。


目录


作者:Johnson
原创文章,版权所有,转载请保留原文链接。

发表评论

电子邮件地址不会被公开。 必填项已用*标注