【Go】--map和struct数据类型
Go 语言中的 map 和 struct 数据类型详解
map用于存储键值对,而struct用于组织不同类型的数据字段。
- Map:适合处理动态的、无序的键值对数据
- Struct:适合组织固定的、有结构的数据字段
1. Map(映射表)数据类型
1.1 基本概念
Map是一种无序的键值对集合,基于哈希表实现。
特性:
- 键必须是可比较的类型(string、int等)
- 值可以是任意类型
- 无序存储,遍历顺序不确定
1.2 初始化方法
1.2.1 字面量初始化
// 整数键的map
mp1 := map[int]string{0: "a",1: "b", 2: "c",3: "d",4: "e",
}// 字符串键的map
mp2 := map[string]int{"a": 0,"b": 22,"c": 33,
}
1.2.2 使用make函数初始化
// 指定初始容量
mp1 := make(map[string]int, 8)
mp2 := make(map[string][]int, 10)
重要:
- Map是引用类型,未初始化的map可以访问但无法存放元素
- 必须为map分配内存后才能使用
1.3 访问操作
1.3.1 基本访问
mp := map[string]int{"a": 0,"b": 1,"c": 2,"d": 3,
}fmt.Println(mp["a"]) // 输出: 0
fmt.Println(mp["b"]) // 输出: 1
fmt.Println(mp["d"]) // 输出: 3
fmt.Println(mp["f"]) // 输出: 0 (不存在键返回零值)
1.3.2 存在性检查
if val, exist := mp["f"]; exist {fmt.Println(val)
} else {fmt.Println("key不存在")
}
1.4 存储操作
1.4.1 基本存储
mp := make(map[string]int, 10)
mp["a"] = 1
mp["b"] = 2
fmt.Println(mp) // 输出: map[a:1 b:2]
1.4.2 覆盖操作
mp := make(map[string]int, 10)
mp["a"] = 1
mp["b"] = 2if _, exist := mp["b"]; exist {mp["b"] = 3 // 覆盖原有值
}
fmt.Println(mp) // 输出: map[a:1 b:3]
1.5 特殊键值处理
1.5.1 NaN键的特殊情况
import "math"func main() {mp := make(map[float64]string, 10)mp[math.NaN()] = "a"mp[math.NaN()] = "b"mp[math.NaN()] = "c"_, exist := mp[math.NaN()]fmt.Println(exist) // 输出: falsefmt.Println(mp) // 输出: map[NaN:c NaN:a NaN:b]
}
注意事项:
- NaN作为键时会出现多个相同键的情况
- 无法正常判断NaN键是否存在
- 无法删除NaN键值对
- 应避免使用NaN作为map的键
1.6 删除操作
func main() {mp := map[string]int{"a": 0,"b": 1,"c": 2,"d": 3,}fmt.Println(mp) // 输出: map[a:0 b:1 c:2 d:3]delete(mp, "a")fmt.Println(mp) // 输出: map[b:1 c:2 d:3]
}
1.7 遍历操作
func main() {mp := map[string]int{"a": 0,"b": 1,"c": 2,"d": 3,}for key, val := range mp {fmt.Println(key, val)}// 可能的输出(无序):// c 2// d 3// a 0// b 1
}
1.8 清空操作
1.8.1 Go 1.21之前的方法
func main() {m := map[string]int{"a": 1,"b": 2,}for k, _ := range m {delete(m, k)}fmt.Println(m) // 输出: map[]
}
1.8.2 Go 1.21及之后的方法
func main() {m := map[string]int{"a": 1,"b": 2,}clear(m)fmt.Println(m) // 输出: map[]
}
1.9 长度获取
func main() {mp := map[string]int{"a": 0,"b": 1,"c": 2,"d": 3,}fmt.Println(len(mp)) // 输出: 4
}
2. Struct(结构体)数据类型
2.1 基本概念
Struct是用于存储一组不同类型数据的复合类型。
特性:
- 可以存储不同类型的数据字段
- 通过组合实现类似继承的功能
- 支持方法绑定
2.2 结构体声明
2.2.1 基本声明
type Programmer struct {Name stringAge intJob stringLanguage []string
}type Person struct {name stringage int
}
2.2.2 相邻字段简化声明
type Rectangle struct {height, width, area intcolor string
}
命名规则:
- 结构体本身及其字段都遵守大小写命名的暴露方式
- 大写字母开头的字段可被外部包访问
- 小写字母开头的字段只能在包内访问
2.3 实例化方法
2.3.1 字段名初始化(推荐)
programmer := Programmer{Name: "jack",Age: 19,Job: "coder",Language: []string{"Go", "C++"},
}
2.3.2 省略字段名初始化(不推荐)
programmer := Programmer{"jack",19,"coder",[]string{"Go", "C++"},
}
2.4 构造函数模式
2.4.1 基本构造函数
type Person struct {Name stringAge intAddress stringSalary float64
}func NewPerson(name string, age int, address string, salary float64) *Person {return &Person{Name: name, Age: age, Address: address, Salary: salary}
}
2.4.2 选项模式(函数式选项模式)
type Person struct {Name stringAge intAddress stringSalary float64Birthday string
}// 选项函数类型
type PersonOptions func(p *Person)// 具体的选项函数
func WithName(name string) PersonOptions {return func(p *Person) {p.Name = name}
}func WithAge(age int) PersonOptions {return func(p *Person) {p.Age = age}
}func WithAddress(address string) PersonOptions {return func(p *Person) {p.Address = address}
}func WithSalary(salary float64) PersonOptions {return func(p *Person) {p.Salary = salary}
}// 构造函数
func NewPerson(options ...PersonOptions) *Person {p := &Person{}for _, option := range options {option(p)}// 默认值处理if p.Age < 0 {p.Age = 0}return p
}// 使用示例
func main() {p1 := NewPerson(WithName("John Doe"),WithAge(25),WithAddress("123 Main St"),WithSalary(10000.00),)p2 := NewPerson(WithName("Mike jane"),WithAge(30),)
}
2.5 组合(Composition)
2.5.1 显式组合
type Person struct {name stringage int
}type Student struct {p Personschool string
}type Employee struct {p Personjob string
}// 使用示例
student := Student{p: Person{name: "jack", age: 18},school: "lili school",
}
fmt.Println(student.p.name) // 输出: jack
2.5.2 匿名组合(类似继承)
type Person struct {name stringage int
}type Student struct {Person // 匿名组合school string
}type Employee struct {Person // 匿名组合job string
}// 使用示例
student := Student{Person: Person{name: "jack", age: 18},school: "lili school",
}
fmt.Println(student.name) // 直接访问,输出: jack
2.6 指针操作
p := &Person{name: "jack"
}// 指针可以直接访问字段,无需解引用
fmt.Println(p.name) // 输出: jack
3. Map和Struct的对比
3.1 使用场景对比
特性 | Map | Struct |
---|---|---|
数据结构 | 键值对集合 | 字段集合 |
键类型 | 必须可比较 | 固定字段名 |
顺序 | 无序 | 字段定义顺序固定 |
内存布局 | 哈希表,动态 | 连续内存,静态 |
性能 | O(1)访问 | O(1)字段访问 |
适用场景 | 动态键值数据 | 固定结构数据 |
3.2 性能考虑
- Map:适合动态键值对,但哈希计算有开销
- Struct:内存连续,访问速度快,但结构固定
4. 最佳实践
4.1 Map使用建议
- 预分配容量:使用make时指定合理容量减少扩容
- 存在性检查:访问前检查键是否存在
- 避免NaN键:避免使用math.NaN()作为键
- 并发安全:多goroutine访问时使用sync.Map
4.2 Struct使用建议
- 使用选项模式:复杂结构体使用函数式选项模式
- 合理使用组合:优先使用组合而非模拟继承
- 字段命名规范:遵循Go的命名约定
- 方法绑定:为结构体绑定相关方法
5. 实际应用示例
5.1 学生管理系统
type Student struct {ID intName stringGrades map[string]float64Contact struct {Phone stringEmail string}
}func NewStudent(id int, name string) *Student {return &Student{ID: id,Name: name,Grades: make(map[string]float64),}
}// 使用示例
student := NewStudent(1, "Alice")
student.Grades["Math"] = 95.5
student.Grades["English"] = 88.0
student.Contact.Phone = "123-456-7890"
student.Contact.Email = "alice@example.com"
5.2 配置管理系统
type Config struct {Database struct {Host stringPort intUsername stringPassword string}Server struct {Port intTimeout time.Duration}Features map[string]bool
}func LoadConfig() *Config {config := &Config{Features: make(map[string]bool),}// 加载配置逻辑...return config
}