Featured image of post 速通编程语言|Go

速通编程语言|Go

假设你已经有OPP基础,那么这篇文章可以帮你快速迁移到Go

go.dev

初始化项目

1
go mod init github.com/{username}/{modulename}

这会创建一个 .mod 文件,作为一个 Go 模块的项目文件。
代码的后缀是 .go

对于入口文件,需要包含以下内容:

1
2
3
4
package main

func main() {
}

要运行一个项目,使用:

1
go run .

变量

Go 是自动推断变量类型的语言,但它的推断发生在编译期,是静态类型。

如果在函数里,这样:

1
2
3
4
5
6
func main() {
    a := 1
    var b = 2 // 都可以
    var c int // 如果没有初始值,就要指定类型
    c = 3 
}

如果在函数外(全局),就只能使用 var 来定义。

运算符

和大部分编程语言一致。

流程控制

if

1
2
3
4
if b := 3; a > 10 {
} else if a > 5 {
} else {
}

switch

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
a := 10
switch a {
case 1:
    // do
    fallthrough // 继续匹配下面的 case,默认直接跳出
case 5:
    // do
default:
    // do
}

for

1
2
for i := 1; i < 5; i++ {
}

函数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
func sum(a, b int) (int, int) { // 参数列表内如果多个值类型一致可以只写最后一个
    return a+b, a-b
}

func calculate(a int, b int, transform func(int, int) int) int {
    return transform(a, b)
}

func main() {
    a, b := sum(1, 2)
    
    trans := func(x, y int) int {
        return x * y
    }
    c := calculate(a, b, trans)
}

数组和字典

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
b := [5]int{1, 2, 3, 4, 5} // 长度固定的数组
a := make([]int, 0)        // 可变长度数组(slice),int 类型,初始长度 0
a := []int{0, 9}           // 也可以这样
a = append(a, 1, 2, 3, 4, 5) // 追加元素
a[0] = 666                 // 修改元素

m := map[string]int{
    "a": 1,
    "b": 2,
}

n := make(map[string]int)
n["c"] = 3

结构

1
2
3
4
5
6
7
8
9
type Point struct {
    x int
    y int
}

func main() {
    p := Point{1, 2}
    p.y = 3
}

结构可以添加方法,和函数类似,区别是要说明作用范围:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
func (p Point) SetPoint2(x, y int) { // 注意这里是值传递(复制),修改不生效!
    p.x = x
    p.y = y
}

func (p *Point) SetPoint(x, y int) { // 使用指针接收者,可以修改原结构
    p.x = x
    p.y = y
}

p.SetPoint(3, 4)

指针

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
type Point struct {
    x int
    y int
}

func main() {
    p := Point{1, 2}
    p.y = 3
    q := p      // 复制结构
    po := &p    // 创建指针
    po.x = 10   // 自动解引用
    fmt.Println(*po) // 手动解引用
}

默认 = 使用复制策略。

接口

可以在结构里声明一组接口,要求使用者必须实现。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
type Shape interface {
    Print()
}

type Rectangle struct{}
type Triangle struct{}

func (r Rectangle) Print() { // 直接定义同名方法即可,会自动匹配 interface
    fmt.Println("矩形")
}

func (t Triangle) Print() {
    fmt.Println("三角形")
}

注意:Go 的 Interface 主要是用于多态性的实现,而不是约束。
如果一个函数接收 Shape 类型的数据,那它就可以接受 RectangleTriangle

错误处理

Go 的错误处理通常通过函数返回多个值,而没有抛出异常的系统。

1
2
3
4
5
6
n, err := fmt.Println("TEST")
if err == nil {
    // 正常逻辑
} else {
    // 错误处理
}

协程(Goroutines)

协程在 Go 中叫做 Goroutines,可以在函数之外开一个新的地方执行函数。

1
2
3
func main() {
    go func1()
}

为了在不同协程间传递信息,于是就有了 Channels,有点像共享的队列。读写时阻塞。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
func func1(ch chan string) {
    ch <- "func 1"
}
func func2(ch chan string) {
    ch <- "func 2"
}

func main() {
    ch := make(chan string)
    go func1(ch)
    res1 := <-ch
    go func2(ch)
    res2 := <-ch
    fmt.Println(res1, res2)
}

Go 没有提供手动管理线程的功能,目的是为了轻量方便。

控制并发数量

方法一:使用带缓冲的 Channel 作为信号量

1
2
3
4
5
6
7
8
9
sem := make(chan struct{}, 20) // 最多 20 个并发

for i := 0; i < 10000; i++ {
    sem <- struct{}{} // 获取一个“许可”,若满则阻塞
    go func(id int) {
        defer func() { <-sem }() // 释放许可
        doWork(id)
    }(i)
}

方法二:使用 golang.org/x/sync/semaphore

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
import "golang.org/x/sync/semaphore"

sem := semaphore.NewWeighted(20) // 最多 20 个并发

for i := 0; i < 10000; i++ {
    sem.Acquire(context.Background(), 1) // 阻塞直到获得许可
    go func(id int) {
        defer sem.Release(1) // 释放许可
        doWork(id)
    }(i)
}

也可以使用类似线程池的设计,Worker 是一直循环的 goroutine。

一些常用包

fmt

输出文本到控制台:

1
2
fmt.Println("Hello, world!")
fmt.Printf("Value: %d\n", 42)
本文采用 CC BY 4.0 许可协议,转载请注明出处。
使用 Hugo 构建
主题 StackJimmy 设计