map
基本介绍
map 是 key-value 数据结构,又称为字段或者关联数组。类似其它编程语言的集合, 在编程中是经常使用到
key 可以是什么类型
golang 中的 map,的 key 可以是很多种类型,比如 bool, 数字,string, 指针, channel , 还可以是只包含前面几个类型的 接口, 结构体, 数组
通常 key 为 int 、string
注意: slice, map 还有 function 不可以,因为这几个没法用 == 来判断
valuetype 可以是什么类型
valuetype 的类型和 key 基本一样,通常为: 数字(整数,浮点数),string,map,struct
基本语法
var a map[string]string
var a map[string]int
var a map[int]string
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的增删改查
map 增加和更新:map["key"] = value //如果 key 还没有,就是增加,如果 key 存在就是修改。
map 删除: delete(map,"key") ,delete 是一个内置函数,如果 key 存在,就删除该 key-value,如果 key 不存在, 不操作,但是也不会报错
如果我们要删除 map 的所有 key ,没有一个专门的方法一次删除,可以遍历一下 key, 逐个删除或者 map = make(...),make 一个新的,让原来的成为垃圾,被 gc 回收
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的排序
基本介绍
golang 中没有一个专门的方法针对 map 的 key 进行排序
golang 中的 map 默认是无序的,注意也不是按照添加的顺序存放的,你每次遍历,得到的输出可能不一样.
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的注意事项
map 是引用类型,遵守引用类型传递的机制,在一个函数接收 map,修改后,会直接修改原来的 map
map 的容量达到后,再想 map 增加元素,会自动扩容,并不会发生 panic,也就是说 map 能动态的增长 键值对(key-value)
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, ";")
}
}
}
案例二
使用 map[string]map[string]sting 的 map 类型
key: 表示用户名,是唯一的,不可以重复
如果某个用户名存在,就将其密码修改"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)
}
面向对象
基本介绍
Golang 也支持面向对象编程(OOP),但是和传统的面向对象编程有区别,并不是纯粹的面向对象语言。所以我们说 Golang 支持面向对象编程特性是比较准确的。
Golang 没有类(class),Go 语言的结构体(struct)和其它编程语言的类(class)有同等的地位,你可以理解 Golang 是基于 struct 来实现 OOP 特性的。
Golang 面向对象编程非常简洁,去掉了传统 OOP 语言的继承、方法重载、构造函数和析构函数、隐藏的 this 指针等等
Golang 仍然有面向对象编程的继承,封装和多态的特性,只是实现的方式和其它 OOP 语言不一样,比如继承 :Golang 没有 extends 关键字,继承是通过匿名字段来实现。
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)
}
结构体和结构体变量(实例)的区别和联系
通过上面的案例和讲解我们可以看出:
结构体是自定义的数据类型,代表一类事物.
结构体变量(实例)是具体的,实际的,代表一个具体变量
结构体变量(实例)在内存的布局
内存分析
结构体struct
基本语法
type 结构体名称 struct {
field1 type
field2 type
}
字段 / 属性的的基本介绍
从概念或叫法上看: 结构体字段 = 属性 = field
字段是结构体的一个组成部分,一般是基本数据类型、数组,也可是引用类型。比如我们前面定义猫结构体 的 Name string 就是属性
字段 / 属性的的注意事项
字段声明语法同变量,示例:字段名 字段类型
字段的类型可以为:基本类型、数组或引用类型
在创建一个结构体变量后,如果没有给字段赋值,都对应一个零值(默认值),规则同前面讲的一样:
布尔类型是 false ,数值是 0 ,字符串是 ""。
数组类型的默认值和它的元素类型相关,比如 score [3]int 则为[0, 0, 0]
指针,slice,和 map 的零值都是 nil ,即还没有分配空间。
不同结构体变量的字段是独立,互不影响,一个结构体变量字段的更改,不影响另外一个, 结构体是值类型。
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-直接声明,案例演示: var person Person
方式 2-{},案例演示: var person Person = Person{}
方式 3-&,案例: var person *Person = new (Person)
方式 4-{},案例: var person *Person = &Person{}
说明:
第 3 种和第 4 种方式返回的是 结构体指针。
结构体指针访问字段的标准方式应该是:(*结构体指针).字段名 ,比如 (*person).Name = "tom"
但 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使用注意事项和细节
结构体的所有字段在内存中是连续的
代码示例
代码示例
对应的分析图:
内存分析
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))
}
}
评论区