Go语言顺序编程

本文介绍了Go语言的基础编程概念,包括顺序编程的各个方面,如流程控制(if、for、switch语句)、函数、类型处理(类型转换和类型断言)以及错误处理机制(error、defer、panic和recover)。此外,还展示了如何使用类型开关进行类型判断,并提供了示例代码进行练习和实验总结。
摘要由CSDN通过智能技术生成

目录

Go语言顺序编程

流程控制

if 语句

for 语句

switch语句

函数

函数的多返回值

类型处理

类型转换

类型断言

错误处理

error

defer

panic和recover

练习题

实验总结


Go语言顺序编程

流程控制

Go 语言提供的流程控制语句包括 ifswitchforgotoselect,其中 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[]stringstring 类型的切片)声明为 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 语言的优美特性!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

王水最甜

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值