Skip to content

第01章:Go 语言极速入门(为 GORM 学习准备) ​

本章目标 ​

学完本章后,你将能够:

  1. 在自己的电脑上安装 Go 并配置好开发环境
  2. 理解 Go 的基本语法:变量、函数、结构体、指针、接口
  3. 使用 go mod init 初始化一个 Go 项目
  4. 看懂并编写简单的 Go 程序
  5. 理解为什么结构体和指针是 GORM 的核心基础

前置知识 ​

  • 本章是第1章,无需任何 Go 语言前置知识
  • 基本的编程概念(变量、函数——你学过的任何语言都可以)
  • 会使用命令行(终端/cmd)

1.1 安装 Go 与配置环境 ​

下载与安装 ​

Go 语言的官网是 https://go.dev,下载页在 https://go.dev/dl。

Windows 用户:

  1. 下载 .msi 安装包(例如 go1.21.windows-amd64.msi)
  2. 双击运行,一路 "Next" 即可
  3. 默认安装路径:C:\Program Files\Go\

Mac 用户:

bash
# 方式一:下载 .pkg 安装包
# 方式二:使用 Homebrew
brew install go

Linux 用户:

bash
# 下载并解压
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

验证安装 ​

打开终端(命令行),输入:

bash
go version

如果看到类似 go version go1.21.0 windows/amd64 的输出,说明安装成功了。

三个关键环境变量 ​

bash
# 查看所有 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 程序 ​

项目初始化 ​

bash
# 创建一个目录作为你的项目
mkdir hello-gorm
cd hello-gorm

# 初始化 Go Module(module 名字随意,建议用目录名)
go mod init hello-gorm

执行后会生成一个 go.mod 文件,内容大致如下:

module hello-gorm

go 1.21

Hello World ​

创建文件 main.go:

go
package main

import "fmt"

func main() {
	fmt.Println("Hello, GORM!")
}

运行:

bash
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 有几种声明变量的方式:

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。

基本数据类型 ​

go
// 整数
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, uintBIGINT平台相关
int8TINYINT-128 到 127
int16SMALLINT
int32INT
int64BIGINT
float32FLOAT
float64DOUBLE
stringVARCHAR(255) 或 TEXTGORM 默认 VARCHAR(255)
boolTINYINT(1)true → 1, false → 0
time.TimeDATETIME时间类型
[]byteBLOB二进制数据

零值(Zero Value) ​

Go 中每个类型都有一个"零值"——变量声明但未赋值时的默认值。这是 GORM 最重要的概念之一,务必记住!

go
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 函数 ​

基本函数 ​

go
// 无参数,无返回值
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 的特色) ​

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),但可以给类型定义方法:

go
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 中,每个数据库表对应一个结构体,每行数据对应一个结构体实例。

定义结构体 ​

go
// 一个"用户"结构体——对应数据库中的 users 表
type User struct {
	ID        uint   // 主键
	Name      string // 用户名
	Age       int    // 年龄
	Email     string // 邮箱
}

创建和使用结构体 ​

go
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 字段如何映射到数据库列:

go
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 的几乎所有操作都需要传指针。

什么是指针 ​

指针存储的是另一个变量的内存地址,而不是值本身。

go
func main() {
	name := "张三"
	
	// & 取地址
	p := &name  // p 是指向 name 的指针,类型是 *string
	
	fmt.Println(p)   // 0xc000010050(内存地址,每次运行不同)
	fmt.Println(*p)  // 张三(* 解引用,获取指针指向的值)
	
	// 通过指针修改原变量的值
	*p = "李四"
	fmt.Println(name)  // 李四(原变量也被改了!)
}

为什么 GORM 需要指针 ​

go
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":

go
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 的接口定义了一组方法签名,任何实现了这些方法的类型都自动满足该接口。

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) ​

包的规则 ​

go
// 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)
}
go
// models/user.go
package models  // 包名和目录名通常一致

type User struct {
	Name string
}

标识符的可见性 ​

Go 用首字母大小写控制可见性(没有 public/private 关键字):

go
type User struct {
	Name  string  // 大写开头 → 公开(其他包可以访问)
	email string  // 小写开头 → 私有(只能本包内访问)
}

func GetUser() User { ... }  // 公开函数
func validate() error { ... } // 私有函数

这对 GORM 很重要:结构体字段必须是大写开头才能被 GORM 映射到数据库列。小写字段 GORM 直接忽略。


1.9 错误处理 ​

Go 没有 try-catch,错误通过返回值传递:

go
// 标准错误处理模式
result, err := someFunction()
if err != nil {
	// 处理错误
	fmt.Println("出错了:", err)
	return
}
// 正常使用 result

在 GORM 中:

go
result := db.Create(&user)
if result.Error != nil {
	fmt.Println("插入失败:", result.Error)
	return
}
fmt.Printf("成功插入,影响行数:%d,ID:%d\n", result.RowsAffected, user.ID)

常见错误 ​

错误1::= 不能用于包级别 ​

go
// ❌ 错误
package main
name := "张三"  // 编译错误!:= 只能在函数内使用

// ✅ 正确
package main
var name = "张三"  // 包级别用 var

错误2:忘记传指针给 GORM ​

go
// ❌ 错误
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:小写字段无法映射到数据库 ​

go
type User struct {
	Name  string  // ✅ 会被 GORM 映射
	email string  // ❌ GORM 忽略这个字段
}

错误4:混淆 = 和 := ​

go
// ✅ 声明 + 赋值
name := "张三"

// ✅ 已经声明过,用 = 赋值
var name string
name = "张三"

// ❌ 重复用 := 声明已存在变量
var name string
name := "张三"  // 编译错误!

本章小结 ​

知识点为什么对 GORM 重要
结构体(struct)每个数据库表对应一个结构体
结构体标签(tag)告诉 GORM 如何映射字段到列
指针(* 和 &)GORM 所有操作都需要传指针
零值概念GORM 最大的坑——零值字段可能被忽略
大小写可见性只有大写字段才能被 GORM 映射
多返回值 + errorGORM 所有操作的错误处理模式

练习题 ​

  1. 创建一个 Person 结构体,包含 Name(string)、Age(int)、Email(string)三个字段。
  2. 给 Person 结构体添加 GORM 标签:Name 列名设为 person_name,Age 默认值设为 18。
  3. 写一个 divide 函数,接收两个 float64 参数,返回结果和 error。除数为0时返回错误。
  4. 解释为什么以下代码是错的:db.Create(user) 而不是 db.Create(&user)。
  5. 结构体字段首字母小写会发生什么?为什么?
  6. (思考题)如果 Age 字段类型是 int,值为 0 时表示"未设置"还是"年龄为0"?GORM 如何区分?提示:想想指针。

把零散知识,整理成可以反复走的路。