324 lines
7.1 KiB
Markdown
324 lines
7.1 KiB
Markdown
# 第 1 章 Go 环境与基础语法
|
||
|
||
> 学完本章你将:装好 Go、理解 go mod、掌握变量/类型/流程/函数/结构体/接口/错误处理,能读懂本项目 90% 的非并发代码。
|
||
|
||
## 1.1 安装与环境
|
||
|
||
### 安装
|
||
|
||
- 官网 https://go.dev/dl 下载对应系统安装包
|
||
- 验证:`go version` 应输出 `go1.26.x`
|
||
- 本项目要求 `go 1.26.2`(见 `go.mod` 第一行)
|
||
|
||
### 环境变量
|
||
|
||
```bash
|
||
go env GOPATH GOROOT GOPROXY
|
||
# GOPATH Workspace 目录(Go 1.11 后已弱化,保留兼容)
|
||
# GOROOT Go 安装目录
|
||
# GOPROXY 代理,国内建议:
|
||
go env -w GOPROXY=https://goproxy.cn,direct
|
||
go env -w GOPRIVATE=""
|
||
```
|
||
|
||
### 编辑器
|
||
|
||
推荐 VS Code + Go 插件(gopls),或 GoLand。安装后执行 `go install golang.org/x/tools/gopls@latest`。
|
||
|
||
## 1.2 go mod 模块管理
|
||
|
||
Go 1.11+ 用 `go.mod` 管理依赖,替代 GOPATH。本项目 `go.mod` 开头:
|
||
|
||
```go
|
||
module streambox
|
||
go 1.26.2
|
||
```
|
||
|
||
常用命令(对应本项目 `Makefile`):
|
||
|
||
```bash
|
||
go mod init streambox # 初始化(已存在无需重复)
|
||
go mod tidy # 整理依赖,对应 make tidy
|
||
go get github.com/gin-gonic/gin@latest # 添加/升级依赖
|
||
go list -m all # 查看所有依赖
|
||
go mod why github.com/gin-gonic/gin # 查询谁依赖它
|
||
```
|
||
|
||
`go.sum` 是依赖校验和,提交到 Git,不要手改。
|
||
|
||
## 1.3 Hello World
|
||
|
||
创建 `main.go`:
|
||
|
||
```go
|
||
package main
|
||
|
||
import "fmt"
|
||
|
||
func main() {
|
||
fmt.Println("hello, streambox")
|
||
}
|
||
```
|
||
|
||
运行 `go run main.go`。`package main` + `func main()` 是可执行程序入口。
|
||
|
||
## 1.4 变量与常量
|
||
|
||
```go
|
||
// 显式声明
|
||
var name string = "streambox"
|
||
var port int = 8080
|
||
var debug bool
|
||
|
||
// 类型推导
|
||
var addr = ":8080"
|
||
|
||
// 短声明(函数内常用)
|
||
count := 42
|
||
msg := "ok"
|
||
|
||
// 批量声明
|
||
var (
|
||
host = "0.0.0.0"
|
||
timeout = 10
|
||
)
|
||
|
||
// 常量
|
||
const AppName = "streambox"
|
||
const (
|
||
ModeDebug = "debug"
|
||
ModeRelease = "release"
|
||
)
|
||
|
||
// iota 枚举
|
||
const (
|
||
CodeSuccess = iota // 0
|
||
CodeBadRequest // 1
|
||
CodeInternal // 2
|
||
)
|
||
```
|
||
|
||
零值:`int` 为 0,`string` 为 "",`bool` 为 false,指针/slice/map/channel/interface 为 nil。
|
||
|
||
## 1.5 基础类型
|
||
|
||
| 类型 | 说明 | 示例 |
|
||
|------|------|------|
|
||
| `bool` | 布尔 | `true/false` |
|
||
| `int/int8/int16/int32/int64` | 有符号整型 | `int` 随平台 32/64 位 |
|
||
| `uint/...` | 无符号整型 | `uint port = 8080` |
|
||
| `float32/float64` | 浮点 | `float64` 常用 |
|
||
| `string` | 字符串,不可变,UTF-8 | `"hello"` |
|
||
| `byte` (alias uint8) | 字节 | `'a'` |
|
||
| `rune` (alias int32) | Unicode 码点 | `'中'` |
|
||
|
||
字符串操作:
|
||
|
||
```go
|
||
s := "hello"
|
||
len(s) // 字节长度 5
|
||
s[1] // byte 'e'
|
||
for _, r := range s { // 按 rune 遍历
|
||
fmt.Printf("%c", r)
|
||
}
|
||
s + " world" // 拼接
|
||
fmt.Sprintf("port=%d", 8080)
|
||
```
|
||
|
||
## 1.6 复合类型:数组、切片、Map
|
||
|
||
```go
|
||
// 数组(定长,很少直接用)
|
||
var a [3]int = [3]int{1,2,3}
|
||
|
||
// 切片(动态数组,最常用)
|
||
s := []int{1, 2, 3}
|
||
s = append(s, 4)
|
||
sub := s[1:3] // [2,3] 左闭右开
|
||
s2 := make([]int, 0, 8) // len 0 cap 8
|
||
|
||
// Map
|
||
m := map[string]int{"a": 1}
|
||
m["b"] = 2
|
||
v, ok := m["c"] // ok 判断是否存在
|
||
delete(m, "a")
|
||
for k, v := range m { fmt.Println(k, v) }
|
||
m2 := make(map[string]string)
|
||
```
|
||
|
||
注意:slice/map 作为参数传递时是引用语义;nil slice 可 append,nil map 不可写入。
|
||
|
||
## 1.7 流程控制
|
||
|
||
```go
|
||
// if
|
||
if err := do(); err != nil {
|
||
log.Fatal(err)
|
||
} else {
|
||
fmt.Println("ok")
|
||
}
|
||
|
||
// for 只有 for
|
||
for i := 0; i < 3; i++ {}
|
||
for range s {}
|
||
for { break } // 死循环
|
||
|
||
// switch
|
||
switch mode {
|
||
case "debug":
|
||
fmt.Println("debug")
|
||
case "release":
|
||
fmt.Println("release")
|
||
default:
|
||
fmt.Println("unknown")
|
||
}
|
||
|
||
// switch 带表达式
|
||
switch {
|
||
case port < 1024:
|
||
fmt.Println("privileged")
|
||
default:
|
||
fmt.Println("ok")
|
||
}
|
||
|
||
// defer 延迟执行(LIFO),常用于资源释放
|
||
f, _ := os.Open("config.yaml")
|
||
defer f.Close()
|
||
defer logger.Sync() // 本项目 main.go 中就有
|
||
```
|
||
|
||
## 1.8 函数
|
||
|
||
```go
|
||
// 普通函数
|
||
func Add(a, b int) int { return a + b }
|
||
|
||
// 多返回值(Go 错误处理的核心)
|
||
func Divide(a, b int) (int, error) {
|
||
if b == 0 {
|
||
return 0, fmt.Errorf("divide by zero")
|
||
}
|
||
return a / b, nil
|
||
}
|
||
|
||
// 命名返回值
|
||
func Split(s string) (first, second string) {
|
||
// ...
|
||
return
|
||
}
|
||
|
||
// 可变参数
|
||
func Sum(nums ...int) int {
|
||
n := 0
|
||
for _, v := range nums { n += v }
|
||
return n
|
||
}
|
||
|
||
// 函数作为值
|
||
fn := func(s string) string { return s + "!" }
|
||
type HandlerFunc func(*gin.Context) // Gin 中大量使用
|
||
```
|
||
|
||
## 1.9 结构体与方法
|
||
|
||
```go
|
||
// 定义
|
||
type ServerConfig struct {
|
||
Port int `yaml:"port" mapstructure:"port"`
|
||
Mode string `yaml:"mode"`
|
||
ReadTimeout string `yaml:"read_timeout"`
|
||
WriteTimeout string `yaml:"write_timeout"`
|
||
}
|
||
|
||
// 方法(接收者为值或指针)
|
||
func (c ServerConfig) Addr() string {
|
||
return fmt.Sprintf(":%d", c.Port)
|
||
}
|
||
func (c *ServerConfig) SetPort(p int) { c.Port = p }
|
||
|
||
// 初始化
|
||
cfg := ServerConfig{Port: 8080, Mode: "debug"}
|
||
cfg2 := &ServerConfig{Port: 8080} // 指针
|
||
|
||
// 嵌入(组合,非继承)
|
||
type Config struct {
|
||
Server ServerConfig `mapstructure:"server"`
|
||
Log LogConfig
|
||
}
|
||
```
|
||
|
||
本项目 `config/config.go` 就是典型的结构体 + viper 绑定。
|
||
|
||
## 1.10 接口
|
||
|
||
接口是 Go 多态的核心,隐式实现:
|
||
|
||
```go
|
||
type Pinger interface {
|
||
Ping() string
|
||
}
|
||
|
||
// 只要实现了 Ping() string,就实现了 Pinger,无需显式声明
|
||
type PingService struct{}
|
||
func (s *PingService) Ping() string { return "pong" }
|
||
|
||
var _ Pinger = (*PingService)(nil) // 编译期校验
|
||
|
||
// 空接口 interface{}(Go 1.18 后别名 any)可表示任意类型
|
||
var x any = 42
|
||
x = "hello"
|
||
|
||
// 类型断言
|
||
if s, ok := x.(string); ok { fmt.Println(s) }
|
||
|
||
// type switch
|
||
switch v := x.(type) {
|
||
case string: fmt.Println("string", v)
|
||
case int: fmt.Println("int", v)
|
||
}
|
||
```
|
||
|
||
Gin 的 `gin.HandlerFunc` 本质是 `func(*gin.Context)`,也是一种函数类型接口。
|
||
|
||
## 1.11 错误处理
|
||
|
||
Go 无异常,错误即值:
|
||
|
||
```go
|
||
f, err := os.Open("config.yaml")
|
||
if err != nil {
|
||
return fmt.Errorf("open config: %w", err) // %w 包装,支持 errors.Is/As
|
||
}
|
||
defer f.Close()
|
||
|
||
// 自定义错误
|
||
type AppError struct {
|
||
Code int
|
||
Msg string
|
||
}
|
||
func (e *AppError) Error() string { return e.Msg }
|
||
|
||
// 判断
|
||
if errors.Is(err, os.ErrNotExist) { ... }
|
||
var appErr *AppError
|
||
if errors.As(err, &appErr) { fmt.Println(appErr.Code) }
|
||
|
||
// panic/recover 仅用于不可恢复错误,业务错误不要用
|
||
// 本项目 middleware/recovery.go 用 recover 捕获 panic,避免进程崩溃
|
||
```
|
||
|
||
## 1.12 包与可见性
|
||
|
||
- 同目录同 `package` 名
|
||
- 大写开头导出(Public),小写私有:`logger.Log` 可导出,`join` 不可(见 `middleware/cors.go`)
|
||
- 导入:`import "streambox/pkg/logger"`,`import gin "github.com/gin-gonic/gin"`
|
||
|
||
## 1.13 动手练习
|
||
|
||
1. 编写 `fib(n int) int` 递归与迭代两版。
|
||
2. 定义 `User` 结构体,实现 `String() string` 方法。
|
||
3. 模仿 `pkg/response` 封装 `Result`,编写 `Success`/`Fail` 并用 `encoding/json` 序列化验证。
|
||
|
||
下一章将学习并发与工程化。
|
||
|