Map和结构体

教程地址:点击去往视频教程open in new window

1. Map

map是一个无序的key-value(键值对)格式的集合。

如下图:

img

map是引用类型,零值是nil,所以必须初始化后才能使用

示例:

func main() {
    //int为key的类型 string为value的类型
	var m map[int]string = map[int]string{}
    //2是代表容量 也就是在内存中占用多大的空间
    //指定容量 有助于不额外占用内存
	//make方式 var m = make(map[int]string, 2) 
	m[1] = "mszlu"
	m[2] = "新版go教程"
	change(m)
	fmt.Println(m)
}
func change(m map[int]string) {
	m[2] = "2024新版go教程"
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

key和value是成对出现,key是唯一的

比如:

func main() {
	var m map[int]string = map[int]string{
		1: "mszlu",
		2: "2024新版go教程",
	}
	//当我们改了对应key的value,会覆盖原有的值
	m[1] = "码神之路"
	fmt.Println(m)
}
1
2
3
4
5
6
7
8
9

value可以是多种类型,比如我们要实现1对多的形式,value就可以使用切片

func main() {
	m := make(map[string][]int)
	v := []int{
		1, 2, 3, 4, 5,
	}
	m["key"] = v
	fmt.Println(m)
}
1
2
3
4
5
6
7
8

从map取值有两种方式

func main() {
	m := make(map[string]string)
	m["key"] = "value"
	m["key1"] = "value1"
	//通过key取值
	v, ok := m["key"]
	if ok {
		fmt.Println(v)
	}
	//通过for range取值 注意无序的
    //想要有序的结果,可以遍历后对key排序
	for k, v := range m {
		fmt.Println(k, v)
	}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

删除map中的键值对

func main() {
	m := make(map[string]string)
	m["key"] = "value"
	m["key1"] = "value1"
	delete(m, "key1")
	fmt.Println(m)
}
1
2
3
4
5
6
7

1. 结构体定义

通过关键字type可以定义结构体,结构体是一种复合类型,通过将各个不同的类型通过自定义的方式集合起来。

type User struct {
	Name    string
	Age     int
	Address Address
}
type Address struct {
	City string
}
1
2
3
4
5
6
7
8
  • User为结构体类型的名称,同一包内不能重复
  • Name,Age等为结构体的字段或者叫成员变量
  • 字段名必须唯一
  • 字段首字母大写代表访问权限为public(对所有包可见)
  • 字段首字母小写代表访问权限为private(仅对本包有效)
  • 结构体之间可以嵌套

结构体的定义只是一种内存布局的描述,只有当结构体初始化时,才会真正的分配内存

var ins T //T为结构体类型 ins为变量名称
//注意如果不赋值,成员变量会使用零值初始化
1
2

2. 结构体使用

type User struct {
	Name string
	Age  int
}

func main() {
	user := User{}
    //通过等号赋值
	user.Name = "mszlu"
	user.Age = 20
    //通过.取值
	fmt.Printf("name:%s,age:%d \n", user.Name, user.Age)
}
1
2
3
4
5
6
7
8
9
10
11
12
13

函数参数传递为值传递,如果将结构体做为参数,会频繁的进行复制,也就是会频繁的分配内存

func main() {
    //通过&取地址符号,获取结构体User的指针
	user := &User{}
    //或者使用new关键字 user := new(User)
	change(user)
	fmt.Printf("name:%s,age:%d \n", user.Name, user.Age)
}

func change(user *User) {
	user.Name = "mszlu"
	user.Age = 20
}
1
2
3
4
5
6
7
8
9
10
11
12

推荐在使用结构体传参时,使用指针,也就是说推荐使用*T,如果使用切片推荐使用[]*T

结构体中的字段可以没有名字,这个字段称为匿名字段或者叫内嵌字段,一个结构体中可以包含多个匿名字段

type Human struct {
	Sex int
}
//实际上是一种组合的方式实现
type Man struct {
	Name string
	Human
}
func main() {
	m := Man{}
	m.Sex = 1
	fmt.Println(m)
}
1
2
3
4
5
6
7
8
9
10
11
12
13

3. 方法

方法函数类似,唯一的不同,方法拥有一个接收器,这个接收器可以是任意类型(除了接口类型)

func main() {
	u := User{}
	u.ModifyName("new")
	fmt.Println(u)
}

type User struct {
	Name string
}
//如果这不用指针 修改会不成功
//推荐接收器使用指针形式
//(a *User)这个就叫接收器
func (a *User) ModifyName(name string) {
	a.Name = name
}

type Hp int

func (a Hp) Get() Hp {
	return a + 100
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

当方法作用于非指针接收器时,Go语言会在代码运行时将接收器的值复制一份,在非指针接收器的方法中可以获取接收器的成员值,但修改后无效

4. 内存对齐

  • 现代计算机中内存空间都是按照字节(byte)进行划分的
  • 理论上讲对于任何类型的变量访问都可以从任意地址开始
  • 有些CPU可以访问任意地址上的任意数据,而有些CPU只能在特定地址访问数据,因此不同硬件平台具有差异性,这样的代码就不具有移植性
  • 如果在编译时,将分配的内存进行对齐,就具有平台移植性
  • CPU每次寻址都是要消费时间的
  • CPU 访问内存时,并不是逐个字节访问,而是以字长(word size)为单位访问
  • 64位COU在单位时间能处理字长为64位的二进制数据,字长为8字节
  • 数据结构应该尽可能地在自然边界上对齐,如果访问未对齐的内存,处理器需要做两次内存访问,而对齐的内存访问仅需要一次访问,内存对齐后可以提升性能。

Go语言有内存对齐,所有类型都需要进行内存对齐,这里我们以结构体举例,比较容易理解

//64位系统字长为8,
type test1 struct {
    a bool // 1
    b int32 // 4
    c string // 16
}

type test2 struct {
    a int32 // 4
    b string // 16
    c bool // 1
}


func main()  {
    var t1 test1
    var t2 test2
	//t1 size is  24
	//t2 size is  32
    fmt.Println("t1 size is ",unsafe.Sizeof(t1))
    fmt.Println("t2 size is ",unsafe.Sizeof(t2))
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

test1的内存布局:

img

test2的内存布局:

img