跳到主要内容

Data Types(数据类型)

Go 是静态类型语言:每个变量在编译时都有确定的类型,类型决定它能保存什么值、支持哪些操作,以及赋值和比较是否合法。Go 不会在不同命名类型之间随意隐式转换,这使许多类型错误能在程序运行前被发现。

从一个最小示例开始

下面的程序声明了几种常用类型,并使用 %T 输出值的动态类型:

package main

import "fmt"

func main() {
var enabled bool = true
var count int = 3
var ratio float64 = 0.75
var name string = "Go"

fmt.Printf("%T %T %T %T\n", enabled, count, ratio, name)
}

输出如下:

bool int float64 string

显式类型适合强调范围或 API 契约;初始值已经能清楚表达类型时,也可以让编译器推断。

基础类型

Go 的预声明基础类型主要包括:

类别类型说明
布尔bool只包含 truefalse
有符号整数intint8int16int32int64int 的宽度取决于目标平台,至少为 32 位
无符号整数uintuint8uint16uint32uint64不能表示负数;uintptr 用于底层指针相关运算
浮点数float32float64通常优先使用精度更高的 float64
复数complex64complex128实部和虚部分别使用对应精度的浮点数
字符串string不可变的字节序列,通常用于保存 UTF-8 编码的文本

byteuint8 的别名,通常表示原始字节;runeint32 的别名,通常表示 Unicode 码点。一个中文字符在 UTF-8 中可能占多个字节,因此字符串的字节数和码点数不一定相同:

package main

import (
"fmt"
"unicode/utf8"
)

func main() {
text := "Go语言"
fmt.Println(len(text)) // 8:字节数
fmt.Println(utf8.RuneCountInString(text)) // 4:码点数
}

复合类型

基础类型可以组成更复杂的数据结构:

类型主要用途
数组 [N]T保存固定数量、相同元素类型的值
切片 []T表示数组的一段动态长度视图,是最常用的序列类型
映射 map[K]V按键查找值;键类型必须可比较
结构体 struct把不同类型的字段组合为一个值
指针 *T保存变量地址,允许间接读取或修改值
函数 func(...) ...函数本身也有类型,可以赋值和传递
接口 interface描述一组方法;具体类型通过实现方法隐式满足接口
通道 chan T在 goroutine 之间传递指定类型的值并提供同步语义

本目录分别讲解 Array(数组)Slice(切片)Map(映射)Pointer(指针)Struct(结构体)。函数、接口、泛型和通道需要结合方法集、类型约束或并发语义继续学习,不应只依据这张表使用。

package main

import "fmt"

type User struct {
Name string
Scores []int
}

func main() {
user := User{Name: "Ada", Scores: []int{90, 95}}
lookup := map[string]bool{"Ada": true}

fmt.Println(user.Name, user.Scores[0], lookup[user.Name])
}

切片、映射、通道、函数、指针和接口的零值是 nil,但它们不是同一种“引用类型”。例如,向 nil 映射写入会 panic,而向 nil 切片执行 append 是合法的。应分别学习每种类型的表示和操作规则,不要只依赖“像引用一样”的笼统类比。

零值

变量声明后即使没有显式初始化,也会获得该类型的零值:

package main

import "fmt"

func main() {
var count int
var ready bool
var title string
var values []int

fmt.Printf("%d %t %q %v\n", count, ready, title, values)
}

输出如下:

0 false "" []

结构体的零值由每个字段的零值组成,数组的每个元素也会被置为零值。设计自定义类型时,可以尽量让零值具有明确且安全的含义;如果零值不可用,则应提供构造函数并清楚说明约束。

定义类型与别名

type 可以定义一个新类型。新类型与底层类型是不同的命名类型,即使底层表示相同,也不能直接互相赋值:

package main

import "fmt"

type Celsius float64
type Fahrenheit float64

func main() {
var c Celsius = 20
var f Fahrenheit = Fahrenheit(c*9/5 + 32)
fmt.Println(f)
}

定义类型可以附加方法,并阻止语义不同的数据被误混用。类型别名则使用 =,别名和原类型代表同一个类型:

type ID = int64

别名常用于兼容或迁移已有 API;如果目标是建立新的领域含义,通常应定义新类型,而不是使用别名。

类型转换

Go 使用 T(value) 进行显式转换:

package main

import "fmt"

func main() {
total := 7
count := 2
average := float64(total) / float64(count)
fmt.Println(average) // 3.5
}

转换不等于安全校验。把较大的整数转换为较窄的整数类型可能截断高位;浮点数转换为整数会舍弃小数部分。处理外部数据时,应先检查范围,再执行转换。

字符串与数字之间也不是通过普通类型转换完成解析。应使用 strconv.Atoistrconv.ParseIntstrconv.FormatInt 等标准库函数,并处理它们返回的错误。

可比较性

类型是否可使用 ==!= 由语言规则决定:

  • 布尔值、数值、字符串、指针和通道可比较。
  • 数组在元素类型可比较时可比较。
  • 结构体在所有字段都可比较时可比较。
  • 切片、映射和函数不能彼此比较,只能与 nil 比较。
  • 接口值相互比较时,如果其中保存的动态值不可比较,程序会在运行时 panic。

映射的键必须可比较,因此切片不能直接作为映射键。需要复合键时,可以使用字段均可比较的结构体。

与 TypeScript 的关键差异

  • TypeScript 的类型主要用于静态检查,运行时仍由 JavaScript 值模型决定;Go 类型直接参与编译、内存表示和运行时行为。
  • Go 的数值类型有明确宽度和有无符号之分,不等同于 TypeScript 常用的 number
  • Go 的 string 是字节序列,按索引取得的是字节;JavaScript 字符串索引基于 UTF-16 代码单元。
  • Go 的数组长度属于类型的一部分,[2]int[3]int 是不同类型;切片才更接近常用的动态数组。
  • Go 的 nil 不等于 JavaScript 的 nullundefined,它只适用于特定类型,并受静态类型约束。

延伸学习

小结

  • 每个 Go 值都有确定类型,类型决定合法操作与赋值规则。
  • 基础类型可组成数组、切片、映射、结构体、接口等复合类型。
  • 未显式初始化的变量会获得零值,但不同类型的零值可用性不同。
  • 定义类型用于建立新的语义和方法集合,类型别名仍代表原类型。
  • 类型转换必须显式进行,并不自动提供溢出或范围保护。
  • 可比较性是类型规则的一部分,切片、映射和函数不能彼此使用 ==