Go入门:基本数据类型全览与选择指南

大家好,我是你们的Go语言向导。前面我们学习了变量声明和零值机制,这些知识都建立在一个基础之上------Go的类型系统。今天我们就来全面了解Go语言的基本数据类型,掌握每种类型的特点和适用场景。
💡 类型系统是一门语言的"骨架"。Go的类型系统简洁但不简单------它只有25个关键字,但类型体系覆盖了从系统编程到应用开发的各种需求。理解每种类型的设计意图,你才能在正确的场景选择正确的类型。
一、Go类型系统全景
1.1 类型分类总览
Go语言的类型可以分为四大类:
Go类型体系
├── 基本类型(Basic Types)
│ ├── 布尔型(bool)
│ ├── 数值型
│ │ ├── 整数型(int, int8~64, uint, uint8~64, uintptr)
│ │ ├── 浮点型(float32, float64)
│ │ ├── 复数型(complex64, complex128)
│ │ └── 别名型(byte = uint8, rune = int32)
│ └── 字符串型(string)
│
├── 复合类型(Composite Types)
│ ├── 数组(Array)
│ ├── 切片(Slice)
│ ├── 映射(Map)
│ ├── 结构体(Struct)
│ └── 指针(Pointer)
│
├── 引用类型(Reference Types)
│ ├── 切片
│ ├── 映射
│ ├── 通道(Channel)
│ └── 函数(Function)
│
└── 接口类型(Interface Types)
1.2 为什么Go类型这么简洁
Go没有以下在其他语言中常见的类型:
- 没有
char--- 用rune(int32) 或byte(uint8) - 没有
long/long long--- 用int32/int64 - 没有
double--- 用float64 - 没有
unsigned short等各种组合 --- Go的命名更具可读性
二、布尔类型
2.1 基本用法
go
var isActive bool = true
var isClosed bool = false
var isAdmin = true // 类型推断
// 零值
var flag bool // 零值为 false
2.2 布尔运算
go
// 逻辑运算
result := true && false // false (AND)
result = true || false // true (OR)
result = !true // false (NOT)
// 比较运算产生布尔值
isEqual := (5 == 5) // true
isGreater := (10 > 5) // true
isLessOrEqual := (3 <= 3) // true
2.3 关键注意点
go
// ⚠️ Go中bool与整数不互通
var flag bool = true
// var i int = int(flag) // ❌ 编译错误
// var flag2 bool = bool(1) // ❌ 编译错误
// if 1 { } // ❌ 编译错误
// ✅ Go要求显式的布尔表达式
if isActive {
// ...
}
// ❌ 其他语言的习惯在Go中不适用
// if count { } // ❌ 编译错误
// if !count { } // ❌ 编译错误
if count > 0 { // ✅ 必须写完整的比较
// ...
}
💡 Go的布尔类型非常"严格"------它拒绝一切隐式转换。这种设计避免了C/C++中因整数与布尔值混用导致的大量Bug。
三、整数类型
3.1 整数类型一览
Go提供了丰富的整数类型,覆盖了各种位宽需求:
| 类型 | 位宽 | 范围 |
|---|---|---|
int8 |
8位 | -128 ~ 127 |
int16 |
16位 | -32768 ~ 32767 |
int32 |
32位 | -2^31 ~ 2^31-1 |
int64 |
64位 | -2^63 ~ 2^63-1 |
uint8 |
8位 | 0 ~ 255 |
uint16 |
16位 | 0 ~ 65535 |
uint32 |
32位 | 0 ~ 2^32-1 |
uint64 |
64位 | 0 ~ 2^64-1 |
int |
平台相关(32/64) | 取决于平台 |
uint |
平台相关(32/64) | 取决于平台 |
uintptr |
足够存下指针 | 取决于平台 |
byte |
8位(uint8别名) | 0 ~ 255 |
rune |
32位(int32别名) | 0 ~ 0x10FFFF |
3.2 整数类型的选择指南
go
// ── 日常编程 ──
var count int = 100 // 推荐:用int作为默认整数类型
var index int = 0
// ── 明确需要特定位宽 ──
var fileSize int64 = 1024 // 文件大小(可能非常大)
var timestamp int64 = time.Now().Unix() // Unix时间戳
var port int32 = 8080 // 网络端口(16位就够了,但32位更安全)
// ── 与系统交互 ──
var id uint32 = 12345 // 数据库中的UNSIGNED INT
var flags uint16 = 0xFFFF // 标志位
var uid uint64 = 10000000001 // 分布式ID
// ── 字节处理 ──
var b byte = 'A' // 单个字节(ASCII字符)
var data []byte = []byte("hello") // 字节切片
// ── Unicode字符 ──
var ch rune = '中' // 单个Unicode字符
var chars []rune = []rune("你好") // rune切片
// ── 数组索引和长度 ──
var length int = len(slice) // len()返回int
// 注意:如果使用其他整数类型做索引,需要转换
3.3 int vs int64
这是Go新手常问的一个问题:
go
// int 是Go的"默认"整数类型
// - 在32位系统上是32位
// - 在64位系统上是64位
// 选择建议:
// 1. 普通变量、计数器、索引 → int
// 2. 需要"很大"的整数 → int64
// 3. 序列化/反序列化(JSON、protobuf)→ int64(保证一致性)
// 4. 时间相关 → int64
go
// ⚠️ int和int64不能直接互操作
var a int = 10
var b int64 = 20
// c := a + b // ❌ 编译错误:类型不匹配
c := int64(a) + b // ✅ 需要显式转换
// 这在循环中尤其常见
slice := []int{1, 2, 3}
var index int64 = 0
// slice[index] // ❌ 编译错误
slice[int(index)] // ✅ 显式转换
3.4 整数运算的溢出
go
// ⚠️ 整数溢出不会引发panic,而是静默回绕
var max uint8 = 255
max = max + 1 // max = 0(回绕!)
var min int8 = -128
min = min - 1 // min = 127(回绕!)
// ✅ 如何安全处理
func SafeAddUint8(a, b uint8) (uint8, error) {
if a > 255 - b {
return 0, fmt.Errorf("uint8 overflow: %d + %d", a, b)
}
return a + b, nil
}
四、浮点数类型
4.1 float32与float64
go
var f32 float32 = 3.141592653589793 // 大约7位有效数字
var f64 float64 = 3.141592653589793 // 大约15位有效数字
fmt.Printf("float32: %.15f\n", f32) // 3.141592741012573
fmt.Printf("float64: %.15f\n", f64) // 3.141592653589793
📝 选择指南:
- float64:默认选择,精度足够大多数计算
- float32:内存受限场景(大量数组)、图形处理
- 不要用浮点数表示金额(见下文陷阱)
4.2 浮点数的特殊值
go
var inf = math.Inf(1) // 正无穷
var negInf = math.Inf(-1) // 负无穷
var nan = math.NaN() // Not a Number
// ⚠️ NaN不等于任何值,包括它自己
fmt.Println(nan == nan) // false
fmt.Println(math.IsNaN(nan)) // true
// 无穷的运算
fmt.Println(1.0 / 0.0) // +Inf
fmt.Println(-1.0 / 0.0) // -Inf
fmt.Println(0.0 / 0.0) // NaN
4.3 浮点数精度陷阱
go
// ⚠️ 浮点数比较的经典陷阱
var a = 0.1
var b = 0.2
var c = 0.3
// 错误:直接用 == 比较
fmt.Println(a+b == c) // false!(因为0.1+0.2 != 0.3在二进制浮点中)
// 原因:0.1、0.2、0.3在二进制中都是无限循环小数
fmt.Printf("%.20f\n", 0.1+0.2) // 0.30000000000000004441...
fmt.Printf("%.20f\n", 0.3) // 0.29999999999999998890...
// ✅ 正确的比较方法:使用很小的误差范围
const epsilon = 1e-9
func floatEqual(a, b float64) bool {
return math.Abs(a-b) < epsilon
}
fmt.Println(floatEqual(a+b, c)) // true
go
// ⚠️ 金融计算:绝不能用浮点数!
// 错误示例
var price float64 = 19.90
var quantity float64 = 100
var total = price * quantity // 1989.99999999... 而不是 1990.00
// ✅ 正确做法:用整数表示分(或厘)
var priceInCents int64 = 1990 // 19.90元 = 1990分
var quantity2 int64 = 100
var totalInCents int64 = priceInCents * quantity2 // 199000分 = 1990.00元
fmt.Printf("总价: %d.%02d元\n", totalInCents/100, totalInCents%100)
五、复数类型
5.1 基本使用
go
var c1 complex128 = 3 + 4i
var c2 complex64 = complex(1, 2) // 1+2i
c3 := 5 - 3i // 类型推断为 complex128
// 实部和虚部
fmt.Println(real(c1)) // 3
fmt.Println(imag(c1)) // 4
// 复数运算
sum := c1 + c3 // (3+4i) + (5-3i) = (8+1i)
product := c1 * c3 // (3+4i) * (5-3i) = 27+11i
abs := cmplx.Abs(c1) // 模 = sqrt(3²+4²) = 5
5.2 复数在Go中的应用
Go是为数不多内置复数类型的语言,这在科学计算中非常有用:
go
// 傅里叶变换、信号处理、量子力学等场景
func DFT(input []complex128) []complex128 {
n := len(input)
output := make([]complex128, n)
for k := 0; k < n; k++ {
var sum complex128
for t := 0; t < n; t++ {
angle := -2 * math.Pi * float64(k) * float64(t) / float64(n)
sum += input[t] * complex(math.Cos(angle), math.Sin(angle))
}
output[k] = sum
}
return output
}
六、字符串类型
6.1 字符串的本质
在Go中,字符串是一个不可变的字节序列:
go
var s string = "Hello, Go"
fmt.Println(len(s)) // 9(字节数,不是字符数)
// 字符串的底层结构(概念上):
// type stringStruct struct {
// str unsafe.Pointer // 指向底层字节数组
// len int // 字节长度
// }
// 字符串是不可变的
// s[0] = 'h' // ❌ 编译错误:cannot assign to s[0]
// 但可以整体替换
s = "Hello, World!" // ✅ 整体赋值
6.2 字符串与Unicode
go
// Go源码是UTF-8编码,字符串字面量也是
s := "你好,世界"
fmt.Println(len(s)) // 18(UTF-8编码后的字节数)
fmt.Println(utf8.RuneCountInString(s)) // 5(Unicode字符数)
// 遍历字符串的两种方式
// 方式一:按字节遍历
for i := 0; i < len(s); i++ {
fmt.Printf("%x ", s[i])
}
// 输出: e4 bd a0 e5 a5 bd ...
// 方式二:按Unicode字符遍历
for i, ch := range s {
fmt.Printf("位置%d: %c ", i, ch)
}
// 输出: 位置0: 你 位置3: 好 位置6: , 位置9: 世 位置12: 界
6.3 字符串的字面量形式
go
// 双引号字符串:支持转义
s1 := "Hello\nWorld\t!"
s2 := "他说:\"你好\""
// 反引号原始字符串:不支持转义,可以包含换行
s3 := `这是原始字符串
可以包含换行
\t不会被转义,会原样输出
`
// 多行字符串拼接
s4 := "第一部分" +
"第二部分" +
"第三部分"
// 常用于正则表达式和HTML模板
re := regexp.MustCompile(`\d+\.\d+\.\d+`)
template := `
<html>
<body>
<h1>Hello, {{.Name}}</h1>
</body>
</html>
`
七、类型别名和类型定义
7.1 byte和rune
go
// byte 是 uint8 的别名
var b byte = 65
fmt.Println(b) // 65
fmt.Printf("%c\n", b) // A
// rune 是 int32 的别名
var r rune = '中'
fmt.Println(r) // 20013
fmt.Printf("%c\n", r) // 中
7.2 类型定义创建新类型
go
// 类型定义:创建一个全新的类型
type UserID int64
type UserName string
type Score float64
var uid UserID = 12345
var name UserName = "张三"
// 新类型不与原类型兼容
var i64 int64 = 12345
// uid = i64 // ❌ 编译错误
uid = UserID(i64) // ✅ 需要显式转换
// 类型定义可以有自己方法,实现接口
type Celsius float64
func (c Celsius) String() string {
return fmt.Sprintf("%.1f°C", c)
}
func (c Celsius) ToFahrenheit() float64 {
return float64(c)*9/5 + 32
}
temp := Celsius(36.5)
fmt.Println(temp) // 36.5°C
fmt.Println(temp.ToFahrenheit()) // 97.7
7.3 类型别名 vs 类型定义
go
// 类型别名(Type Alias):完全等同
type MyInt = int // MyInt 就是 int,完全一样
var a MyInt = 10
var b int = 20
a = b // ✅ 可以直接赋值(它们是同一个类型)
// 类型定义(Type Definition):创建新类型
type YourInt int // YourInt 是基于int的新类型
var c YourInt = 10
// var d int = c // ❌ 编译错误:类型不匹配
var d int = int(c) // ✅ 需要显式转换
八、类型选择决策树
你需要存储什么内容?
├── 真/假值 → bool
├── 整数
│ ├── 普通计数/索引 → int
│ ├── 需要64位保证 → int64
│ ├── 需要节省内存(数组)→ int8/int16/int32
│ ├── 无符号值 → uint/uint8/uint16/uint32/uint64
│ ├── 单个字节 → byte (uint8)
│ └── Unicode字符 → rune (int32)
├── 小数
│ ├── 科学计算 → float64
│ ├── 图形/大数组 → float32
│ └── 金钱 → ❌ 用整数(分)
├── 文本
│ ├── 一般文本 → string
│ └── 可变字节 → []byte
└── 复数 → complex128
九、本篇总结
✅ 本篇我们全面了解了Go语言的基本数据类型:
- 布尔型:严格的true/false,与整数不互通
- 整数型:从int8到int64,int是默认选择
- 浮点型:float64为默认,注意精度问题和金钱陷阱
- 复数型:内建支持,用于科学计算
- 字符串型:不可变,UTF-8编码,区分字节和字符
- 类型系统:byte/rune别名,类型定义vs别名
💡 Go的基本类型虽然数量不多,但设计精良。记住几个核心原则:
- 默认整数用int,需要特定范围时用intX/uintX
- 金钱永远不用浮点数,用整数表示最小单位
- 用==比较浮点数前先想想精度问题
- 遍历字符串时注意是用range(按字符)还是索引(按字节)
下一篇,我们将深入探讨byte与rune的区别和使用场景,理解Go语言的字符处理机制。