go.dev
初始化项目
1
| go mod init github.com/{username}/{modulename}
|
这会创建一个 .mod 文件,作为一个 Go 模块的项目文件。
代码的后缀是 .go。
对于入口文件,需要包含以下内容:
1
2
3
4
| package main
func main() {
}
|
要运行一个项目,使用:
变量
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 类型的数据,那它就可以接受 Rectangle 或 Triangle。
错误处理
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)
|