跳转到内容

Go 数据类型基础

建议学习 35-45 分钟更新于 2025/10/29

这篇笔记用于入门 Go 语言的数据类型。

刚开始学习 Go 时,最容易混淆的是:byterune、数组和切片、nil 和零值、map 和结构体、接口和普通类型。先把这些基础类型理清楚,后面学习函数、方法、并发和 Web 开发会顺很多。

Go 的数据类型可以先分成两大类:

Go 数据类型
├─ 基本数据类型
│ ├─ 数值类型
│ │ ├─ 整数类型
│ │ ├─ 浮点类型
│ │ └─ 复数类型
│ ├─ 布尔类型
│ └─ 字符串类型
└─ 派生数据类型 / 复合数据类型
├─ 指针
├─ 数组
├─ 切片
├─ map
├─ 结构体
├─ 函数
├─ 接口
└─ 通道

可以先记住一句话:

基本类型用来表示简单值,复合类型用来组织多个值或表达更复杂的关系。

Go 的整数类型分为有符号整数和无符号整数。

类型说明
int有符号整数,长度和平台有关
int88 位有符号整数
int1616 位有符号整数
int3232 位有符号整数
int6464 位有符号整数
uint无符号整数,长度和平台有关
uint88 位无符号整数
uint1616 位无符号整数
uint3232 位无符号整数
uint6464 位无符号整数
uintptr用于保存指针地址的整数类型
byteuint8 的别名,常用于表示字节
runeint32 的别名,常用于表示 Unicode 字符

示例:

integer.go
package main
import "fmt"
func main() {
var age int = 18
var count uint = 100
var b byte = 'A'
var r rune = ''
fmt.Println(age)
fmt.Println(count)
fmt.Println(b) // 65
fmt.Println(r) // 20320
}

这里要注意:

  • byte 本质是 uint8,适合处理字节流。
  • rune 本质是 int32,适合处理中文、emoji 等 Unicode 字符。
  • intuint 的长度和平台有关,普通业务里常用 int 即可。

Go 有两种常用浮点类型:

类型说明
float32单精度浮点数
float64双精度浮点数

示例:

float.go
package main
import "fmt"
func main() {
var price float64 = 19.99
var rate float32 = 0.85
fmt.Println(price)
fmt.Println(rate)
}

实际开发中,如果没有特殊要求,通常使用 float64

需要注意:浮点数不适合直接表示精确金额。涉及金额时,常见做法是使用整数保存分,或者使用 decimal 类库。

Go 也提供复数类型:

类型说明
complex64实部和虚部都是 float32
complex128实部和虚部都是 float64

示例:

complex.go
package main
import "fmt"
func main() {
var value complex128 = 1 + 2i
fmt.Println(value)
fmt.Println(real(value))
fmt.Println(imag(value))
}

普通后端开发很少直接用复数类型,但它属于 Go 的数值类型。

布尔类型只有两个值:

  • true
  • false

示例:

bool.go
package main
import "fmt"
func main() {
var isLogin bool = true
var isDeleted bool = false
fmt.Println(isLogin)
fmt.Println(isDeleted)
}

Go 中不能把数字当作布尔值使用。

// 错误写法
// if 1 {
// }

判断条件必须是布尔表达式。

字符串类型是 string

string.go
package main
import "fmt"
func main() {
var name string = "xiaoxi"
message := "你好,Go"
fmt.Println(name)
fmt.Println(message)
}

Go 字符串是不可变的。

如果需要频繁拼接字符串,可以使用 strings.Builder

string-builder.go
package main
import (
"fmt"
"strings"
)
func main() {
var builder strings.Builder
builder.WriteString("hello")
builder.WriteString(" ")
builder.WriteString("go")
fmt.Println(builder.String())
}

Go 没有单独的 char 类型。

通常用:

  • byte 表示一个字节。
  • rune 表示一个 Unicode 字符。

示例:

byte-rune.go
package main
import "fmt"
func main() {
text := "Go语言"
fmt.Println(len(text)) // 字节长度
for i := 0; i < len(text); i++ {
fmt.Printf("%c\n", text[i])
}
for _, ch := range text {
fmt.Printf("%c\n", ch)
}
}

这里 len(text) 返回的是字节长度,不是字符数量。

如果要按字符遍历,应该使用 range,它会按 rune 处理。

Go 中变量声明后,如果没有显式赋值,会有默认零值。

类型零值
数字类型0
布尔类型false
字符串""
指针nil
切片nil
mapnil
channelnil
interfacenil
函数nil
结构体每个字段都是对应类型零值

示例:

zero-value.go
package main
import "fmt"
func main() {
var age int
var name string
var ok bool
var scores []int
fmt.Println(age) // 0
fmt.Println(name) // ""
fmt.Println(ok) // false
fmt.Println(scores) // []
fmt.Println(scores == nil)
}

零值是 Go 里很重要的设计。很多类型即使没有显式初始化,也能处于一个可预测状态。

指针用于保存变量的内存地址。

pointer.go
package main
import "fmt"
func main() {
age := 18
p := &age
fmt.Println(age)
fmt.Println(p)
fmt.Println(*p)
*p = 20
fmt.Println(age)
}

这里:

  • &age 获取变量地址。
  • p 保存地址。
  • *p 通过指针访问地址中的值。

Go 有指针,但不支持像 C 那样的指针运算。

数组是固定长度的同类型元素集合。

array.go
package main
import "fmt"
func main() {
var nums [3]int = [3]int{1, 2, 3}
fmt.Println(nums)
fmt.Println(nums[0])
}

数组长度是类型的一部分。

var a [3]int
var b [4]int
// a 和 b 是不同类型

所以 Go 中直接使用数组的场景相对少,更多时候使用切片。

切片是动态长度的序列,是 Go 中最常用的数据结构之一。

slice.go
package main
import "fmt"
func main() {
nums := []int{1, 2, 3}
nums = append(nums, 4)
fmt.Println(nums)
fmt.Println(len(nums))
fmt.Println(cap(nums))
}

切片有三个重要信息:

  • 指向底层数组的指针。
  • 长度 len
  • 容量 cap

数组和切片区别:

对比项数组切片
长度固定动态
类型是否包含长度包含不包含
使用频率较少很常用
扩容不支持append 可能触发扩容

map 是键值对集合。

map.go
package main
import "fmt"
func main() {
user := map[string]int{
"xiaoxi": 18,
"veyliss": 20,
}
age, ok := user["xiaoxi"]
if ok {
fmt.Println(age)
}
user["new"] = 22
delete(user, "veyliss")
fmt.Println(user)
}

读取 map 时,经常使用 value, ok 判断 key 是否存在。

value, ok := user["name"]

需要注意:未初始化的 nil map 不能直接写入。

make-map.go
package main
func main() {
user := make(map[string]int)
user["xiaoxi"] = 18
}

结构体用于组织多个字段,适合描述一个对象或实体。

struct.go
package main
import "fmt"
type User struct {
ID int
Name string
Age int
}
func main() {
user := User{
ID: 1,
Name: "xiaoxi",
Age: 18,
}
fmt.Println(user.Name)
}

结构体经常用于:

  • 表示业务实体。
  • 接收 JSON。
  • 数据库模型。
  • 配置对象。

函数也是一种类型,可以赋值给变量,也可以作为参数传递。

function-type.go
package main
import "fmt"
func calculate(a int, b int, fn func(int, int) int) int {
return fn(a, b)
}
func main() {
add := func(a int, b int) int {
return a + b
}
result := calculate(1, 2, add)
fmt.Println(result)
}

函数类型常用于回调、策略函数、中间件等场景。

接口定义一组方法。一个类型只要实现了接口要求的方法,就自动实现了这个接口。

interface.go
package main
import "fmt"
type Speaker interface {
Speak() string
}
type User struct {
Name string
}
func (u User) Speak() string {
return "hello, " + u.Name
}
func say(s Speaker) {
fmt.Println(s.Speak())
}
func main() {
user := User{Name: "xiaoxi"}
say(user)
}

Go 的接口是隐式实现的,不需要写 implements

这点和 Java 很不一样。

通道也叫 channel,用于 goroutine 之间通信。

channel.go
package main
import "fmt"
func main() {
ch := make(chan string)
go func() {
ch <- "hello"
}()
message := <-ch
fmt.Println(message)
}

通道体现了 Go 并发里很重要的思想:

不要通过共享内存来通信,而要通过通信来共享内存。

后面学习 goroutine、并发控制、生产者消费者模型时,会经常用到 channel。

Go 不会自动进行隐式类型转换。

type-conversion.go
package main
import "fmt"
func main() {
var a int = 10
var b int64 = 20
result := int64(a) + b
fmt.Println(result)
}

即使都是整数类型,intint64 也需要显式转换。

这让 Go 的类型边界更清楚,但刚开始写时会觉得啰嗦。

易混点说明
byterunebyte 是字节,rune 是 Unicode 字符
数组和切片数组长度固定,切片长度动态
nil mapnil map 可以读,但不能写
字符串长度len(string) 返回字节数,不是字符数
interface接口是隐式实现,不需要显式声明
channel用于 goroutine 通信,不是普通队列的完全替代
类型转换Go 要求显式类型转换

学完基础类型后,可以顺手记住 Go 程序最常用的运行和编译命令。

Go 有两个高频命令:

  • go run:直接运行 Go 程序,适合开发和测试。
  • go build:编译 Go 程序,生成可执行文件。

运行指定文件:

Terminal window
go run main.go

运行当前目录下的整个包:

Terminal window
go run .

如果项目里有多个 .go 文件,并且它们属于同一个 package main,通常更推荐使用 go run .

编译指定文件:

Terminal window
go build main.go

编译当前目录下的整个包:

Terminal window
go build

也可以显式写成:

Terminal window
go build .

指定输出文件名:

Terminal window
go build -o app main.go

常见命令可以这样记:

命令作用
go run main.go运行指定 Go 文件
go run .运行当前目录下的包
go build main.go编译指定 Go 文件
go build编译当前目录下的包
go build .编译当前目录下的包
go build -o app main.go编译并指定输出文件名

交叉编译指的是:在当前系统上编译出另一个系统可运行的程序。

常见会用到三个环境变量:

变量作用
GOOS目标操作系统,例如 linuxdarwinwindows
GOARCH目标 CPU 架构,例如 amd64arm64
CGO_ENABLED是否启用 CGO,纯 Go 项目通常可以设为 0

在 PowerShell 中,可以这样交叉编译。

编译到 Linux 64 位:

Terminal window
$env:GOOS="linux"; $env:GOARCH="amd64"; $env:CGO_ENABLED="0"; go build -o output-linux main.go

编译到 macOS 64 位:

Terminal window
$env:GOOS="darwin"; $env:GOARCH="amd64"; $env:CGO_ENABLED="0"; go build -o output-macos main.go

编译到 Windows 64 位:

Terminal window
$env:GOOS="windows"; $env:GOARCH="amd64"; $env:CGO_ENABLED="0"; go build -o output-windows.exe main.go

如果是在 macOS 或 Linux 的 shell 中,可以这样写:

Terminal window
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -o output-linux main.go
GOOS=darwin GOARCH=amd64 CGO_ENABLED=0 go build -o output-macos main.go
GOOS=windows GOARCH=amd64 CGO_ENABLED=0 go build -o output-windows.exe main.go

如果项目依赖 CGO,例如依赖某些 C 库,交叉编译会更复杂,不能简单地把 CGO_ENABLED 设置为 0。普通 Go 入门项目大多可以先按纯 Go 项目处理。

Go 数据类型可以先记成两类:

基本类型:数字、布尔、字符串
复合类型:指针、数组、切片、map、结构体、函数、接口、通道

入门阶段最重要的是:

  • 会区分 byterune
  • 会区分数组和切片。
  • 会使用 map 存键值对。
  • 会用结构体组织业务数据。
  • 理解接口是隐式实现。
  • 知道 channel 用于 goroutine 通信。
  • 会使用 go rungo build 运行和编译基础程序。

这些内容掌握后,Go 的基础语法就有了比较稳的地基。