01. 变量、类型与控制流

01. 变量、类型与控制流

这一节先把 Go 最常见的语法元素过一遍。你不需要一次记住全部细节,但需要建立一种感觉:Go 倾向于简单、显式、可读。

本节目标

  • 理解 Go 的变量声明方式和零值
  • 熟悉常见基础类型、数组、切片、映射
  • 掌握 ifforswitch 这些控制流写法
  • 能看懂项目里最基础的数据处理代码

变量声明

Go 里最常见的声明方式有三种:

go 复制代码
// 显式指定类型
var projectName string = "commit-dashboard"

// 让编译器推断类型
var port = 8080

// 函数内部最常见的短变量声明
enabled := true

// 常量
const defaultPageSize = 10

经验上可以这么理解:

  • 函数内优先使用 :=
  • 包级变量通常使用 var
  • 不会变化的值使用 const

基础类型与零值

Go 的变量在声明后即使没有显式赋值,也会自动拥有零值:

类型 示例 零值
string "hello" ""
int / int64 42 0
bool true false
float64 3.14 0
指针 *Repo nil
切片 []string nil
map map[string]int nil

示例:

go 复制代码
var name string
var count int
var tags []string

fmt.Println(name == "")   // true
fmt.Println(count == 0)   // true
fmt.Println(tags == nil)  // true

这也是为什么在 Go 里我们经常能通过"是否为零值"判断一个字段有没有被设置。

数组:固定长度的序列

Go 里确实有数组,但它和切片不是一回事。

go 复制代码
var top3 [3]string
top3[0] = "api"
top3[1] = "web"
top3[2] = "worker"

fmt.Println(len(top3)) // 3

也可以直接初始化:

go 复制代码
languages := [3]string{"Go", "TypeScript", "SQL"}

数组的特点是:

  • 长度是类型的一部分,[3]string[4]string 不是同一种类型
  • 长度固定,创建后不能扩容
  • 更适合少量、固定长度的数据

在日常 Web 项目里,数组没有切片常用,但理解它能帮助你看懂"为什么切片不是数组本身"。

切片:最常用的集合类型

切片可以理解成"动态数组",在 Go 里非常常见。

go 复制代码
repos := []string{"api", "web"}
repos = append(repos, "worker")

fmt.Println(len(repos)) // 3
fmt.Println(repos[0])   // api

也可以用 make 创建:

ini 复制代码
scores := make([]int, 3, 5)
scores[0] = 95
scores[1] = 88
scores[2] = 100

这里的 make([]int, 3, 5) 表示:

  • 长度 len3
  • 容量 cap5

切片还支持再次切片:

go 复制代码
repos := []string{"api", "web", "worker", "docs"}
backend := repos[0:3]

fmt.Println(backend) // [api web worker]

配合 range 使用最顺手:

go 复制代码
for index, repo := range repos {
    fmt.Println(index, repo)
}

如果你不需要索引,可以用 _ 忽略:

go 复制代码
for _, repo := range repos {
    fmt.Println(repo)
}

你可以先用一句话记住数组和切片的区别:

  • 数组是固定长度的数据本体
  • 切片是对一段连续数据的轻量视图

所以工程代码里我们几乎总是在用切片,比如 []string[]Repository[]Commit

map:键值对结构

Go 的 map 很适合做查表、聚合、计数:

go 复制代码
commitCount := map[string]int{
    "alice": 12,
    "bob":   8,
}

commitCount["carol"] = 5
fmt.Println(commitCount["alice"])

判断键是否存在时,常见写法是:

go 复制代码
count, ok := commitCount["david"]
if !ok {
    fmt.Println("david 不存在")
}
fmt.Println(count)

if:支持前置语句

Go 的 if 经常和错误处理一起使用:

go 复制代码
if err := doSomething(); err != nil {
    return err
}

这种写法非常常见,你在后面的仓储层、处理器里会频繁看到。

for:Go 唯一的循环

Go 没有 while,统一使用 for

css 复制代码
for i := 0; i < 3; i++ {
    fmt.Println(i)
}

也可以只写条件:

css 复制代码
count := 0
for count < 3 {
    count++
}

还可以写成"无限循环":

arduino 复制代码
for {
    // 常用于 worker、消息循环、重试逻辑
}

switch:更整洁的分支判断

go 复制代码
switch env := "dev"; env {
case "dev":
    fmt.Println("开发环境")
case "prod":
    fmt.Println("生产环境")
default:
    fmt.Println("未知环境")
}

Go 的 switch 默认不会自动贯穿到下一个 case,所以通常更安全。

一个贴近项目的小例子

下面这个例子模拟"读取分页参数并修正默认值"的思路:

go 复制代码
type RepoQuery struct {
    Page     int
    PageSize int
    Keywords string
}

func normalizeQuery(q RepoQuery) RepoQuery {
    if q.Page < 1 {
        q.Page = 1
    }

    if q.PageSize < 1 || q.PageSize > 100 {
        q.PageSize = 10
    }

    switch q.Keywords {
    case "":
        fmt.Println("无搜索关键词")
    default:
        fmt.Println("按关键词过滤:", q.Keywords)
    }

    return q
}

这类"接收数据 -> 处理默认值 -> 分支判断"的逻辑,在 Web 后端里非常常见。

常见易错点

  • 数组和切片不是同一种类型,不能直接混着用
  • nil 的切片可以 append,但 nil 的 map 不能直接赋值
  • := 只能在函数内部使用
  • for range 拿到的是副本,修改元素时要注意是否真的改到了原值
  • Go 没有三元表达式,条件判断请老老实实写 if

小结

你现在需要记住的核心点只有三个:

  1. Go 喜欢显式和简单的写法。
  2. 零值是 Go 里非常重要的默认行为。
  3. 切片、map、if err != nilfor range 是最常见的代码形态,而数组更偏底层和固定长度场景。
相关推荐
冒充野生程序猿2 小时前
04. 并发基础:goroutine、channel、context
go
程序员爱钓鱼3 小时前
Go并发同步核心库:syn 包深度指南
后端·面试·go
ithadoop1 天前
go面试知识点分类整理
golang·go
葡萄城技术团队1 天前
Go 后端开发必知的 10 条最佳实践
go
ReSearch1 天前
工业物联网的“瘦身”革命:Go 实现 20MB 级边缘存储,基于 LSM-Tree 的深度定制实践
数据库·go
程序员爱钓鱼1 天前
Go图像处理基础: image包深度指南
后端·面试·go
江湖十年2 天前
Go 并发控制:sync.Pool 详解
后端·面试·go
程序员爱钓鱼2 天前
Go高性能缓冲IO详解: bufio包深度指南
后端·面试·go
哈里谢顿3 天前
golang常见面试题总结
面试·go