这是「Golang学习」模块的第一篇。用问答的方式记录我和 AI 学习 Go 的过程——有踩坑记录、有顿悟时刻、也有经验总结。


Q1:Go 到底是什么?为什么又要学一门语言?

A: Go(Golang)是 Google 在 2009 年发布的开源编程语言。核心卖点就三个:

特性 说明
编译快 大项目秒级编译,不像 C++ 那样喝杯咖啡回来还在编
并发原生 go 关键字 + channel,协程比线程轻量几个数量级
部署简单 编译出一个二进制文件,扔上去就能跑,无运行时依赖

你已经有 Java/Python/Go 的经验,Go 最适合的场景是:网络服务、微后端、CLI 工具、云基础设施。你的 slg-go 项目就是典型用例。

Q2:Hello World 长什么样?

1
2
3
4
5
6
7
package main

import "fmt"

func main() {
fmt.Println("Hello, Izzy 👋")
}

几点说明:

  • package main — 每个 Go 程序必须有且只有一个 main 包
  • func main() — 程序入口,没有参数、没有返回值(和 C/Java 不同)
  • import — 只引用用到的包,Go 不允许 import 了不用的包(编译直接报错)
  • 分号 — 不需要写,编译器自动插入

Q3:变量声明有几种写法?

A: 四种,从啰嗦到简洁:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 方式一:完整声明(最啰嗦)
var name string = "Izzy"
var age int = 99

// 方式二:类型推断
var name = "Izzy" // 编译器推断为 string
var age = 99 // 编译器推断为 int

// 方式三:短变量声明(最常用)
name := "Izzy"
age := 99

// 方式四:多变量一起声明
var (
name string = "Izzy"
age int = 99
alive bool = true
)

实战建议:函数内部用 :=,包级别用 var。别纠结,肌肉记忆两天就有了。

Q4:Go 的类型系统有什么特别的?

A: 几个关键点:

没有隐式类型转换

1
2
var x int32 = 42
var y int64 = int64(x) // 必须显式转换!不能直接赋值

这对 Java 开发者来说一开始很烦,但能避免一堆隐蔽的 bug。

有指针但没有指针运算

1
2
3
4
5
x := 42
p := &x // p 是 *int 类型(指向 int 的指针)
fmt.Println(*p) // 输出 42(解引用)
*p = 100 // x 现在变成 了
// p++ // ❌ 编译错误!不支持指针运算

比 C 安全,比 Java 更灵活。恰到好处。

有 string 但注意它是不可变的

1
2
3
s := "hello"
// s[0] = 'H' // ❌ 编译错误!字符串不可变
s = "Hello" // ✅ 可以整体重新赋值

Q5:数组和切片(Slice)什么区别?这个坑我踩过!

A: 这是新手第一坑:

1
2
3
4
5
6
7
8
// 数组:固定长度,值类型
var arr [5]int // 长度永远是 5
arr2 := arr // 复制了一份!修改 arr2 不影响 arr

// 切片:动态长度,引用类型
slice := []int{1, 2, 3} // 长度可变
slice2 := slice // 引用同一底层数组!修改 slice2 会影响 slice
slice = append(slice, 4) // 追加元素

口诀:日常开发 99% 用切片,数组几乎只在底层优化时碰到。

Q6:Map 怎么用?

1
2
3
4
5
6
7
8
9
10
11
12
// 创建
scores := map[string]int{
"Izzy": 100,
"AI": 99,
}

// 增删改查
scores["Newbie"] = 60 // 增/改
delete(scores, "AI") // 删
if v, ok := scores["Izzy"]; ok { // 查(带安全检查)
fmt.Println(v) // 100
}

注意那个 ok 返回值——Go 的风格是不抛异常,而是返回两个值。这是 Go 的标志性设计哲学。

Q7:for 是唯一的循环语句?

A: 对,Go 没有 while、也没有 do-while。只有 for,但用法灵活:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 经典 for 循环
for i := 0; i < 5; i++ {
fmt.Println(i)
}

// 当 while 用
for condition() {
// ...
}

// 无限循环
for {
// 配合 break 使用
}

// range 遍历
names := []string{"Alice", "Bob"}
for i, name := range names {
fmt.Printf("%d: %s\n", i, name)
}

少记几个关键字,也挺好。

Q8:函数可以返回多个值?

A: 可以,这是 Go 的杀手级特性之一:

1
2
3
4
5
6
7
8
9
10
11
12
13
func divide(a, b float64) (float64, error) {
if b == 0 {
return 0, errors.New("division by zero")
}
return a / b, nil
}

// 使用
result, err := divide(10, 3)
if err != nil {
log.Fatal(err)
}
fmt.Println(result) // 3.333...

还可以给返回值命名(命名返回值):

1
2
3
4
5
func rect(l, w float64) (area, perimeter float64) {
area = l * w
perimeter = 2 * (l + w)
return // 裸 return,自动返回 area 和 perimeter
}

命名返回值适合返回值多的时候用,代码更清晰。

Q9:defer 是什么?为什么说它很优雅?

A: defer 延迟执行一个函数调用,直到当前函数返回前才执行:

1
2
3
4
5
6
7
8
9
10
func readFile(path string) error {
f, err := os.Open(path)
if err != nil {
return err
}
defer f.Close() // 函数结束时自动关闭文件

// ... 使用 f 做各种操作 ...
return nil // 此时 f.Close() 会自动被调用
}

实际效果:

  • 不管函数怎么退出(正常 return、panic),defer 都会执行
  • 多个 defer 按 LIFO(后进先出) 顺序执行
  • 常用于:关闭文件、释放锁、恢复状态

这比 Java 的 try-finally 写起来舒服多了。

Q10:goroutine 和 channel 是什么?

A: Go 并发的两大基石。

goroutine — 极轻量级线程

1
2
3
4
go func() {
fmt.Println("我在后台跑")
}()
fmt.Println("主程序继续执行")

启动一个 goroutine 只需要 2KB 栈内存(对比操作系统线程的几 MB)。轻松创建百万级并发。

channel — goroutine 之间通信

1
2
3
4
5
6
7
8
9
10
ch := make(chan string)

// 发送方
go func() {
ch <- "hello" // 发送数据到 channel
}()

// 接收方
msg := <-ch // 从 channel 接收数据
fmt.Println(msg) // hello

核心理念:

Do not communicate by sharing memory; instead, share memory by communicating.
不要通过共享内存来通信,而要通过通信来共享内存。

这意味着:不用加锁了!数据通过 channel 传递,天然安全。


下期预告

下一篇我们会深入聊:结构体与方法(struct & method)、接口(interface)的鸭子类型设计、以及 error 处理的最佳实践。

TODO: 如果你对某个话题特别感兴趣,或者想跳过某些基础内容直接进阶,随时告诉我——这个学习节奏由你来定。