初识 Golang 语法篇 - 控制语句、基本语法

127 阅读5分钟

Golang 中的条件控制语句与 Clang 相似,但省去了许多不必要的附加符号,提供必要的精简方式。

条件控制语句

if-else 语句

  • Clang 不同的是 Golang 中的表达式都省去了 ()

Usage 1:

if <condition1> {
    <block1>
} else if <condition2> {
    <block2>
} else {
    <block0>
}

Example:

if returnValue == -1 {
    fmt.Println("Something Error.")
}
if weight < 240 {
    fmt.Println("Weight OK!")
} else {
    fmt.Println("Too weight.")
}
if score < 60 {
    fmt.Println("F")
} else if score < 70 {
    fmt.Println("P")
} else if (score < 80) {
    fmt.Println("C")
} else if (score < 90) {
    fmt.Println("B")
} else {
    fmt.Println("A")
}

Usage 2:

if <init>; <conditional> {
	<block>
}

Golang 没有三目运算符,因为官方认为其影响 Golang 的语法整洁性。

switch-case 语句

Usage 1:

switch <variable> {
case <value1>:
    <block1>
case <value2>:
    <block2>
...
default:
    <block0>
}

Example:

switch level {
case 1:
    fmt.Println("P")
case 2:
    fmt.Println("C")
case 3:
    fmt.Println("B")
case 4:
    fmt.Println("A")
default:
    fmt.Println("F")
}

Usage 2:

switch {
case <condition1>:
    <block1>
case <condition2>:
    <block2>
...
default:
    <block0>
}

Example:

switch {
case score < 60:
    fmt.Println("F")
case score < 70:
    fmt.Println("P")
case score < 80:
    fmt.Println("C")
case score < 90:
    fmt.Println("B")
default:
    fmt.Println("A")
}

select-case 语句

  • expression 必须是一个通信操作,如 x := <-c
  • select 语句将随机抽取一个 case,如果通信成功将运行 block;如果通信失败并且存在 default 将运行 block0
select {
case <expression1>:
    <block1>
case <expression2>:
    <block2>
default:
    <block0>
}

Example:

var c1 = make(chan string)
var c2 = make(chan string)

func Thread1() {
    time.Sleep(time.Millisecond * 2100)  // A1
    c1 <- "Thread1 is ready."
}

func Thread2() {
    time.Sleep(time.Millisecond * 2100)  // A1
    c2 <- "Thread2 is ready."
}

func ThreadMain() {
    for i := 0; i < 10; i++ {
        select {
        case x := <-c1:
            fmt.Println("Get:", x)
        case x := <-c2:
            fmt.Println("Get:", x)
        default:
            fmt.Println("Get Nothing.")
            time.Sleep(time.Millisecond * 500)  // A2
        }
    }
}

func main() {
    go Thread1()
    go Thread2()
    ThreadMain()
}
Get Nothing.
Get Nothing.
Get Nothing.
Get Nothing.
Get Nothing.
Get: Thread1 is ready.
Get: Thread2 is ready.
Get Nothing.
Get Nothing.
Get Nothing.
  • 注意在案例输出中,Thread1 is ready.Thread2 is ready. 先后是随机的,因为到第五次循环(2500msThread1Thread2 都准备就绪。
  • 如果将 A1 处参数修改为 2000,则 Thread1 is ready. 在先概率更高,因为 Thread1Thread2 理论上恰好同时在第四次循环(2000ms)准备就绪,而线程启动需要其他准备时间且 Thread1 先于 Thread2 启动。

循环控制语句

for 语句

GolangClang
for <init>; <condition>; <post> { <block> }for (<init>; <condition>; <post>) { <block>; }
for <condition> { <block> }while (<condition>) { <block>; }
for { <block> }while (true) { <block>; }
  • Golang 中的 for 语句 ; 间的语句也可以是空语句

Example:

for i := 0; i < 10; i++ {
    fmt.Println(i)
}
var i int = 0
// for ; i < 10; {
//     fmt.Println(i)
//     i++
// }
for i < 10 {
    fmt.Println(i)
    i++
}
// for ;; {
//     fmt.Println("Running...")
//     time.Sleep(time.Millisecond * 500)
// }
for {
    fmt.Println("Running...")
    time.Sleep(time.Millisecond * 500)
}

跳转语句

  • continue:跳转到下一个 for 循环。

    for i := 0; i < 5; i++ {
        if i == 3 {
            continue
        }
        fmt.Println(i)
    }
    
  • break:跳转到当前 for 循环外。

    for i := 0; i < 5; i++ {
        if i == 3 {
            break
        }
        fmt.Println(i)
    }
    
  • goto:跳转到指定标签

    for i := 0; i < 5; i++ {
        for j := 0; j < 5; j++ {
            if i == 3 and j == 3 {
                goto tag
            }
            fmt.Println(i, j)
        }
    }
    tag:
    

for-range 语句

slice := []int{1, 2, 3, 4, 5}
for key, value := range slice {
    fmt.Println(key, value)
}
  • 数组、stringslicemapchannel 等都可以使用 for-range 语句遍历。其中 channel 没有 key 且阻塞。
channel := make(chan int, 10)
for value := range channel {
    fmt.Println(value)
}

特殊控制语句

  • Golang 从语法上支持并发,它有其特殊的控制语句。

defer 语句

defer 语句用法

  • defer 语句用于将操作延迟到函数结束执行,其操作甚至迟于 return 操作。
  • defer 语句将延迟操作压栈,在结束时逆序执行。
  • defer 语句必须使用函数。可以使用匿名函数。
defer <functionCall>
func Echo() {
    defer fmt.Println("Function Exited.")
    defer fmt.Println("Echoed.")
    fmt.Println("Hello World!")
}
Hello World!
Echoed.
Function Exited.

defer 语句怪用

  • 谈及 defer 原理,defer 语句将延迟操作压栈,压栈数据包括:函数名、函数参数(临时)地址。后面的所有怪用都基于这一原理,defer 语句可以修改和访问本该已卸载的内存。defer 压栈后实际对地址又进行了一次引用,因此 Golang 的垃圾回收机制(GC)实际没有卸载这些内存。

  • 修改函数返回值。该案例中使用匿名返回值得不到相同效果,个人猜测是因为匿名返回值返回时进行了拷贝。

func Demo() (s string) {
    s = "Edit by Function"
    defer func() { s = "Edit by Defer" }
    return s
}
Edit by Defer
  • 循环延迟返回值相同。该案例中,Demo1defer 访问 for 循环定义的 i,在程序结束时访问其值为 3Demo2defer 访问每次循环体内定义的 i,可以认为是循环变量 i 的快照版本。
func Demo1() {
    for i := 0; i < 3; i++ {
        defer fmt.Println(i)
    }
}
3
3
3
func Demo2() {
    for i := 0; i < 3; i++ {
        i := i
        defer fmt.Println(i)
    }
}
2
1
0

go 语句

  • go 语句将创建一个 gorountine,可以简单认为是一个线程或协程。go 语句的对象是一个函数,将函数作为一个新的线程运行。
  • 关于什么是线程,不作赘述。
func thread() {
    for i := 0; i < 5; i++ {
        fmt.Println("Thread", i)
        time.Sleep(time.Microsecond * 50)
    }
}

func main() {
    go thread()
    for i := 0; i < 5; i++ {
        fmt.Println("Main", i)
        time.Sleep(time.Microsecond * 50)
    }
}
Main 0
Thread 0
Thread 1
Main 1
Main 2
Thread 2
Main 3
Thread 3
Thread 4
Main 4

异常处理

error 接口

接口原型

type error interface {
    Error() string
}

构造异常

err := errors.New("异常信息")  // error: "异常信息"
err := fmt.Errorf("错误信息: %v", "异常信息")  // errors.New(fmt.Sprintf(...))

捕捉异常

func tryInt(s string) {
    i, err := strconv.ParseInt(s, 0, 64)
    if err == nil {
        fmt.Println("Value:", i)
    } else {
        fmt.Println("Error:", err)
    }
}

func main() {
    tryInt("123123")
    tryInt("123a123")
}
Value: 123123
Error: strconv.ParseInt: parsing "123a123": invalid syntax

异常嵌套

go 1.13 开始支持异常嵌套,引入了 errors.Iserrors.Aserrors.Unwrapfmt.Errorf 中的 %w

func main() {
    err1 := errors.New("error 1")
    err2 := errors.New("error 2")
    err3 := errors.New("error 3")
    err4 := errors.New("error 1")
    fmt.Errorf("%w: %w", err3, err1) // %w 称 Wrap, 将多种错误嵌套, 与 Unwrap 对应
    fmt.Println(errors.Is(err1))     // true
    fmt.Println(errors.Is(err2))     // false
    fmt.Println(errors.Is(err3))     // true
    fmt.Println(errors.Is(err4))     // false
    
}

errors.Is 的本质是不断调用 errors.Unwrap 并检测错误信息指针是否一致,因此第 10 行为 false

系统错误可以使用 fs.xxx 找到对应的系统错误。

panic() 与 recover()

  • Golang 中没有 try-catch 语句,而使用 panic-recover 进行异常控制,两者具有一定区别。
  • func panic(v any)(崩溃)将产生异常,如果异常不被捕捉,将抛出命令行错误并终止程序。
  • func recover() any(恢复)用于捕捉异常,只在 defer 函数中生效。
  • 非不可挽回的错误,建议使用 error 而非 panic

造成 panic 的场景

  • 索引或指针无效或越界
  • 向关闭的 channel 发送消息
  • 类型断言(不获取 ok
  • 用户发送 panic

捕捉与处理异常

defer func() {
    err := recover()
    if err != nil {
        fmt.Printf("捕捉异常: %T %v\n", err, err)
    }
}()
// defer recover() 无效
// defer func() { recover() }() 有效
panic("抛出异常")
捕捉异常: string 抛出异常
  • 由于 recover() 只在 defer 函数中生效,只能在函数结束处理异常。