侧边栏壁纸
博主头像
梦幻世界博主等级

行动起来,活在当下

  • 累计撰写 23 篇文章
  • 累计创建 2 个标签
  • 累计收到 2 条评论

目 录CONTENT

文章目录

DAY12:map;面向对象;结构体struct

梦幻世界
2024-05-24 / 0 评论 / 0 点赞 / 89 阅读 / 18488 字 / 正在检测是否收录...
温馨提示:
本文最后更新于 2024-05-24,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

map

基本介绍

map 是 key-value 数据结构,又称为字段或者关联数组。类似其它编程语言的集合, 在编程中是经常使用到

key 可以是什么类型

  1. golang 中的 map,的 key 可以是很多种类型,比如 bool, 数字,string, 指针, channel , 还可以是只包含前面几个类型的 接口, 结构体, 数组

  2. 通常 key 为 int 、string

注意: slice, map 还有 function 不可以,因为这几个没法用 == 来判断

valuetype 可以是什么类型

  1. valuetype 的类型和 key 基本一样,通常为: 数字(整数,浮点数),string,map,struct

基本语法

  1. var a map[string]string

  2. var a map[string]int

  3. var a map[int]string

  4. var a map[string]map[string]string

注意:声明是不会分配内存的,初始化需要 make ,分配内存后才能赋值和使用。

初始化map的三种方式

package main

import "fmt"

func main() {
	// 第一种使用方式,make的作用的用来分配内存空间的
	var map1 map[string]string
	map1 = make(map[string]string, 10)
	map1["key1"] = "value1"
	fmt.Println(map1)

	// 第二种使用方式
	map2 := make(map[string]string)
	map2["key2"] = "value2"
	fmt.Println(map2)

	// 第三种使用方式
	map3 := map[string]string{
		"key3": "value3",
		"key4": "value4",
	}
	fmt.Println(map3)
}

map的遍历

package main

import "fmt"

func main() {
	map1 := map[string]string{
		"key3": "value3",
		"key4": "value4",
	}

	for k, v := range map1 {
		fmt.Println("key=", k, ";value=", v)
	}
}

map的增删改查

  1. map 增加和更新:map["key"] = value //如果 key 还没有,就是增加,如果 key 存在就是修改。

  2. map 删除: delete(map,"key") ,delete 是一个内置函数,如果 key 存在,就删除该 key-value,如果 key 不存在, 不操作,但是也不会报错

  3. 如果我们要删除 map 的所有 key ,没有一个专门的方法一次删除,可以遍历一下 key, 逐个删除或者 map = make(...),make 一个新的,让原来的成为垃圾,被 gc 回收

  4. map 查找:map 中存在, 那么 findRes 就会返回 true,否则返回 false

package main

import "fmt"

func main() {
	map1 := map[string]string{
		"key3": "value3",
		"key4": "value4",
	}

	// map的增加和修改
	map1["key1"] = "v"
	fmt.Println(map1)
	// map的删除
	delete(map1, "key3")
	fmt.Println(map1)
	// map的查询
	val, ok := map1["key4"]
	if ok {
		fmt.Println("key4=", val)
	} else {
		fmt.Println("没有key4")
	}
}

map-slice

基本介绍

切片的数据类型如果是 map,则我们称为 slice of map,map 切片,这样使用则 map 个数就可以动态变化了。

应用案例

使用一个 map 来记录 monster 的信息 name 和 age, 也就是说一个 monster 对应一个 map,并且妖怪的个数可以动态的增加=>map 切片

package main

import "fmt"

func main() {

	var monster []map[string]string
	monster = make([]map[string]string, 2)

	if monster[0] == nil {
		monster[0] = make(map[string]string)
		monster[0]["name"] = "牛魔王"
		monster[0]["age"] = "500"
	}

	if monster[1] == nil {
		monster[1] = make(map[string]string)
		monster[1]["name"] = "牛魔王"
		monster[1]["age"] = "500"
	}
	fmt.Println(monster)

	// 进行动态的添加,需要用到slice的append方法
	map1 := map[string]string{
		"大魔王": "600",
	}
	monster = append(monster, map1)
	fmt.Println(monster)
}

map的排序

基本介绍

  1. golang 中没有一个专门的方法针对 map 的 key 进行排序

  2. golang 中的 map 默认是无序的,注意也不是按照添加的顺序存放的,你每次遍历,得到的输出可能不一样.

  3. golang 中 map 的排序,是先将 key 进行排序,然后根据 key 值遍历输出即可

应用案例

package main

import (
	"fmt"
	"sort"
)

func main() {
	map1 := map[int]int{
		1: 200,
		2: 300,
		4: 600,
	}

	var slice1 []int
	// 先将key放入到切片中
	for k, _ := range map1 {
		slice1 = append(slice1, k)
	}
	// 对切片进行排序
	sort.Ints(slice1)
	// 排序完之后遍历切片 并且取值map
	for _, v := range slice1 {
		fmt.Println("map key=", v, "map value=", map1[v])
	}
}

map的注意事项

  1. map 是引用类型,遵守引用类型传递的机制,在一个函数接收 map,修改后,会直接修改原来的 map

  2. map 的容量达到后,再想 map 增加元素,会自动扩容,并不会发生 panic,也就是说 map 能动态的增长 键值对(key-value)

  3. map 的 value 也经常使用 struct 类型,更适合管理复杂的数据(比前面 value 是一个 map 更好),比如 value 为 Student 结构体

package main

import "fmt"

func mapTest(map1 map[int]int) {
	map1[10] = 200
}

func main() {
	map1 := map[int]int{
		1:  100,
		10: 1,
	}
	fmt.Println(map1)
	mapTest(map1)
	fmt.Println(map1)
}

map应用案例

案例一

我们要存放 3 个学生信息, 每个学生有 name 和 sex 信息

package main

import "fmt"

func main() {
	stu := make(map[string]map[string]string)
	// 这一个make 一定不能少,一定要分配内存空间
	stu["张三"] = make(map[string]string)
	stu["张三"]["name"] = "张三"
	stu["张三"]["sex"] = "男"

	stu["李四"] = make(map[string]string)
	stu["李四"]["name"] = "李四"
	stu["李四"]["sex"] = "男"

	stu["王五"] = make(map[string]string)
	stu["王五"]["name"] = "王五"
	stu["王五"]["sex"] = "女"

	for _, v := range stu {
		for key, value := range v {
			fmt.Println(key, value, ";")
		}
	}
}

案例二

  1. 使用 map[string]map[string]sting 的 map 类型

  2. key: 表示用户名,是唯一的,不可以重复

  3. 如果某个用户名存在,就将其密码修改"888888",如果不存在就增加这个用户信息,(包括昵称nickname 和 密码 pwd)

编写一个函数 modifyUser(users map[string]map[string]sting, name string) 完成上述功能

package main

import "fmt"

func modifyUser(userMap map[string]map[string]string, username string) {
	value, ok := userMap[username]
	if ok {
		value["pwd"] = "888888"
	} else {
		user := map[string]string{
			"nickname": username,
			"pwd":      "1111",
		}
		userMap[username] = user
	}
}

func main() {
	userMap := make(map[string]map[string]string)
	// 这一个make 一定不能少,一定要分配内存空间
	userMap["张三"] = make(map[string]string)
	userMap["张三"]["nickname"] = "张三"
	userMap["张三"]["pwd"] = "123"

	userMap["李四"] = make(map[string]string)
	userMap["李四"]["nickname"] = "李四"
	userMap["李四"]["pwd"] = "23233"

	userMap["王五"] = make(map[string]string)
	userMap["王五"]["nickname"] = "王五"
	userMap["王五"]["pwd"] = "1444444"

	fmt.Println(userMap)

	modifyUser(userMap, "张三")
	fmt.Println(userMap)
	modifyUser(userMap, "麻六")
	fmt.Println(userMap)
}

面向对象

基本介绍

  1. Golang 也支持面向对象编程(OOP),但是和传统的面向对象编程有区别,并不是纯粹的面向对象语言。所以我们说 Golang 支持面向对象编程特性是比较准确的。

  2. Golang 没有类(class),Go 语言的结构体(struct)和其它编程语言的类(class)有同等的地位,你可以理解 Golang 是基于 struct 来实现 OOP 特性的。

  3. Golang 面向对象编程非常简洁,去掉了传统 OOP 语言的继承、方法重载、构造函数和析构函数、隐藏的 this 指针等等

  4. Golang 仍然有面向对象编程的继承,封装和多态的特性,只是实现的方式和其它 OOP 语言不一样,比如继承 :Golang 没有 extends 关键字,继承是通过匿名字段来实现。

  5. Golang 面向对象(OOP)很优雅,OOP 本身就是语言类型系统(type system)的一部分,通过接口(interface)关联,耦合性低,也非常灵活。后面同学们会充分体会到这个特点。也就是说在Golang 中面向接口编程是非常重要的特性。

快速入门-面向对象的方式(struct)解决养猫问题

package main

import "fmt"

type Cat struct {
	Name  string
	Age   int
	Color string
	Hobby string
}

func main() {
	var cat Cat
	cat.Name = "小猫名字"
	cat.Age = 1
	cat.Color = "黄色"
	cat.Hobby = "喜欢吃"
	fmt.Println(cat)
}

结构体和结构体变量(实例)的区别和联系

通过上面的案例和讲解我们可以看出:

  1. 结构体是自定义的数据类型,代表一类事物.

  2. 结构体变量(实例)是具体的,实际的,代表一个具体变量

结构体变量(实例)在内存的布局

内存分析

结构体struct

基本语法

type 结构体名称 struct {

field1 type

field2 type

}

字段 / 属性的的基本介绍

  1. 从概念或叫法上看: 结构体字段 = 属性 = field

  2. 字段是结构体的一个组成部分,一般是基本数据类型、数组,也可是引用类型。比如我们前面定义猫结构体 的 Name string 就是属性

字段 / 属性的的注意事项

  1. 字段声明语法同变量,示例:字段名 字段类型

  2. 字段的类型可以为:基本类型、数组或引用类型

  3. 在创建一个结构体变量后,如果没有给字段赋值,都对应一个零值(默认值),规则同前面讲的一样:

  4. 布尔类型是 false ,数值是 0 ,字符串是 ""。

  5. 数组类型的默认值和它的元素类型相关,比如 score [3]int 则为[0, 0, 0]

  6. 指针,slice,和 map 的零值都是 nil ,即还没有分配空间。

  7. 不同结构体变量的字段是独立,互不影响,一个结构体变量字段的更改,不影响另外一个, 结构体是值类型。

package main

import "fmt"

type Monster struct {
	Name string
	Age  int
}

func main() {
	// 结构体是值类型
	var monster1 Monster
	monster1.Name = "牛魔王"
	monster1.Age = 500
	monster2 := monster1
	monster2.Name = "青牛精"
	fmt.Println(monster1)
	fmt.Println(monster2)
}

内存分析

创建结构体变量和访问结构体字段

  1. 方式 1-直接声明,案例演示: var person Person

  2. 方式 2-{},案例演示: var person Person = Person{}

  3. 方式 3-&,案例: var person *Person = new (Person)

  4. 方式 4-{},案例: var person *Person = &Person{}

说明:

  1. 第 3 种和第 4 种方式返回的是 结构体指针。

  2. 结构体指针访问字段的标准方式应该是:(*结构体指针).字段名 ,比如 (*person).Name = "tom"

  3. 但 go 做了一个简化,也支持 结构体指针.字段名, 比如 person.Name = "tom"。更加符合程序员使用的习惯,go 编译器底层 对 person.Name 做了转化 (*person).Name。

package main

import "fmt"

type Person struct {
	Name string
}

func main() {
	// 方式一
	var person1 Person
	person1.Name = "person1"
	fmt.Println(person1)
	// 方式二
	person2 := Person{}
	person2.Name = "person2"
	fmt.Println(person2)
	// 方式三
	person3 := new(Person)
	(*person3).Name = "person3"
	fmt.Println(*person3)
	// 方式四
	person4 := &Person{}
	person4.Name = "person4" //  person4.Name 这种写法等价于 (*person4).Name, 这个是go底层做了优化,回自动进行取值的运算
	fmt.Println(*person4)
}

struct 类型的内存分配机制

思考1:

会输出什么结果

输出的结果是: p2.Name = tom p1.Name = 小明

结构体在内存中示意图

内存分析图

思考2:

下面代码的输出结果

package main

import "fmt"

type Person struct {
	Name string
	Age  int
}

func main() {

	var p1 Person
	p1.Age = 10
	p1.Name = "小明"
	var p2 *Person = &p1

	fmt.Println((*p2).Age)
	fmt.Println(p2.Age)

	p2.Name = "tom"

	fmt.Printf("p2.name=%v p1.name=%v \n", p2.Name, p1.Name)
	fmt.Printf("p2.name=%v p1.name=%v \n", (*p2).Name, p1.Name)

	fmt.Printf("p1的地址%p \n", &p1)
	fmt.Printf("p2的地址%p p2的值%p", &p2, p2)
}

内存分析:

内存分析图

思考三:

思考3

struct使用注意事项和细节

  1. 结构体的所有字段在内存中是连续的

代码示例

代码示例

对应的分析图:

内存分析

2. 结构体是用户单独定义的类型,和其它类型进行转换时需要有完全相同的字段(名字、个数和类型)

类型转换

3. 结构体进行 type 重新定义(相当于取别名),Golang 认为是新的数据类型,但是相互间可以强转

说明分析

4. struct 的每个字段上,可以写上一个 tag, 该 tag 可以通过反射机制获取,常见的使用场景就是序列化和反序列化

package main

import (
	"encoding/json"
	"fmt"
)

type Monster struct {
	Name string `json:"name"`
	Age  int    `json:"age"`
}

func main() {
	monster := Monster{"牛魔王", 500}

	marshal, err := json.Marshal(monster)
	if err != nil {
		fmt.Println("错误")
	} else {
		fmt.Println(string(marshal))
	}
}

0

评论区