Go 语言过程式编程实验简介这节讲解Go语言的各种语句,控制结构,以及如何使用这些语句进行过程式编程,课程的内容比较多,但是都比较重要,在编写Go程序的过程中会频繁使用到。 一、实验说明本课程所有源代码,可以在XfceTerminal中通过以下方式克隆到实验环境:
二. Go语言语句基础之所以先学习过程式编程,是因为在Go语言中面向对象编程也是建立在面向过程的基础上的。形式上讲,Go语言需要使用分号(;) 来作为上下文语句的分隔结束符。实际上在前面的代码中我们可以看到在Go语言中很少使用分号,那是因为编译器会自动在需要分号的地方加上分号。但是有两个地方必须使用分号,第一个是需要在一个行中放入一条或多条语句时,或者是在使用原始的 for 循环时。Go语言也支持多重赋值,如
以上代码中,先使用 1. 类型转换Go语言提供了一种在不同但相互兼容的类型之间相互转换的方式,这种转换非常有用并且是安全的。但是需要注意的是在数值之间进行转换可能造成其他问题,如精度丢失或者错误的结果。以下是类型转换的语法:
几个例子:
另外在Go语言中可以通过 2. 类型断言说到类型断言就需要先了解下Go语言中的接口。在Go语言中接口是一个自定义类型。它声明了一个或者多个方法。任何实现了这些方法的对象(类型)都满足这个接口。接口是完全抽象的,不能实例化。
使用VIM创建源文件
运行程序:
三. 分支和
|
语法 | 含义 |
---|---|
channel <- value | 发送value到通道中,有可能阻塞 |
<-channel | 从通道中接收数据 |
x := <-channel | 接收数据并赋值给x |
x, ok := <-channel | 功能同上,同时检查通道是否已关闭或者是否为空 |
select
语句在前面的课程中我们提到过select语句,用于监听通道。其语法如下:
select {
case sendOrReceviae1: block1
...
case sendOrReceiveN: blockN
default: blockD
}
Go语言会从头至尾的判断每一个case
中的发送和接收语句。如果其中任何一条语句可以执行(即没有被阻塞),那就从那些可执行的语句中任意选择一条来使用。如果所有的通道都被阻塞,那可能有两种情况。第一种,如果有default
语句,那就会执行default
语句,同时程序的执行会从select
语句恢复。第二种,如果没有default
语句,则select
语句会一直阻塞,直到有一个通道可用
下面让我们使用以上的相关知识进行下练习,使用VIM创建源文件goroutine_channel_t.go
,输入如下源代码:
package main
import (
"fmt"
"math/rand"
)
func main() {
channels := make([]chan bool, 6) // 创建一个类型为chan bool的切片,每一项是能发送bool值的通道
for i := range channels { // 通过`range`初始化切片
channels[i] = make(chan bool)
}
go func() { // 在其他gouroutine中执行匿名函数
for {
channels[rand.Intn(6)] <- true // rand.Intn(n int)的用途是产生一个不大于n的随机数
} // 发送数据到随机出现的通道
}()
for i := 0; i < 36; i++ {
var x int
select { // select 语句当监听到哪个分支的同道未阻塞时就跳转到哪个分支
case <-channels[0]:
x = 1
case <-channels[1]:
x = 2
case <-channels[2]:
x = 3
case <-channels[3]:
x = 4
case <-channels[4]:
x = 5
case <-channels[5]:
x = 6
}
fmt.Printf("%d ", x)
}
fmt.Println()
}
通过以上注释可以很清晰的看到整个代码的执行流程,下面我们执行代码:
$ go run goroutine_channel_t.go
6 4 6 6 2 1 2 3 5 1 3 2 1 6 5 3 4 6 6 3 6 1 3 5 4 2 2 5 1 4 2 1 6 6 4 3
defer
, panic
和recover
defer
开发程序时,有的时候忘记关闭打开的文件导致程序执行失败,在python中可以很方便的使用with
语句对这些资源进行自动管理。在Go中我们可以使用defer
语句完成这项任务。defer
语句用于延迟执行一个函数或者方法或者是当前创建的匿名函数,它会在外部函数或者方法返回之前但是其返回值计算之后执行。这样就可能在一个延迟执行的函数中修改函数的命名返回值。如果一个函数中又多个defer
语句,它们会以后进先出的顺序执行。defer
最常用的地方就是保证一个使用完成后的文件正常关闭。如下例子:
var file *os.File
var err error
if file, err = os.Open(filename); err != ni {
do_something(file)
return
}
defer file.Close()
panic
和recover
panic
类似于其他程序中的异常,而recover
则用于恢复异常。当panic()
函数被调用时,外围函数或者方法的执行会立即终止。然后任何延迟执行的函数都会被调用。这个过程一直在调用栈中层层发生,最后到达main
函数,这个时候整个程序会终止,最终将最初的调用栈信息输出到stderr。但是当延迟执行函数中包含recover
语句时,recover
会捕捉到panic
引发的异常,并停止panic
的传播,这个时候我们能够以任何我们想用的方式处理panic
。
Go语言将错误和异常两者区分对待。错误是指有可能出错的东西,程序中已经包含处理这些错误的优雅逻辑。而异常则是指不可能发生的事情。例如,一个永远为true的条件在实际环境中却是false。Go语言推荐使用错误,而不使用异常。通常情况下,我们可以在recover
中阻止panic
的传播,并将recover()
的返回值转换成错误。
使用VIM创建源文件panic_t.go
, 输入以下代码:
package main
import (
"fmt"
"math"
)
func ContvertIntToInt16(x int) int16 {
if math.MinInt16 <= x && x <= math.MaxInt16 {
return int16(x)
}
panic(fmt.Sprintf("%d is out of int16 range", x)) // 手动触发panic
}
func main() {
i := ContvertIntToInt16(655567)
fmt.Printf("%d", i)
}
上面代码中为了演示panic
,代码中手动促发了panic()
的执行,但是我们没有使用recover
进行捕捉,这会导致整个程序执行失败,下面执行程序验证下:
$ go run panic_t.go
panic: 655567 is out of int16 range
goroutine 16 [running]:
runtime.panic(0x96bc0, 0x208178180)
/usr/local/go/src/pkg/runtime/panic.c:279 +0xf5
main.ContvertIntToInt16(0xa00cf, 0x3ec8f)
/Users/aiden/Project/golang/panic_t.go:13 +0x10f
main.main()
/Users/aiden/Project/golang/panic_t.go:17 +0x26
goroutine 17 [runnable]:
runtime.MHeap_Scavenger()
/usr/local/go/src/pkg/runtime/mheap.c:507
runtime.goexit()
/usr/local/go/src/pkg/runtime/proc.c:1445
goroutine 18 [runnable]:
bgsweep()
/usr/local/go/src/pkg/runtime/mgc0.c:1976
runtime.goexit()
/usr/local/go/src/pkg/runtime/proc.c:1445
goroutine 19 [runnable]:
runfinq()
/usr/local/go/src/pkg/runtime/mgc0.c:2606
runtime.goexit()
/usr/local/go/src/pkg/runtime/proc.c:1445
exit status 2
可以看到没有捕捉panic
时,整个程序退出,并且打印出了调用栈的异常信息。
下面我们使用Go语言推荐的做法捕捉panic
并将panic
转换为error, 创建源文件panic_t1.go
,输入以下代码:
package main
import (
"fmt"
"math"
)
func ContvertIntToInt16(x int) int16 {
if math.MinInt16 <= x && x <= math.MaxInt16 {
return int16(x)
}
panic(fmt.Sprintf("%d is out of int16 range", x)) // 手动触发panic
}
func Int16FromInt(x int) (i int16, err error) {
defer func() { // 延迟执行匿名函数,并使用recover捕捉了panic,并将panic转换为了error
if e := recover(); e != nil {
err = fmt.Errorf("%v", e)
}
}()
i = ContvertIntToInt16(x)
return i, nil
}
func main() {
if _, e := Int16FromInt(655567); e != nil {
fmt.Printf("%v\n", e)
} else {
fmt.Printf("no errors\n")
}
}
以上代码中,我们通过recover
捕捉了异常,现在程序将异常转换成了错误,所以程序不会异常退出,执行验证如下:
$ go run panic_t1.go
655567 is out of int16 range
值得注意的地方是,在以上代码中的Int16FromInt(x int) (i int16, err error)
函数中,我们在defer
语句的匿名函数中修改了命名的返回值err
。该函数在被调用时,Go语言会自动的将其返回值设置为对应类型的零值,在Int16FromInt
函数中,i被初始化为0,err被初始化为nil。当在defer
语句中匿名函数执行时候,recover
如果捕捉到异常,然后修改了命名返回值err
,并保持i
的值(零值 )不变。如果没有捕捉到异常,则程序正常返回i
和nil
。
请使用本节所讲知识编写一个函数,实现输入一个正整数n,输出2-n之间的所有素数。
|
来自: 心平9bwburua3p > 《待分类》