Skip to content

类型系统与类型推断

本章深入Go类型系统:静态类型vs动态类型、强类型与零值机制、类型别名与自定义类型、类型转换与断言。重点:Go类型安全是编译时保证,理解零值机制可避免很多bug。

前置知识:第3章 语法基础 学习目标:理解Go类型系统差异、掌握类型转换/断言用法、学会用自定义类型增加安全性


# Python: 类型在运行时决定
x = 10 # int
print(type(x)) # <class 'int'>
x = "hello" # 变成str
print(type(x)) # <class 'str'>
x = [1, 2, 3] # 变成list
print(type(x)) # <class 'list'>
# 函数参数无类型约束
def process(value):
return value * 2
print(process(5)) # 10
print(process("ab")) # "abab"
print(process([1])) # [1, 1]
// Go: 类型在编译时决定
var x int = 10
fmt.Println(reflect.TypeOf(x)) // int
// x = "hello" // 编译错误:无法将string赋值给int
// 函数参数有类型约束
func process(value int) int {
return value * 2
}
fmt.Println(process(5)) // 10
// fmt.Println(process("ab")) // 编译错误:无法将string作为int传入

Go支持类型推断,Python也支持(但机制不同):

// Go: 编译器推断类型
x := 10 // int
y := 3.14 // float64
z := "hello" // string
b := true // bool
// 切片推断
items := []int{1, 2, 3} // []int
# Python: 动态类型,无需推断
x = 10 # int
y = 3.14 # float
z = "hello" # str
b = True # bool
# 列表推断
items = [1, 2, 3] # list
// 错误在编译时被捕获
func getUser(id int) User {
return users[id] // 如果users不是map,编译错误
}
// Python错误在运行时
def get_user(id):
return users[id] # 如果users不是字典,运行时错误
特性PythonGo
类型检查运行时编译时
变量类型可变固定
函数参数类型可变固定(可推断)
类型推断无需(动态)支持
泛型无支持(Go 1.18+)

本节目的:理解Go强类型约束和零值机制,对比Python的None

Go是强类型语言,类型转换必须显式进行:

var i int = 10
var f float64 = i // 编译错误:类型不匹配
// 正确做法:显式类型转换
var f float64 = float64(i)

Python相对宽松,但也需要显式转换:

i = 10
f = float(i) # 必须显式转换

Go的变量初始化有默认值:

var i int // 0
var f float64 // 0.0
var b bool // false
var s string // ""(空字符串)
var ptr *int // nil
var slice []int // nil
var m map[string]int // nil
// struct的零值
type User struct {
Name string
Age int
}
var user User // Name="" Age=0
# Python变量必须初始化
# 使用前必须赋值,否则NameError
name = "" # 需要手动初始化
age = 0
# 类实例
class User:
def __init__(self):
self.name = "" # 手动初始化
self.age = 0
// Go: nil表示"无"
var s []string = nil // 切片为空
var m map[string]int = nil // map为空
var p *int = nil // 指针为空
// 判断nil
if s == nil {
fmt.Println("切片为空")
}
// 注意:nil切片和空切片不同
var s1 []int = nil // len=0, cap=0
s2 := []int{} // len=0, cap=0
s3 := make([]int, 0) // len=0, cap=0
// s1 == nil 为true
// s2 == nil 为false(空切片)
# Python: None表示"无"
s = None # 列表为空
s = [] # 空列表
m = None # 字典为空
m = {} # 空字典
# 判断
if s is None:
print("为空")
if s == []:
print("空列表")
// 使用零值可以安全地使用变量
func getScore(name string) int {
scores := map[string]int{
"Alice": 95,
"Bob": 80,
}
// 如果name不存在,返回int的零值0
return scores[name] // 安全,不会报错
}
// 对比Python
def get_score(name):
scores = {
"Alice": 95,
"Bob": 80,
}
return scores.get(name, 0) # 需要使用get并提供默认值

本节目的:区分类型别名(type A = int)与自定义类型(type A int),理解自定义类型的价值

// byte是uint8的别名
type Byte = uint8
type Rune = int32
var b Byte = 255
var r Rune = 100
// 它们可以互相赋值
var u uint8 = b // 合法
// 定义新类型(不是别名)
type ID int
type UserName string
var id ID = 12345
var name UserName = "Alice"
// id和int不兼容
var n int = id // 编译错误
// 需要显式转换
var n int = int(id)

1. 增加类型安全性

type UserID int
type OrderID int
func getUser(id UserID) {
fmt.Printf("User ID: %d\n", id)
}
var uid UserID = 100
var oid OrderID = 200
getUser(uid) // OK
// getUser(oid) // 编译错误:参数类型不匹配

2. 给已有类型添加方法

type ID int
func (i ID) String() string {
return fmt.Sprintf("ID:%d", i)
}
var id ID = 123
fmt.Println(id.String()) // ID:123
# 类型别名
UserId = int
OrderId = int
# 类型约束(运行时)
class UserId:
def __init__(self, value):
if not isinstance(value, int):
raise TypeError("must be int")
self.value = value
# Python无法在编译时强制类型约束

本节目的:区分类型转换(编译时、显式)和类型断言(运行时、接口)

将A类型转为B类型:

// 整数之间
var i int = 10
var i64 int64 = int64(i)
var i8 int8 = int8(i) // 可能溢出
// 浮点转整数(截断)
var f float64 = 3.14
var i int = int(f) // 3
// 数值转字符串
str := strconv.Itoa(123)
str := fmt.Sprintf("%d", 123)
// 字符串转数值
i, err := strconv.Atoi("123")
f, err := strconv.ParseFloat("3.14", 64)
// 字节切片转字符串
bytes := []byte{'h', 'e', 'l', 'l', 'o'}
s := string(bytes)
// 字符串转字节切片
s := "hello"
b := []byte(s)
# 整数之间
i = 10
i64 = int(i) # Python int是任意精度
# 浮点转整数
f = 3.14
i = int(f) # 3
# 数值转字符串
s = str(123)
# 字符串转数值
i = int("123")
f = float("3.14")
# 字节和字符串
b = "hello".encode('utf-8')
s = b.decode('utf-8')

从接口类型提取具体类型:

// 接口类型
var i interface{} = "hello"
// 类型断言
s, ok := i.(string)
if ok {
fmt.Println(s) // hello
}
// 失败情况
var i interface{} = 123
s, ok := i.(string)
if !ok {
fmt.Println("不是字符串") // 执行这里
}
// 安全断言(可能panic)
s := i.(string) // 如果不是string,panic
func describe(i interface{}) {
switch v := i.(type) {
case int:
fmt.Printf("int: %d\n", v)
case string:
fmt.Printf("string: %s\n", v)
case float64:
fmt.Printf("float64: %f\n", v)
default:
fmt.Printf("unknown: %T\n", v)
}
}
describe(123) // int: 123
describe("hello") // string: hello
describe(3.14) // float64: 3.140000
describe([]int{1, 2}) // unknown: []int
def describe(i):
if isinstance(i, int):
print(f"int: {i}")
elif isinstance(i, str):
print(f"string: {i}")
elif isinstance(i, float):
print(f"float: {i}")
else:
print(f"unknown: {type(i)}")
describe(123)
describe("hello")
// 方法一:ok模式
value, ok := interface{}(123).(string)
if !ok {
fmt.Println("转换失败,使用默认值")
value = "default"
}
// 方法二:switch type
switch v := i.(type) {
case string:
fmt.Println("字符串:", v)
default:
fmt.Println("非字符串类型")
}

  1. 自定义类型增加安全性:用type UserID int代替int,防止UserID和OrderID混用
  2. 类型断言用ok模式:除非确定类型,否则不要直接i.(string),用value, ok := i.(string)
  3. 空接口interface{}慎用:Go 1.18+可用any代替,但泛型是更好的选择
  4. 类型转换必须显式:Go不会像Python那样自动转换,编译时会报错
  5. nil不等于零值:指针、slice、map、channel、interface的零值是nil,不是”空”而是”未初始化”

  1. 静态类型:Go在编译时检查类型,Python在运行时
  2. 零值机制:Go所有变量有默认值(0, "", false, nil),Python必须初始化
  3. 类型别名:type Alias = ExistingType,类型安全增强
  4. 类型转换:Type(value)显式转换
  5. 类型断言:value, ok := i.(Type)从接口提取值
PythonGo
动态类型静态类型
无零值零值机制
isinstancetype switch / 类型断言
无type alias: type ID = int
无新类型: type ID int

  1. 声明不同类型的变量,打印它们的零值
  2. 创建UserID和OrderID类型,写函数防止混用
  3. 实现字符串转浮点数的错误处理
  4. 用类型断言从interface{}提取不同类型的值
  5. 写一个函数,使用type switch打印任意值类型