变量、类型与常量
1. Go语言类型系统概述
Go是一门静态类型语言,所有变量在编译时都有确定的类型。Go的类型系统设计简洁而强大。
1.1 类型分类
Go类型
├── 基本类型 (Basic Types)
│ ├── 布尔型: bool
│ ├── 数值型
│ │ ├── 整型: int8, int16, int32, int64, int
│ │ │ uint8, uint16, uint32, uint64, uint, uintptr
│ │ └── 浮点型: float32, float64
│ │ └── 复数: complex64, complex128
│ └── 字符串: string
├── 复合类型 (Composite Types)
│ ├── 数组: [n]T
│ ├── 切片: []T
│ ├── 映射: map[K]V
│ ├── 结构体: struct
│ ├── 指针: *T
│ ├── 函数: func
│ ├── 接口: interface
│ └── 通道: chan T
└── 自定义类型 (Type Definitions)
2. 变量的底层表示
2.1 变量的内存布局
每个变量在内存中占据一定的空间,其大小由类型决定:
go
package main
import (
"fmt"
"unsafe"
)
func main() {
var b bool
var i8 int8
var i16 int16
var i32 int32
var i64 int64
var f32 float32
var f64 float64
fmt.Printf("bool: %d bytes\n", unsafe.Sizeof(b)) // 1
fmt.Printf("int8: %d bytes\n", unsafe.Sizeof(i8)) // 1
fmt.Printf("int16: %d bytes\n", unsafe.Sizeof(i16)) // 2
fmt.Printf("int32: %d bytes\n", unsafe.Sizeof(i32)) // 4
fmt.Printf("int64: %d bytes\n", unsafe.Sizeof(i64)) // 8
fmt.Printf("float32: %d bytes\n", unsafe.Sizeof(f32)) // 4
fmt.Printf("float64: %d bytes\n", unsafe.Sizeof(f64)) // 8
}
2.2 平台相关类型
int、uint和uintptr的大小取决于平台:
- 32位系统:4字节
- 64位系统:8字节
go
// 在64位系统上
var i int
fmt.Printf("int: %d bytes\n", unsafe.Sizeof(i)) // 8
var ptr uintptr
fmt.Printf("uintptr: %d bytes\n", unsafe.Sizeof(ptr)) // 8
2.3 零值机制
Go语言的所有变量都有零值(Zero Value),这是一个重要的安全特性:
go
var i int // 0
var f float64 // 0.0
var b bool // false
var s string // ""
var p *int // nil
var slice []int // nil
var m map[string]int // nil
var ch chan int // nil
var fn func() // nil
底层实现:编译器在分配内存时会自动将内存清零。
3. 基本类型详解
3.1 布尔类型
go
// 源码:src/runtime/runtime2.go
type bool uint8 // 实际上是uint8的别名
布尔值在内存中占1字节,但只使用最低位:
true:0x01false:0x00
3.2 整数类型
有符号整数
使用二进制补码表示:
go
var i8 int8 = -1
// 内存表示: 0xFF (11111111)
var i16 int16 = -1
// 内存表示: 0xFFFF (1111111111111111)
无符号整数
直接使用二进制表示:
go
var u8 uint8 = 255
// 内存表示: 0xFF (11111111)
整数溢出
Go语言的整数运算会发生溢出,但不会panic:
go
var u8 uint8 = 255
u8 = u8 + 1 // 溢出,结果为0
var i8 int8 = 127
i8 = i8 + 1 // 溢出,结果为-128
3.3 浮点类型
Go使用IEEE 754标准表示浮点数。
float32 (单精度)
符号位(1) | 指数位(8) | 尾数位(23)
float64 (双精度)
符号位(1) | 指数位(11) | 尾数位(52)
示例:
go
var f32 float32 = 3.14
var f64 float64 = 3.14159265359
// 特殊值
var inf = math.Inf(1) // 正无穷
var nan = math.NaN() // 非数字
注意 :浮点数比较不能使用==:
go
// 错误做法
if 0.1 + 0.2 == 0.3 { // false!
fmt.Println("Equal")
}
// 正确做法
const epsilon = 1e-9
if math.Abs((0.1 + 0.2) - 0.3) < epsilon {
fmt.Println("Equal")
}
3.4 复数类型
go
var c64 complex64 = 1 + 2i
var c128 complex128 = complex(1.0, 2.0)
// 底层表示
// complex64: 两个float32
// complex128: 两个float64
real := real(c128) // 实部: 1.0
imag := imag(c128) // 虚部: 2.0
4. 常量
4.1 常量的特性
常量在编译期确定,不占用运行时内存:
go
const Pi = 3.14159
const (
StatusOK = 200
StatusNotFound = 404
)
4.2 无类型常量
Go的常量可以是无类型的,具有更高的精度:
go
const (
// 无类型常量,精度可以非常高
Big = 1 << 100
Small = Big >> 99
)
func needInt(x int) int { return x*10 + 1 }
func needFloat(x float64) float64 { return x * 0.1 }
func main() {
fmt.Println(needInt(Small)) // 21
fmt.Println(needFloat(Small)) // 0.2
fmt.Println(needFloat(Big)) // 1.2676506002282295e+29
}
4.3 iota枚举器
iota是Go的常量生成器:
go
const (
Sunday = iota // 0
Monday // 1
Tuesday // 2
Wednesday // 3
Thursday // 4
Friday // 5
Saturday // 6
)
// 位掩码示例
const (
FlagNone = 1 << iota // 1 << 0 = 1
FlagRed // 1 << 1 = 2
FlagGreen // 1 << 2 = 4
FlagBlue // 1 << 3 = 8
)
// 跳过值
const (
_ = iota // 跳过0
KB = 1 << (10 * iota) // 1 << 10 = 1024
MB // 1 << 20 = 1048576
GB // 1 << 30 = 1073741824
TB // 1 << 40 = 1099511627776
)
4.4 常量的编译期计算
编译器会在编译期计算常量表达式:
go
const (
// 这些计算在编译期完成
Size = 1024 * 1024
Mask = (1 << 8) - 1
Result = Size * 2 + Mask
)
// 查看编译后的汇编代码
// go tool compile -S main.go
5. 类型转换
5.1 显式类型转换
Go不支持隐式类型转换,必须显式转换:
go
var i int = 42
var f float64 = float64(i)
var u uint = uint(f)
// 不同大小的整数转换
var i32 int32 = 1000
var i64 int64 = int64(i32)
var i8 int8 = int8(i32) // 可能溢出!
5.2 字符串与字节切片转换
go
s := "hello"
b := []byte(s) // 字符串转字节切片(会复制)
s2 := string(b) // 字节切片转字符串(会复制)
// 底层实现涉及内存复制
// 源码:src/runtime/string.go
// func slicebytetostring(buf *tmpBuf, ptr *byte, n int) string
5.3 unsafe转换
使用unsafe包可以进行零拷贝转换(危险!):
go
import "unsafe"
func stringToBytes(s string) []byte {
return *(*[]byte)(unsafe.Pointer(&s))
}
func bytesToString(b []byte) string {
return *(*string)(unsafe.Pointer(&b))
}
// 注意:这种转换破坏了字符串的不可变性!
6. 类型别名与类型定义
6.1 类型定义(Type Definition)
创建一个新类型:
go
type MyInt int
var a int = 10
var b MyInt = 20
// a = b // 编译错误:类型不兼容
a = int(b) // 需要显式转换
6.2 类型别名(Type Alias)
Go 1.9引入,创建类型的别名:
go
type MyInt = int // 注意有等号
var a int = 10
var b MyInt = 20
a = b // OK,MyInt和int是同一类型
6.3 底层类型
每个类型都有底层类型(Underlying Type):
go
type MyInt int
type MyInt2 MyInt
// MyInt的底层类型是int
// MyInt2的底层类型也是int
7. 内存对齐
7.1 对齐规则
Go遵循平台的内存对齐规则以提高性能:
go
type Example struct {
a bool // 1 byte
b int32 // 4 bytes
c bool // 1 byte
}
fmt.Println(unsafe.Sizeof(Example{})) // 12 bytes (不是6!)
// 内存布局:
// [a][padding][b b b b][c][padding padding padding]
// 1 3 4 1 3
7.2 优化结构体布局
go
// 未优化:12字节
type Bad struct {
a bool // 1 + 3 padding
b int32 // 4
c bool // 1 + 3 padding
}
// 优化后:8字节
type Good struct {
b int32 // 4
a bool // 1
c bool // 1 + 2 padding
}
8. 实践建议
8.1 选择合适的类型
go
// 使用int而不是int32/int64(除非有特殊需求)
var count int // 推荐
// 使用具体大小的类型用于序列化/网络传输
type Header struct {
Version uint8
Length uint16
Flags uint32
}
8.2 避免不必要的类型转换
go
// 不好
var i int = 10
var i64 int64 = int64(i)
result := i64 + 20 // 需要转换
// 好
var i int64 = 10
result := i + 20
8.3 使用常量提高可读性
go
// 不好
if status == 200 {
// ...
}
// 好
const StatusOK = 200
if status == StatusOK {
// ...
}
9. 总结
- Go的类型系统简洁而强大,所有类型在编译期确定
- 变量都有零值,提供了安全的默认状态
- 常量在编译期计算,不占用运行时内存
- 类型转换必须显式进行
- 内存对齐影响结构体大小,合理布局可以节省内存