Go语言学习笔记(四)——函数、方法、接口与并发编程入门

Go 的强大之处不仅在于语法简洁,更在于它把很多实用能力设计得非常自然。函数、方法、接口和并发,是 Go 从“会写”走向“会用”的关键阶段。

一、函数 Function

函数是 Go 代码组织的基本单位。

1. 函数定义

func add(a int, b int) int {
    return a + b
}

调用:

result := add(3, 5)
fmt.Println(result)

2. 参数类型简写

如果多个参数类型相同,可以简写:

func add(a, b int) int {
    return a + b
}

3. 多返回值

Go 非常有特色的一点就是支持多返回值。

func calc(a, b int) (int, int) {
    return a + b, a - b
}

调用:

sum, diff := calc(10, 3)
fmt.Println(sum, diff)

4. 命名返回值

func getInfo() (name string, age int) {
    name = "Tom"
    age = 20
    return
}

虽然命名返回值可以用,但项目中通常不建议滥用,否则会降低代码可读性。

二、错误处理

Go 没有 Java 那种异常机制作为主要错误处理方式,而是强调显式返回 error

func divide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, fmt.Errorf("除数不能为0")
    }
    return a / b, nil
}

调用:

result, err := divide(10, 0)
if err != nil {
    fmt.Println("错误:", err)
    return
}
fmt.Println(result)

这种写法虽然看起来比 try-catch 更“啰嗦”,但优点是逻辑明确,错误路径清晰。

三、方法 Method

Go 没有传统面向对象里的类,但可以给结构体绑定方法。

1. 定义方法

type Person struct {
    Name string
    Age  int
}

func (p Person) SayHello() {
    fmt.Println("你好,我是", p.Name)
}

调用:

p := Person{Name: "小明", Age: 20}
p.SayHello()

2. 值接收者与指针接收者

func (p *Person) GrowUp() {
    p.Age++
}

如果方法需要修改结构体内容,通常使用指针接收者。

四、接口 Interface

接口是 Go 中非常重要的抽象能力。接口定义的是行为,而不是具体实现。

1. 定义接口

type Speaker interface {
    Speak()
}

2. 实现接口

type Dog struct {}

func (d Dog) Speak() {
    fmt.Println("汪汪汪")
}

3. 使用接口

func makeSpeak(s Speaker) {
    s.Speak()
}

func main() {
    d := Dog{}
    makeSpeak(d)
}

在 Go 中,一个类型只要实现了接口中的所有方法,就算实现了该接口,不需要显式声明。这种设计非常灵活。

五、并发编程:goroutine

并发是 Go 的最大亮点之一。

1. 启动 goroutine

package main

import (
    "fmt"
    "time"
)

func task() {
    fmt.Println("goroutine 执行中")
}

func main() {
    go task()
    time.Sleep(time.Second)
}

go task() 表示开启一个新的 goroutine 并发执行任务。

goroutine 非常轻量,可以很方便地创建大量并发任务。

六、channel 通道

如果 goroutine 之间需要通信,可以使用 channel。

1. 创建通道

ch := make(chan int)

2. 发送和接收数据

package main

import "fmt"

func main() {
    ch := make(chan int)

    go func() {
        ch <- 100
    }()

    num := <-ch
    fmt.Println(num)
}

这里子 goroutine 向通道发送数据,主 goroutine 从通道接收数据。

3. 带缓冲通道

ch := make(chan int, 2)
ch <- 1
ch <- 2
fmt.Println(<-ch)
fmt.Println(<-ch)

带缓冲通道允许在缓冲区未满时直接写入,不会立刻阻塞。

七、select 语句

select 用于同时监听多个 channel。

select {
case msg1 := <-ch1:
    fmt.Println("收到:", msg1)
case msg2 := <-ch2:
    fmt.Println("收到:", msg2)
default:
    fmt.Println("暂无数据")
}

它非常适合做超时控制、多路通信处理等。

八、示例:并发打印数字

package main

import (
    "fmt"
    "time"
)

func printNums() {
    for i := 1; i <= 5; i++ {
        fmt.Println("子协程:", i)
        time.Sleep(200 * time.Millisecond)
    }
}

func main() {
    go printNums()

    for i := 1; i <= 5; i++ {
        fmt.Println("主协程:", i)
        time.Sleep(200 * time.Millisecond)
    }
}

运行后可以看到主协程和子协程交替输出,这就是并发执行的表现。

九、学习并发时要注意的问题

初学 Go 并发时,常见问题有:

一是主协程退出太快,导致子 goroutine 来不及执行。
二是通道读写不匹配,容易造成死锁。
三是多个 goroutine 同时操作共享资源时,可能产生竞态问题。
四是看到并发就乱开 goroutine,结果逻辑难以维护。

所以学习并发不能只会写 go 关键字,更重要的是理解协程调度、通道通信和同步控制。

十、总结

函数让代码可复用,方法让结构体具备行为,接口让程序具有更好的扩展性,而 goroutine 和 channel 则让 Go 在并发场景中表现出色。

Go 的很多设计理念都很务实:不用过度复杂的语法,却能把实际开发中最需要的能力做得很顺手。这也是 Go 受到大量后端开发者青睐的重要原因。

posted @ 2026-03-13 21:44  空之匣  阅读(1)  评论(0)    收藏  举报