Map和结构体
教程地址:点击去往视频教程
1. Map
map
是一个无序的
,key-value(键值对)
格式的集合。
如下图:
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
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
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
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
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
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
2
3
4
5
6
7
8
- User为结构体类型的名称,同一包内不能重复
- Name,Age等为结构体的字段或者叫成员变量
- 字段名必须唯一
- 字段首字母大写代表访问权限为public(对所有包可见)
- 字段首字母小写代表访问权限为private(仅对本包有效)
- 结构体之间可以嵌套
结构体的定义只是一种
内存布局
的描述,只有当结构体初始化时,才会真正的分配内存
var ins T //T为结构体类型 ins为变量名称
//注意如果不赋值,成员变量会使用零值初始化
1
2
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
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
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
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
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
test1
的内存布局:
test2
的内存布局: