目录
Go语言顺序编程
流程控制
Go 语言提供的流程控制语句包括 if
、switch
、for
、goto
、select
,其中 select
用于监听 channel
(通道)在讲解通道的时候再详细介绍。
if 语句
语法:
if optionalStatement1; booleanExpression1 {
block1
} else if optionalStatement2; booleanExpression2 {
block2
} else {
block3
}
其中 optionalStatement
是可选的表达式,真正决定分支走向的是 booleanExpression1
的值。
for 语句
Go 语言的 for
语句可以遍历数组,切片,映射等类型,也可以用于无限循环。以下是其语法:
for { // 无限循环
block
}
for booleanExpression { // while循环,在Go语言中没有while关键字
}
for index, char := range aString { // 迭代字符串
}
for item := range aChannel { // 迭代通道
}
跳转语句
Go 语言中使用 goto
关键字实现跳转。goto
语句的语义非常简单,就是跳转到本函数内的某个标签,例如:
func myfunc(){
i := 0
THIS: //定义一个THIS标签
fmt.Println(i)
i++
if i < 1 {
goto THIS //跳转到THIS标签
}
}
switch语句
Go 语言中 switch
分支既可用于常用的分支就像 C 语言中的 switch
一样,也可以用于类型开关,所谓类型开关就是用于判断变量属于什么类型。但是需要注意的是 Go 语言的 switch
语句不会自动贯穿,相反,如果想要贯穿需要添加 fallthrough
语句。表达式开关 switch
的语法如下:
switch optionalStatement; optionalExpression {
case expression1: block1
...
case expressionN: blockN
default: blockD
}
下面是个例子:
switch { // 没有表达式,默认为True值,匹配分支中值为True的分支
case value < minimum:
return minimum
case value > maximum:
return maximum
default:
return value
}
在上面的例子中,switch 后面没有默认的表达式,这个时候 Go 语言默认其值为 True
。
在前面我们提到过类型断言,如果我们知道变量的类型就可以使用类型断言,但是当我们知道类型可能是许多类型中的一种时候,我们就可以使用类型开关。其语法如下:
switch optionalStatement; typeSwitchGuard {
case type1: block1
...
case typeN: blockN
default: blockD
}
说了这么多,让我们进行下练习,创建源文件 switch_t.go
,输入以下代码:
package main
import (
"fmt"
)
func classchecker(items ...interface{}) { // 创建一个函数,该函数可以接受任意多的任意类型的参数
for i, x := range items {
switch x := x.(type) { // 创建了影子变量
case bool:
fmt.Printf("param #%d is a bool, value: %t\n", i, x)
case float64:
fmt.Printf("param #%d is a float64, value: %f\n", i, x)
case int, int8, int16, int32, int64:
fmt.Printf("param #%d is a int, value: %d\n", i, x)
case uint, uint8, uint16, uint32, uint64:
fmt.Printf("param #%d is a uint, value: %d\n", i, x)
case nil:
fmt.Printf("param #%d is a nil\n", i)
case string:
fmt.Printf("param #%d is a string, value: %s\n", i, x)
default:
fmt.Printf("param #%d's type is unknow\n", i)
}
}
}
func main() {
classchecker(5, -17.98, "AIDEN", nil, true, complex(1, 1))
}
以上代码中我们首先创建了一个接收任意数量任意类型参数的函数,然后使用 for ... range aSlice
的语法迭代了每一个在切片 items
中的元素,接着使用了 switch
类型开关判断了每一个参数的类型,并打印了其值和类型。程序运行输出如下:
$ go run switch_t.go
param #0 is a int, value: 5
param #1 is a float64, value: -17.980000
param #2 is a string, value: AIDEN
param #3 is a nil
param #4 is a bool, value: true
param #5's type is unknow
函数
Go 语言可以很方便的自定义函数,其中有特殊的函数 main
函数。main
函数必须出现在 main
包里,且只能出现一次。当 Go 程序运行时候会自动调用 main
函数开始整个程序的执行。main
函数不可接收任何参数,也不返回任何结果。
函数的定义
在 Go 语言中,函数的基本组成包括:关键字 func
、函数名、参数列表、返回值、函数体和返回语句,这里我们用一个简单的加法函数来对函数的定义进行说明。
package add
func Add(a int, b int) (num int){
return a + b
}
函数的调用
函数调用非常简单,先将被调用函数所在的包导入,就可以直接使用该函数了。注意需要把包文件夹放到 $GOPATH
目录中,实例如下:
package main
import (
"add" //导入 add 包
"fmt"
)
func main(){
c := add.Add(1, 2) //调用 add 包中的 add 函数
fmt.Println(c)
}
函数的多返回值
与 C/C++ 和 JAVA 不同,Go 语言的函数和方法可以有多个返回值,这是 Go 提供的一个优美的特性,示例如下:
package Divide
import "errors"
func divide (a int, b int) (num int, err error){ //定义两个返回值
if b == 0 {
err = errors.New("被除数不能为零!")
return
}
return a / b, nil //支持多个返回值
}
匿名函数
在 Go 语言中,你可以在代码里随时定义匿名函数,匿名函数由一个不带函数名的函数声明和函数体组成,示例如下:
func (a, b, c int) bool {
return a * b < c
}
你可以将匿名函数直接赋值给一个变量,也可以直接调用运行,示例如下:
x := func (a, b, c int) bool {
return a * b < c
}
func (a, b, c int) bool {
return a * b < c
} (1, 2, 3) //小括号内直接给参数列表表示函数调用
类型处理
类型转换
Go 语言提供了一种在不同但相互兼容的类型之间相互转换的方式,这种转换非常有用并且是安全的。但是需要注意的是在数值之间进行转换可能造成其他问题,如精度丢失或者错误的结果。以下是类型转换的语法:
-
resultOfType := Type(expression)
几个例子:
x := int16(2345) // 声明一个类型为int16的整数,其值为2345
y := int32(x) // 将int16类型的整数转换为int32类型
a := uint16(65000) // 声明一个类型为uint16类型的整数
b := int16(a) // 转换为int16类型,虽然能转换成功,但是由于65000超过in16类型的范围,会导致结果错误,b的值为 -536
另外在 Go 语言中可以通过 type
关键字声明类型,如 type StringsSlice []string
将 []string
(string
类型的切片)声明为 StringSlice
类型。
类型断言
说到类型断言就需要先了解下 Go 语言中的接口。在 Go 语言中接口是一个自定义类型。它声明了一个或者多个方法。任何实现了这些方法的对象(类型)都满足这个接口。
接口是完全抽象的,不能实例化。interface{}
类型表示一个空接口,任何类型都满足空接口。也就是说 interface{}
类型的值可以用于表示任意 Go 语言类型的值。
这里的空接口有点类似于 Python 语言中的 object 实例。既然 interface{}
可以用于表示任意类型,那有的时候我们需要将 interface{}
类型转换为我们需要的类型,这个操作称为类型断言。
一般情况下只有我们希望表达式是某种特定类型的值时才使用类型断言。Go 语言中可以使用以下语法:
-
resultOfType, boolean := expression.(Type)
:安全的类型断言。 -
resultOfType := expression.(Type)
:非安全的类型断言,失败时程序会产生异常。
创建源文件 type_t.go
,输入以下源文件:
package main
import (
"fmt"
)
func main() {
x := uint16(65000)
y := int16(x) // 将 x转换为int16类型
fmt.Printf("type and value of x is: %T and %d\n", x, x) // %T 格式化指令的作用是输出变量的类型
fmt.Printf("type and value of y is: %T and %d\n", y, y)
var i interface{} = 99 // 创建一个interface{}类型,其值为99
var s interface{} = []string{"left", "right"}
j := i.(int) // 我们假设i是兼容int类型,并使用类型断言将其转换为int类型
fmt.Printf("type and value of j is: %T and %d\n", j, j)
if s, ok := s.([]string); ok { // 创建了影子变量,if的作用域中覆盖了外部的变量s
fmt.Printf("%T -> %q\n", s, s)
}
}
运行程序:
$ go run type_t.go
type and value of x is: uint16 and 65000
type and value of y is: int16 and -536
type and value of j is: int and 99
[]string -> ["left" "right"]
错误处理
错误处理是任何语言都需要考虑到的问题,而 Go 语言在错误处理上解决得更为完善,优雅的错误处理机制是 Go 语言的一大特点。
error
Go 语言引入了一个错误处理的标准模式,即 error
接口,该接口定义如下:
type error interface {
Error() string
}
对于大多数函数,如果要返回错误,可以将 error
作为多返回值的最后一个:
func foo(param int)(ret int, err error)
{
...
}
调用时的代码:
n, err := foo(0)
if err != nil {
// 错误处理
} else {
// 使用返回值n
}
我们还可以自定义错误类型,创建源文件 error.go
,输入以下代码:
package main
import "fmt"
import "errors"
//自定义的出错结构
type myError struct {
arg int
errMsg string
}
//实现Error接口
func (e *myError) Error() string {
return fmt.Sprintf("%d - %s", e.arg, e.errMsg)
}
//两种出错
func error_test(arg int) (int, error) {
if arg < 0 {
return -1, errors.New("Bad Arguments - negtive!")
}else if arg >256 {
return -1, &myError{arg, "Bad Arguments - too large!"}
}
return arg*arg, nil
}
//相关的测试
func main() {
for _, i := range []int{-1, 4, 1000} {
if r, e := error_test(i); e != nil {
fmt.Println("failed:", e)
} else {
fmt.Println("success:", r)
}
}
}
defer
你可以在 Go 函数中添加多个 defer
语句,当函数执行到最后时,这些 defer 语句会按照逆序执行(即最后一个 defer
语句将最先执行),最后该函数返回。特别是当你在进行一些打开资源的操作时,遇到错误需要提前返回,在返回前你需要关闭相应的资源,不然很容易造成资源泄露等问题。如下代码所示,我们一般写打开一个资源是这样操作的:
func CopyFile(dst, src string) (w int64, err error) {
srcFile, err := os.Open(src)
if err != nil {
return
}
defer srcFile.Close()
dstFile, err := os.Create(dst)
if err != nil {
return
}
defer dstFile.Close()
return io.Copy(dstFile, srcFile)
}
如果 defer
后面一条语句干不完清理工作,也可以使用一个匿名函数:
defer func(){
...
}()
注意,defer
语句是在 return
之后执行的,新建源文件 defer.go
输入以下代码:
func test() (result int) {
defer func() {
result = 12
}()
return 10
}
func main() {
fmt.Println(test()) // 12
}
panic和recover
panic()
函数用于抛出异常,recover()
函数用于捕获异常,这两个函数的原型如下:
func panic(interface{})
func recover() interface{}
当在一个函数中调用 panic()
时,正常的函数执行流程将立即终止,但函数中之前使用 defer
关键字延迟执行的语句将正常展开执行,之后该函数将返回到调用函数,并导致逐层向上执行 panic()
流程,直至所属的 goroutine
中所有正在执行的函数被终止。错误信息将被报告,包括在调用 panic()
函数时传入的参数,这个过程称为错误流程处理。
panic()
接受一个 interface{}
参数,可支持任意类型,例如:
panic(404)
panic("network broken")
panic(Error("file not exists"))
在 defer
语句中,可以使用 recover()
终止错误处理流程,这样可以避免异常向上传递,但要注意 recover()
之后,程序不会再回到 panic()
那里,函数仍在 defer
之后返回。新建一个源文件 error1.go
,输入以下代码:
func foo() {
panic(errors.New("i'm a bug"))
return
}
func test() (result int) {
defer func() {
if r := recover(); r != nil {
err := r.(error)
fmt.Println("Cache Exception:", err)
}
}()
foo()
return 10
}
func main() {
fmt.Println(test()) // 0
}
练习题
使用 Go 语言实现一个小型计算器程序,包括加、减、乘、除运算(暂不考虑括号运算)
参考:
package main
import (
"fmt"
)
func main() {
var (
a int
b int
c int
d int
)
a = 2
b = 3
c = 4
d = 5
fmt.Printf("%d \n", a+b)
fmt.Printf("%d \n", a-b)
fmt.Printf("%d \n", c*d)
fmt.Printf("%d \n", c/d)
}
实验总结
Go 语言顺序编程到本节就已经结束了,我们先来回顾一下本实验涉及到的内容:
-
流程控制
-
函数
-
类型转换
-
类型断言
-
错误处理
顺序编程是 Go 语言重要的一部分,但也是很小的一部分,后续将为大家带来更多 Go 语言的优美特性!