第01章:Go 语言极速入门(为 GORM 学习准备)
本章目标
学完本章后,你将能够:
- 在自己的电脑上安装 Go 并配置好开发环境
- 理解 Go 的基本语法:变量、函数、结构体、指针、接口
- 使用
go mod init初始化一个 Go 项目 - 看懂并编写简单的 Go 程序
- 理解为什么结构体和指针是 GORM 的核心基础
前置知识
- 本章是第1章,无需任何 Go 语言前置知识
- 基本的编程概念(变量、函数——你学过的任何语言都可以)
- 会使用命令行(终端/cmd)
1.1 安装 Go 与配置环境
下载与安装
Go 语言的官网是 https://go.dev,下载页在 https://go.dev/dl。
Windows 用户:
- 下载
.msi安装包(例如go1.21.windows-amd64.msi) - 双击运行,一路 "Next" 即可
- 默认安装路径:
C:\Program Files\Go\
Mac 用户:
# 方式一:下载 .pkg 安装包
# 方式二:使用 Homebrew
brew install goLinux 用户:
# 下载并解压
wget https://go.dev/dl/go1.21.linux-amd64.tar.gz
sudo tar -C /usr/local -xzf go1.21.linux-amd64.tar.gz
# 添加到 PATH(写入 ~/.bashrc 或 ~/.zshrc)
export PATH=$PATH:/usr/local/go/bin验证安装
打开终端(命令行),输入:
go version如果看到类似 go version go1.21.0 windows/amd64 的输出,说明安装成功了。
三个关键环境变量
# 查看所有 Go 环境变量
go env
# 三个最重要的:
GOROOT # Go 安装路径,如 C:\Program Files\Go
GOPATH # 工作目录(Go Modules 时代已不那么重要)
GO111MODULE # 模块模式,默认 on(不用管)现代 Go 开发:从 Go 1.16 开始,Go Modules 是默认的依赖管理方式。你只需要在任意目录创建项目,用
go mod init初始化即可,不需要把所有代码放在 GOPATH 下。
1.2 你的第一个 Go 程序
项目初始化
# 创建一个目录作为你的项目
mkdir hello-gorm
cd hello-gorm
# 初始化 Go Module(module 名字随意,建议用目录名)
go mod init hello-gorm执行后会生成一个 go.mod 文件,内容大致如下:
module hello-gorm
go 1.21Hello World
创建文件 main.go:
package main
import "fmt"
func main() {
fmt.Println("Hello, GORM!")
}运行:
go run main.go
# 输出:Hello, GORM!程序结构解析
我们来逐行拆解这个最简单的 Go 程序:
| 代码 | 含义 |
|---|---|
package main | 声明这个文件属于 main 包。可执行程序的入口必须在 main 包中 |
import "fmt" | 导入标准库的 fmt 包(用于格式化输入输出) |
func main() | main 函数——程序的入口,Go 从这里开始执行 |
fmt.Println(...) | 调用 fmt 包的 Println 函数打印一行文本 |
关键认知:Go 语言中,每个
.go文件的第一行必须是package 包名。一个目录下的所有.go文件必须属于同一个包。
1.3 变量与基本数据类型
变量声明
Go 有几种声明变量的方式:
package main
import "fmt"
func main() {
// 方式一:完整声明(var 变量名 类型 = 值)
var name string = "张三"
// 方式二:类型推断(var 变量名 = 值)
var age = 25
// 方式三:短变量声明(最常用!只能在函数内使用)
score := 92.5
// 方式四:先声明后赋值
var city string
city = "北京"
fmt.Println(name, age, score, city)
// 输出:张三 25 92.5 北京
}短变量声明
:=是 Go 中最常用的声明方式,它自动推断类型。但注意::=只能在函数内部使用,包级别的变量必须用var。
基本数据类型
// 整数
var a int = 10 // 平台相关(32位或64位)
var b int64 = 100 // 明确的64位整数
var c uint = 20 // 无符号整数(不能是负数)
// 浮点数
var d float64 = 3.14
// 字符串
var e string = "Hello GORM"
// 布尔值
var f bool = true
// 字节(byte 就是 uint8)
var g byte = 'A'Go 和 MySQL 类型的对应关系(记住这个表,学 GORM 时会反复用到):
| Go 类型 | MySQL 类型 | 说明 |
|---|---|---|
int, uint | BIGINT | 平台相关 |
int8 | TINYINT | -128 到 127 |
int16 | SMALLINT | |
int32 | INT | |
int64 | BIGINT | |
float32 | FLOAT | |
float64 | DOUBLE | |
string | VARCHAR(255) 或 TEXT | GORM 默认 VARCHAR(255) |
bool | TINYINT(1) | true → 1, false → 0 |
time.Time | DATETIME | 时间类型 |
[]byte | BLOB | 二进制数据 |
零值(Zero Value)
Go 中每个类型都有一个"零值"——变量声明但未赋值时的默认值。这是 GORM 最重要的概念之一,务必记住!
var i int // 0
var f float64 // 0.0
var s string // ""(空字符串)
var b bool // false
var p *int // nil(指针的零值)⚠️ GORM 零值陷阱:当
Age字段的值为0或Name字段的值为""时,GORM 可能认为"这个字段没有设置",从而在更新/查询时跳过它。这是初学者最容易踩的坑,后续章节会反复讲解。
1.4 函数
基本函数
// 无参数,无返回值
func sayHello() {
fmt.Println("你好!")
}
// 有参数,有返回值
func add(a int, b int) int {
return a + b
}
// 参数类型相同可以简写
func multiply(a, b int) int {
return a * b
}
func main() {
sayHello()
sum := add(3, 5)
fmt.Println("3 + 5 =", sum) // 3 + 5 = 8
}多返回值(Go 的特色)
// 返回两个值:结果和错误
func divide(a, b float64) (float64, error) {
if b == 0 {
return 0, fmt.Errorf("除数不能为零")
}
return a / b, nil
}
func main() {
result, err := divide(10, 3)
if err != nil {
fmt.Println("出错了:", err)
return
}
fmt.Println("10 / 3 =", result)
}多返回值是 Go 错误处理的核心模式。几乎所有 Go 函数都返回
(结果, error)对。GORM 的每个数据库操作也都遵循这个模式。
方法(接收者函数)
Go 没有类(class),但可以给类型定义方法:
type User struct {
Name string
Age int
}
// 给 User 类型定义一个方法
func (u User) SayHello() {
fmt.Printf("你好,我是 %s,今年 %d 岁\n", u.Name, u.Age)
}
func main() {
user := User{Name: "张三", Age: 25}
user.SayHello() // 你好,我是张三,今年25岁
}1.5 结构体(struct)—— GORM 的核心
结构体是 GORM 最重要的 Go 语言基础。在 GORM 中,每个数据库表对应一个结构体,每行数据对应一个结构体实例。
定义结构体
// 一个"用户"结构体——对应数据库中的 users 表
type User struct {
ID uint // 主键
Name string // 用户名
Age int // 年龄
Email string // 邮箱
}创建和使用结构体
func main() {
// 方式一:按字段名初始化(推荐,顺序无关)
user1 := User{
Name: "张三",
Age: 25,
Email: "zhangsan@example.com",
}
// 方式二:按顺序初始化(不推荐,容易出错)
user2 := User{1, "李四", 30, "lisi@example.com"}
// 方式三:零值初始化,后续再赋值
var user3 User
user3.Name = "王五"
user3.Age = 22
// 访问字段
fmt.Println(user1.Name) // 张三
fmt.Println(user2.Age) // 30
}结构体标签(Tag)
结构体标签是 GORM 最核心的机制——通过标签告诉 GORM 字段如何映射到数据库列:
type User struct {
ID uint `gorm:"primaryKey"`
Name string `gorm:"column:user_name;type:varchar(100);not null"`
Age int `gorm:"default:18"`
Email string `gorm:"uniqueIndex"`
}标签的格式是:`gorm:"选项1;选项2;..."`,每个选项用分号分隔。别着急,第06章会详细讲解每一个标签的含义。
关键认知:结构体标签(Struct Tag)在 Go 中是用反引号
`包裹的字符串,可以被反射(Reflection)读取。GORM 通过读取标签来决定如何建表、如何查询。写标签时注意:反引号内不能包含反引号,键值对之间用分号分隔。
1.6 指针 —— GORM 的第二核心
指针是 GORM 另一个最重要的 Go 语言基础。GORM 的几乎所有操作都需要传指针。
什么是指针
指针存储的是另一个变量的内存地址,而不是值本身。
func main() {
name := "张三"
// & 取地址
p := &name // p 是指向 name 的指针,类型是 *string
fmt.Println(p) // 0xc000010050(内存地址,每次运行不同)
fmt.Println(*p) // 张三(* 解引用,获取指针指向的值)
// 通过指针修改原变量的值
*p = "李四"
fmt.Println(name) // 李四(原变量也被改了!)
}为什么 GORM 需要指针
var user User
// ❌ 错误:传值
db.Create(user) // GORM 收到的是 user 的副本,无法把生成的 ID 写回
// ✅ 正确:传指针
db.Create(&user) // GORM 收到的是 user 的地址,可以把 ID 写入 user.ID
fmt.Println(user.ID) // 可以看到数据库生成的自增ID记住:GORM 中几乎所有方法(Create、Find、First、Update、Delete...)都需要传指针。因为 GORM 需要修改你传入的结构体(比如回填自增 ID、填充查询结果)。如果传值,GORM 只能修改副本,你拿不到结果。
指针类型字段
在结构体中使用指针类型字段,可以区分"零值"和"NULL":
type User struct {
Name string // 零值是 "",数据库存空字符串
Age *int // 零值是 nil,数据库存 NULL
}
func main() {
age := 25
user1 := User{Name: "张三", Age: &age} // Age 有值,存 25
user2 := User{Name: "李四"} // Age 是 nil,存 NULL
fmt.Println(user1.Age) // 0xc000012345(内存地址)
fmt.Println(user2.Age) // <nil>
}这是 GORM 中区分"我没设置"和"我设置为零"的关键手段。
1.7 接口(interface)—— 最简理解
Go 的接口定义了一组方法签名,任何实现了这些方法的类型都自动满足该接口。
// 定义一个接口
type Speaker interface {
Speak() string
}
// Dog 类型实现了 Speak() 方法 → 自动满足 Speaker 接口
type Dog struct{}
func (d Dog) Speak() string { return "汪汪!" }
// Cat 类型也实现了 Speak() 方法 → 也满足 Speaker 接口
type Cat struct{}
func (c Cat) Speak() string { return "喵喵!" }
// 接受接口类型的函数
func MakeSound(s Speaker) {
fmt.Println(s.Speak())
}
func main() {
MakeSound(Dog{}) // 汪汪!
MakeSound(Cat{}) // 喵喵!
}在 GORM 中你不需要自己写太多接口,但理解接口概念有助于看懂 GORM 的 Scanner/Valuer 等高级特性。
1.8 包(package)与导入(import)
包的规则
// main.go
package main // 可执行程序必须是 main 包
import (
"fmt" // 标准库
"hello-gorm/models" // 自己项目的包
"gorm.io/gorm" // 第三方库(GORM)
)
func main() {
user := models.User{Name: "张三"} // 使用 models 包中的 User
fmt.Println(user)
}// models/user.go
package models // 包名和目录名通常一致
type User struct {
Name string
}标识符的可见性
Go 用首字母大小写控制可见性(没有 public/private 关键字):
type User struct {
Name string // 大写开头 → 公开(其他包可以访问)
email string // 小写开头 → 私有(只能本包内访问)
}
func GetUser() User { ... } // 公开函数
func validate() error { ... } // 私有函数这对 GORM 很重要:结构体字段必须是大写开头才能被 GORM 映射到数据库列。小写字段 GORM 直接忽略。
1.9 错误处理
Go 没有 try-catch,错误通过返回值传递:
// 标准错误处理模式
result, err := someFunction()
if err != nil {
// 处理错误
fmt.Println("出错了:", err)
return
}
// 正常使用 result在 GORM 中:
result := db.Create(&user)
if result.Error != nil {
fmt.Println("插入失败:", result.Error)
return
}
fmt.Printf("成功插入,影响行数:%d,ID:%d\n", result.RowsAffected, user.ID)常见错误
错误1::= 不能用于包级别
// ❌ 错误
package main
name := "张三" // 编译错误!:= 只能在函数内使用
// ✅ 正确
package main
var name = "张三" // 包级别用 var错误2:忘记传指针给 GORM
// ❌ 错误
var user User
db.First(user, 1) // GORM 无法把查询结果写入 user
fmt.Println(user.Name) // 永远是空的!
// ✅ 正确
var user User
db.First(&user, 1) // 传指针!
fmt.Println(user.Name) // 有值了错误3:小写字段无法映射到数据库
type User struct {
Name string // ✅ 会被 GORM 映射
email string // ❌ GORM 忽略这个字段
}错误4:混淆 = 和 :=
// ✅ 声明 + 赋值
name := "张三"
// ✅ 已经声明过,用 = 赋值
var name string
name = "张三"
// ❌ 重复用 := 声明已存在变量
var name string
name := "张三" // 编译错误!本章小结
| 知识点 | 为什么对 GORM 重要 |
|---|---|
| 结构体(struct) | 每个数据库表对应一个结构体 |
| 结构体标签(tag) | 告诉 GORM 如何映射字段到列 |
指针(* 和 &) | GORM 所有操作都需要传指针 |
| 零值概念 | GORM 最大的坑——零值字段可能被忽略 |
| 大小写可见性 | 只有大写字段才能被 GORM 映射 |
| 多返回值 + error | GORM 所有操作的错误处理模式 |
练习题
- 创建一个
Person结构体,包含Name(string)、Age(int)、Email(string)三个字段。 - 给
Person结构体添加 GORM 标签:Name列名设为person_name,Age默认值设为18。 - 写一个
divide函数,接收两个 float64 参数,返回结果和 error。除数为0时返回错误。 - 解释为什么以下代码是错的:
db.Create(user)而不是db.Create(&user)。 - 结构体字段首字母小写会发生什么?为什么?
- (思考题)如果
Age字段类型是int,值为 0 时表示"未设置"还是"年龄为0"?GORM 如何区分?提示:想想指针。