文章目录
- [Go 常用类型速查表 + 实战建议(实战向)](#Go 常用类型速查表 + 实战建议(实战向))
- [基础值类型(Value Types)](#基础值类型(Value Types))
-
- 整型(Integer)
-
- 有符号整型(可以表示负数)
- 无符号整型(只能表示非负数)
- [Go 里实际测试整型](#Go 里实际测试整型)
- 实战建议
- 浮点型(Float)
- 布尔型(Bool)
- [字符串 & 字符(String / Rune / Byte)](#字符串 & 字符(String / Rune / Byte))
- [数组 vs 切片(Array vs Slice)](#数组 vs 切片(Array vs Slice))
-
- Array(数组:几乎不用)
- Slice(切片:核心⭐⭐⭐⭐⭐)
-
- [Slice 的本质(重点🔥)](#Slice 的本质(重点🔥))
- 底层结构理解(很关键)
- [为什么 slice 修改会影响原数据?](#为什么 slice 修改会影响原数据?)
- [append 的坑(面试常考🔥)](#append 的坑(面试常考🔥))
- 分两种情况:
- 如何判断有没有扩容?
- 实战建议(非常重要)
- 小总结(记住这3句话就够了)
- [Map(字典) & Struct(结构体)](#Map(字典) & Struct(结构体))
-
- Map(字典)
- [Map 基本操作](#Map 基本操作)
- 核心坑点(必会🔥)
-
- [❌ 未初始化直接使用 → panic](#❌ 未初始化直接使用 → panic)
- [✅ 正确写法](#✅ 正确写法)
- [判断 key 是否存在(非常重要)](#判断 key 是否存在(非常重要))
- [Map 特点总结](#Map 特点总结)
- Struct(结构体)
- [Struct 使用方式](#Struct 使用方式)
- [Struct 是值类型(重点)](#Struct 是值类型(重点))
-
- [✅ 想共享数据 → 用指针](#✅ 想共享数据 → 用指针)
- [Struct 实战建议](#Struct 实战建议)
- 总结
- 实战总结(专栏金句版)
- [Go 常用类型:最小实战例子](#Go 常用类型:最小实战例子)
-
- [1️⃣ string / rune / byte 差异](#1️⃣ string / rune / byte 差异)
- [2️⃣ slice 扩容 & 引用语义(高频坑)](#2️⃣ slice 扩容 & 引用语义(高频坑))
- [3️⃣ map nil panic(经典)](#3️⃣ map nil panic(经典))
- [4️⃣ struct 值拷贝 vs 指针](#4️⃣ struct 值拷贝 vs 指针)
- [5️⃣ interface 动态类型](#5️⃣ interface 动态类型)
- [6️⃣ nil slice vs empty slice(很专业)](#6️⃣ nil slice vs empty slice(很专业))
Go 常用类型速查表 + 实战建议(实战向)
在 Go 实战开发中,理解不同类型的语义、零值、拷贝行为,比记语法更重要。
基础值类型(Value Types)
整型(Integer)
有符号整型(可以表示负数)
| 类型 | 位数 | 取值范围 | 备注 |
|---|---|---|---|
int |
32/64 | -2³¹~2³¹-1(32位)或 -2⁶³~2⁶³-1(64位) | 与平台相关 |
int8 |
8 | -128 ~ 127 | 最小整型 |
int16 |
16 | -32768 ~ 32767 | |
int32 |
32 | -2³¹ ~ 2³¹-1 | 别名 rune |
int64 |
64 | -2⁶³ ~ 2⁶³-1 |
无符号整型(只能表示非负数)
| 类型 | 位数 | 取值范围 | 备注 |
|---|---|---|---|
uint |
32/64 | 0 ~ 2³²-1 或 0 ~ 2⁶⁴-1 | 与平台相关 |
uint8 |
8 | 0 ~ 255 | 别名 byte |
uint16 |
16 | 0 ~ 65535 | |
uint32 |
32 | 0 ~ 2³²-1 | |
uint64 |
64 | 0 ~ 2⁶⁴-1 | |
uintptr |
32/64 | 0 ~ 2³²-1 或 0 ~ 2⁶⁴-1 | 用于底层指针操作 |
小结:
- 有符号整型可以存负数;无符号整型只能存 0 及正数。
int和uint会根据平台(32位/64位)变化大小。rune是int32的别名,用于表示 Unicode 字符。byte是uint8的别名,用于表示字节。
Go 里实际测试整型
下面的示例演示了各种整型的赋值、打印、类型转换:
go
package main
import (
"fmt"
)
func main() {
// 有符号整型
var a int = -100
var b int8 = 120
var c int16 = -30000
var d int32 = 100000
var e int64 = -1000000000
fmt.Println("有符号整型:")
fmt.Println(a, b, c, d, e)
// 无符号整型
var ua uint = 100
var ub uint8 = 200
var uc uint16 = 50000
var ud uint32 = 3000000000
var ue uint64 = 100000000000
fmt.Println("无符号整型:")
fmt.Println(ua, ub, uc, ud, ue)
// 类型转换
sum := int32(b) + d // int8 -> int32 再加
fmt.Println("sum =", sum)
// 注意负数转无符号会变成大数(取值范围)
negative := int8(-1)
fmt.Println("int8(-1) =", negative) // 输出 -1
u := uint8(negative)
fmt.Println("uint8(-1) =", u) // 输出 255
abc := int8(-2)
cc := uint8(abc)
fmt.Println("cc =", cc) // 输出 254
}
输出结果:
bash
有符号整型:
-100 120 -30000 100000 -1000000000
无符号整型:
100 200 50000 3000000000 100000000000
sum = 100120
int8(-1) = -1
uint8(-1) = 255
cc = 254
实战建议
- ✅ 业务字段:优先
int - ✅ ID、时间戳、计数:
int64 - ✅ 网络协议/文件格式:显式
int32/uint32 - ❌ 不要滥用
uint(容易引入负数比较 Bug)
浮点型(Float)
Go 浮点型概念
| 类型 | 精度 | 说明 |
|---|---|---|
float32 |
约 6~7 位十进制有效数字 | 占 4 字节 |
float64 |
约 15~16 位十进制有效数字 | 占 8 字节,Go 默认浮点类型 |
特点:
- 浮点数可以表示小数,也能表示非常大的数和非常小的数(科学计数法)。
- 浮点数有 精度限制 ,不能精确表示所有小数,例如
0.1在计算机里不是精确的 0.1。
基本用法
go
package main
import (
"fmt"
)
func main() {
var a float32 = 3.1415926
var b float64 = 3.14159265358979323846
fmt.Println("float32:", a) // 精度有限,输出 3.1415925
fmt.Println("float64:", b) // 精度更高,输出 3.141592653589793
// 科学计数法
c := 1.23e4 // 1.23 × 10^4 = 12300.0
d := 5.67e-3 // 5.67 × 10^-3 = 0.00567
fmt.Println("c:", c)
fmt.Println("d:", d)
}
输出结果:
bash
float32: 3.1415925
float64: 3.141592653589793
c: 12300
d: 0.00567
类型转换
float32和float64不能直接运算,需要显式转换:
go
func main() {
var f32 float32 = 1.5
var f64 float64 = 2.7
sum := float64(f32) + f64
fmt.Println(sum)
}
- 浮点数和整数也需要转换:
go
func main() {
i := 3
f := 2.5
sum := float64(i) + f
fmt.Println(sum) // 5.5
}
注意事项
- 浮点数计算可能有精度误差:
go
fmt.Println(0.1 + 0.2) // 输出 0.30000000000000004
- 默认写浮点数时,Go 会用
float64:
go
f := 3.14 // 默认 float64
布尔型(Bool)
基本概念
- 类型:
bool - 取值:
true或false - 不能把整数或其他类型当作布尔值(没有 0 = false, 非 0 = true 的魔法转换)
go
var flag bool = true
fmt.Println(flag) // true
flag = false
fmt.Println(flag) // false
条件判断
go
package main
import "fmt"
func main() {
a := 10
b := 20
// 比较表达式会得到 bool
isGreater := a > b
fmt.Println(isGreater) // false
if isGreater { // 直接用布尔值if isGreater == true { ... } 也可以,但没必要
fmt.Println("a 大于 b")
} else {
fmt.Println("a 不大于 b")
}
}
输出结果:
bash
false
a 不大于 b
- 注意:你 不能 写
if a { ... },会报错,因为a是整数,不是布尔值。 - 必须写成
if a != 0 { ... }或直接用比较表达式。
与 C/C++ 的不同
| 语言 | 隐式转换 |
|---|---|
| C/C++ | int 0 = false, 非 0 = true |
| Go | ❌ 没有隐式转换,必须是 bool |
这种设计让 Go 的条件判断更安全,不会因为数字或指针被误用而导致逻辑错误。
布尔运算
go
package main
import (
"fmt"
)
func main() {
a := true
b := false
fmt.Println(a && b) // false 有假则为假
fmt.Println(a || b) // true 有真则为真
fmt.Println(!a) // false 反义
}
输出结果:
bash
false
true
false
- 支持
&&(与)、||(或)、!(非) - 没有隐式类型转换,更加类型安全
字符串 & 字符(String / Rune / Byte)
注: 这里那个string与其他的区别是""与''的区别
类型概览
| 类型 | 实际类型 | 用途 |
|---|---|---|
string |
不可变字节序列 | 存储文本,UTF-8 编码 |
byte |
uint8 |
处理二进制数据,文件/网络 I/O |
rune |
int32 |
表示一个 Unicode 字符,适合中文、emoji 等 |
核心知识点
字符串长度
go
package main
import (
"fmt"
)
func main() {
s := "你好" // 2 个字符
fmt.Println(len(s)) // 6 字节(每个中文 3 字节 UTF-8)
fmt.Println(len([]rune(s))) // 2 个字符
}
解释:
len(s)→ 字节长度(string 底层是 byte slice)len([]rune(s))→ 字符长度(Unicode 字符数)
输出结果:
bash
6
2
字符串不可变
go
s := "hello"
// s[0] = 'H' // ❌ 编译错误,string 是只读的
如果要修改:
go
package main
import (
"fmt"
)
func main() {
s := "hello"
b := []byte(s) // 新建一个可变的 []byte 切片,指向 s 的底层数组
// fmt.Println(b) // [104 101 108 108 111](ASCII 码)
b[0] = 'H' // 可以修改
b[4] = '!' // 可以修改
b = append(b, 'w') // 可以修改
s2 := string(b) // 再转换回 string
fmt.Println(s2) // Hello
}
输出结果:
bash
Hell!w
rune 与 byte 的区别
go
package main
import (
"fmt"
"reflect"
)
func main() {
c := '你' // '你' 用单引号括起来,是 字符常量,默认类型是 rune(也就是 int32)
b := byte('A') // 'A' 用单引号括起来,byte('A') 强制转换为 uint8
fmt.Println(c)
fmt.Println(b)
fmt.Println(reflect.TypeOf(c))
fmt.Println(reflect.TypeOf(b))
fmt.Printf("%c %T\n", c, c) // 你 int32,%c → 按 字符 打印(Unicode 字符)
fmt.Printf("%c %T\n", b, b) // A uint8,%T → 打印变量类型
var d rune = '你'
fmt.Println(d) // 输出 Unicode 数值:20320
}
输出结果:
bash
20320
65
int32
uint8
你 int32
A uint8
20320
'你' 的 Unicode 编码是 U+4F60(十进制 20320)
byte(uint8)最大只能存 0~255
所以 '你' 必须用 rune/int32 才能存下完整 Unicode 值
'A' 的 ASCII 编码是 65
65 落在 0~255 范围内
所以可以安全存入 byte/uint8
- byte 适合处理 ASCII 或二进制
- rune 适合处理中文或多字节 Unicode 字符
实战建议
-
处理二进制 → 用
[]bytegodata := []byte{0x01, 0x02, 0x03}输出结果:
[1 2 3] -
处理中文/字符 → 用
[]runegos := "你好,Go" r := []rune(s) r[0] = '您' fmt.Println(string(r)) // 您好,Go -
不要直接修改 string → string 是只读,必须通过转换为
[]byte或[]rune再修改。
数组 vs 切片(Array vs Slice)
在 Go 中,数组(Array)和切片(Slice)都是用于存储一组数据的结构,但它们的设计理念和使用场景完全不同。
Array(数组:几乎不用)
go
var a [3]int = [3]int{1, 2, 3}
特点(重点理解2个就够了)
1.长度是类型的一部分
go
package main
import "fmt"
func main() {
var a [3]int
var b [4]int
fmt.Println(a)
fmt.Println(b)
}
// a 和 b 是完全不同的类型,不能互相赋值
输出结果:
bash
[0 0 0]
[0 0 0 0]
👉 就像:
[3]int≠[4]int- 不能互相赋值或传参
2.是值拷贝(很坑)
go
package main
import "fmt"
func main() {
a := [3]int{1, 2, 3}
b := a
b[0] = 100
fmt.Println(a) // [1 2 3]
fmt.Println(b) // [100 2 3]
}
输出结果:
bash
[1 2 3]
[100 2 3]
👉 修改 b,不影响 a(因为复制了一份)
- 赋值会复制一份数据
- 修改副本不会影响原数据
一句话:👉 数组 = 固定长度 + 拷贝传递 → 不灵活,所以很少用
Slice(切片:核心⭐⭐⭐⭐⭐)
go
package main
import "fmt"
func main() {
s := []int{1, 2, 3}
b := append(s, 4)
fmt.Println(b)
}
👉 输出:
go
[1 2 3 4]
Slice 的本质(重点🔥)
切片本质上是对数组的一层封装:
go
type slice struct {
ptr *T // 指向底层数组
len int // 当前长度
cap int // 容量
}
👉 可以理解成:
切片 =Slice = 引用 + 长度 + 容量
底层结构理解(很关键)
text
s := []int{1,2,3}
底层其实是:
数组: [1 2 3 _ _]
s:
ptr → 指向数组第一个元素
len = 3
cap = 5(可能更大)
为什么 slice 修改会影响原数据?
go
s1 := []int{1,2,3}
s2 := s1
s2[0] = 100
fmt.Println(s1) // [100 2 3]
👉 因为:
- s1 和 s2 指向 同一个底层数组
append 的坑(面试常考🔥)
go
s := []int{1,2,3}
s2 := s
s2 = append(s2, 4)
分两种情况:
情况1:没扩容(共用数组)
text
原数组够用 → 直接追加
👉 修改会互相影响
❗情况2:发生扩容(重点)
text
原数组不够 → 创建新数组
👉 此时:
- s2 指向新数组
- s 还是旧数组
- 两者彻底分离 ❗
如何判断有没有扩容?
go
fmt.Println(len(s), cap(s))
👉 如果 append 后 cap 变了,说明扩容了
go
package main
import "fmt"
func main() {
s := []int{1, 2, 3}
s2 := s
s2 = append(s2, 4)
fmt.Println(s) // 输出 [1 2 3]
fmt.Println(s2)
fmt.Println(len(s), cap(s))
fmt.Println(len(s2), cap(s2))
}
输出结果:
bash
[1 2 3]
[1 2 3 4]
3 3
4 6
实战建议(非常重要)
-
函数参数统一用 slice
gofunc foo(s []int) {}👉 不要用数组!
-
想避免数据被改 → copy
gos2 := make([]int, len(s1)) copy(s2, s1)
-
预分配容量(性能优化)
gos := make([]int, 0, 100)👉 避免频繁扩容
小总结(记住这3句话就够了)
- 数组:固定长度 + 值拷贝 → 基本不用
- 切片:引用底层数组 → 是核心
- append 可能换底层数组 → 会"断开关系"
Map(字典) & Struct(结构体)
在 Go 中,map 和 struct 是两种非常核心的数据结构:
map:用于键值存储(类似字典)struct:用于定义数据模型(类似对象)
Map(字典)
go
package main
import "fmt"
func main() {
m := make(map[string]int)
m["a"] = 1
m["b"] = 2
fmt.Println(m)
delete(m, "a")
fmt.Println(m)
}
输出结果:
bath
map[a:1 b:2]
map[b:2]
常见定义方式
go
map[string]int // key: string, value: int
map[int]string // key: int, value: string
Map 基本操作
go
package main
import "fmt"
func main() {
m := make(map[string]int)
m["a"] = 1 // 添加/修改
fmt.Println(m) // 获取
fmt.Println(m["a"]) // 获取
delete(m, "a") // 删除
fmt.Println(m)
}
输出结果:
bath
map[a:1]
1
map[]
核心坑点(必会🔥)
❌ 未初始化直接使用 → panic
go
var m map[string]int
m["a"] = 1 // panic: assignment to entry in nil map
👉 原因:
var m map[...]只是声明- 此时
m == nil - 不能直接写入数据
✅ 正确写法
go
m := make(map[string]int)
或者:
go
m := map[string]int{
"a": 1,
"b": 2,
}
判断 key 是否存在(非常重要)
go
package main
import "fmt"
func main() {
m := make(map[string]int)
value, ok := m["a"]
if ok {
fmt.Println("存在:", value)
} else {
fmt.Println("不存在")
}
m["a"] = 1
value, ok = m["a"]
if ok {
fmt.Println("存在:", value)
} else {
fmt.Println("不存在")
}
}
输出结果:
bath
不存在
存在: 1
👉 这是 Go 的经典写法(面试必问)
Map 特点总结
- 是引用类型(类似指针)
- 无序(遍历顺序随机)
- 读不存在的 key → 返回零值(不会报错)
- 写 nil map → panic
Struct(结构体)
go
type User struct {
ID int
Name string
Age int
}
👉 用于定义一类数据结构(类似 Java 的 class)
Struct 使用方式
Go
package main
import "fmt"
func main() {
type User struct {
ID int
Name string
Age int
}
u1 := User{1, "Tom", 18}
//初始化
u2 := User{
ID: 2,
Name: "Jerry",
Age: 20,
}
fmt.Println(u1)
fmt.Println(u2)
// 访问字段
fmt.Println(u1.Name)
}
输出结果:
bath
{1 Tom 18}
{2 Jerry 20}
Tom
Struct 是值类型(重点)
go
package main
import "fmt"
func main() {
type User struct {
ID int
Name string
Age int
}
u1 := User{1, "Tom", 18}
u2 := u1
u2.Name = "Jack"
fmt.Println(u1) // {1 Tom 18}
fmt.Println(u2) // {1 Jack 18}
fmt.Println(u1.Name) // Tom
fmt.Println(u2.Name) // Jack
}
输出结果:
bath
{1 Tom 18}
{1 Jack 18}
Tom
Jack
👉 修改 u2 不影响 u1(发生了拷贝)
✅ 想共享数据 → 用指针
go
package main
import "fmt"
func main() {
type User struct {
ID int
Name string
Age int
}
u1 := User{1, "Tom", 18}
fmt.Println(u1) // 输出:{1 Tom 18}
u2 := &u1
u2.Name = "Jack"
fmt.Println(u2)
fmt.Println(u1.Name)
fmt.Println(u2.Name)
}
输出结果:
bath
{1 Tom 18}
&{1 Jack 18}
Jack
Jack
Struct 实战建议
👉 在实际开发中,struct 几乎无处不在:
- 领域模型(User、Order)
- DTO(接口请求/响应)
- 配置文件(YAML/JSON)
- 数据库存储结构
总结
Map
- 必须
make初始化 - 是引用类型
- 常用于键值存储
Struct
- 是值类型(默认拷贝)
- 用于定义数据结构
- 想共享 → 用指针
实战总结(专栏金句版)
Go 的类型系统可以分为:
基础值类型(int、float64、bool、string)
复合类型(slice、map、struct)
抽象与引用类型(pointer、interface、chan、func)
实战中,80% 时间用的是:
int、string、[]T、map、struct、error、interface
Go 常用类型:最小实战例子
1️⃣ string / rune / byte 差异
go
package main
import "fmt"
func main() {
s := "你好Go"
fmt.Println("len(s):", len(s))
fmt.Println("len([]rune(s)):", len([]rune(s)))
for i := 0; i < len(s); i++ {
fmt.Printf("%x ", s[i])
}
fmt.Println()
for _, r := range s {
fmt.Printf("%c ", r)
}
}
输出结果:
bath
len(s): 8
len([]rune(s)): 4
e4 bd a0 e5 a5 bd 47 6f
你 好 G o
2️⃣ slice 扩容 & 引用语义(高频坑)
go
package main
import "fmt"
func main() {
s := make([]int, 0, 2)
fmt.Printf("addr=%p len=%d cap=%d\n", s, len(s), cap(s))
s = append(s, 1, 2)
fmt.Printf("addr=%p len=%d cap=%d\n", s, len(s), cap(s))
s = append(s, 3)
fmt.Printf("addr=%p len=%d cap=%d\n", s, len(s), cap(s))
}
输出结果:
bath
addr=0xc000098040 len=0 cap=2
addr=0xc000098040 len=2 cap=2
addr=0xc0000ba000 len=3 cap=4
3️⃣ map nil panic(经典)
go
package main
func main() {
var m map[string]int
m["a"] = 1 // panic:尝试对空 map 中的条目赋值
}
对比正确写法:
go
package main
import "fmt"
func main() {
m := make(map[string]int)
m["a"] = 1
fmt.Println(m) // map[a:1]
}
👉 运维 / 后端新手最常见坑之一
4️⃣ struct 值拷贝 vs 指针
go
package main
import "fmt"
type User struct {
Name string
}
func modifyByValue(u User) {
u.Name = "Jerry"
}
func modifyByPointer(u *User) {
u.Name = "Jerry"
}
func main() {
u := User{Name: "Tom"}
modifyByValue(u)
fmt.Println(u.Name) // Tom
modifyByPointer(&u)
fmt.Println(u.Name) // Jerry
}
5️⃣ interface 动态类型
go
package main
import "fmt"
func printType(v interface{}) {
fmt.Printf("type=%T, value=%v\n", v, v)
}
func main() {
printType(10)
printType("hello")
printType([]int{1, 2})
}
输出:
type=int, value=10
type=string, value=hello
type=[]int, value=[1 2]
6️⃣ nil slice vs empty slice(很专业)
go
func main() {
var s1 []int
s2 := []int{}
fmt.Println(s1 == nil) // true
fmt.Println(s2 == nil) // false
fmt.Println(len(s1), len(s2)) // 0 0
}