map
map是key-value数据结构,又称为字段或者关联数组,雷士其他编程语言的集合,在编程中经常使用到。
语法
var map变量名 map[keytype]valuetype
golang中的map,的key可以是很多种类型,比如bool,数字,string,指针,channel,还可以是只包含前面几个类型的接口、结构体、数组。通常为int,string
valuetype的类型和key基本一样,通常为数字(整数、浮点数),string,map,struct
map声明
声明是不会分配内存的,初始化需要make,分配内存后才能赋值和使用
package main
import "fmt"
func main() {
// map的声明,注意事项
var a map[string]string
// 空map不能直接赋值
// 在使用map前,需要先make,make的作用就是给map分配数据空间
a = make(map[string]string, 10)
a["num1"] = "宋江"
a["num2"] = "吴用"
fmt.Println(a)
// key不能重复
a["num1"] = "武松"
fmt.Println(a)
// value可以从夫
a["num3"] = "吴用"
fmt.Println(a)
}
map在使用前一定要make
map的key是不能重复,如果重复了,则以最后这个key-value为准
map的value是可以相同的
map的key-value是无序的
make内置函数
使用方式
// 方式一
var a map[string]string
// 空map不能直接赋值
// 在使用map前,需要先make,make的作用就是给map分配数据空间
a = make(map[string]string, 10)
a["num1"] = "宋江"
a["num2"] = "吴用"
fmt.Println(a)
// 方式2
cities := make(map[string]string)
cities["no1"] = "北京"
cities["no2"] = "天津"
cities["no3"] = "上海"
fmt.Println(cities)
// 第三种方式
heros := map[string]string{
"hero1": "宋江",
"hero2": "卢俊义",
}
fmt.Println("heroes=", heros)
案例
package main
import "fmt"
func main() {
// // map的声明,注意事项
// var a map[string]string
// // 空map不能直接赋值
// // 在使用map前,需要先make,make的作用就是给map分配数据空间
// a = make(map[string]string, 10)
// a["num1"] = "宋江"
// a["num2"] = "吴用"
// fmt.Println(a)
// // key不能重复
// a["num1"] = "武松"
// fmt.Println(a)
// // value可以从夫
// a["num3"] = "吴用"
// fmt.Println(a)
// 方式一
var a map[string]string
// 空map不能直接赋值
// 在使用map前,需要先make,make的作用就是给map分配数据空间
a = make(map[string]string, 10)
a["num1"] = "宋江"
a["num2"] = "吴用"
fmt.Println(a)
// 方式2
cities := make(map[string]string)
cities["no1"] = "北京"
cities["no2"] = "天津"
cities["no3"] = "上海"
fmt.Println(cities)
// 第三种方式
heros := map[string]string{
"hero1": "宋江",
"hero2": "卢俊义",
}
fmt.Println("heroes=", heros)
studentMap := make(map[string]map[string]string)
studentMap["stu01"] = make(map[string]string, 3)
studentMap["stu01"]["name"] = "tom"
studentMap["stu01"]["sex"] = "男"
studentMap["stu01"]["address"] = "北京长安街~"
studentMap["stu02"] = make(map[string]string, 3)
studentMap["stu02"]["name"] = "mary"
studentMap["stu02"]["sex"] = "女"
studentMap["stu02"]["address"] = "上海黄浦街~"
fmt.Println(studentMap)
fmt.Println(studentMap["stu01"])
fmt.Println(studentMap["stu01"]["address"])
}
map增删改查
因为key已经存在,重新赋值相当于修改
cities := make(map[string]string)
cities["no1"] = "北京"
cities["no2"] = "天津"
cities["no3"] = "上海"
fmt.Println(cities)
// 因为key已经存在,重新赋值相当于修改
cities["no3"] = "深圳"
fmt.Println(cities)
内置函数delet(),delete(map,key),当delet指定的key不存在时,删除不会操作,也不会报错
// 因为key已经存在,重新赋值相当于修改
cities["no3"] = "深圳"
fmt.Println(cities)
如果我们要删除map的所有key,没有一个专门的方法一次删除,可以遍历一下key,逐个删除
或者map = make(...),make一个新的,让原来的成为垃圾,被gc回收
map查找
val, ok := cities["no2"]
if ok {
fmt.Printf("有 no2 key 值为%v\n", val)
} else {
fmt.Printf("没有no2 key\n")
}
map遍历
for k, v := range cities {
fmt.Printf("k=%v,v=%v\n", k, v)
}
studentMap := make(map[string]map[string]string)
studentMap["stu01"] = make(map[string]string, 3)
studentMap["stu01"]["name"] = "tom"
studentMap["stu01"]["sex"] = "男"
studentMap["stu01"]["address"] = "北京长安街~"
studentMap["stu02"] = make(map[string]string, 3)
studentMap["stu02"]["name"] = "mary"
studentMap["stu02"]["sex"] = "女"
studentMap["stu02"]["address"] = "上海黄浦街~"
for k1, v1 := range studentMap {
fmt.Println(k1 + ":")
for k2, v2 := range v1 {
fmt.Printf("\t %v = %v v2=%v\n", k1, k2, v2)
}
fmt.Println()
}
map的长度
len(map)
map切片
切片的数据类型如果是map,我们称slice of map,map切片,这样使用则map的个数可以动态变化
package main
import "fmt"
func main() {
var monsters []map[string]string
monsters = make([]map[string]string, 2)
if monsters[0] == nil {
monsters[0] = make(map[string]string, 2)
monsters[0]["name"] = "牛魔王"
monsters[0]["age"] = "500"
}
if monsters[1] == nil {
monsters[1] = make(map[string]string, 2)
monsters[1]["name"] = "玉兔精"
monsters[1]["age"] = "400"
}
fmt.Println(monsters)
//使用切片的append函数,可以动态的增加monstr
newMoster := map[string]string{
"name": "天魔",
"age": "200",
}
monsters = append(monsters, newMoster)
fmt.Println(monsters)
}
map排序
golang中没有一个专门的方法针对map的key进行排序
golang中的map默认是升序的,注意也不是按照顺序存放的,每次遍历,得到的输出可能不一样
golang中的map的排序,事先将key进行排序,然后根据key值遍历输出
使用细节
map是引用类型,遵守引用类型的传递机制,在一个函数接收map,修改后,会直接修改原来的map
package main
import "fmt"
func modify(map1 map[int]int) {
map1[10] = 900
}
func main() {
map1 := make(map[int]int)
map1[1] = 90
map1[2] = 88
map1[10] = 12
map1[12] = 3
modify(map1)
// 说明map是引用类型
fmt.Println(map1)
}
map的容量达到后,在想map增加元素,会自动扩容,并不会发生panic,也就是说map能动态的增长键值对(key-value)
map的value也经常使用struct类型,更适合管理复杂的数据,比如value可以为Student结构体
package main
import (
"fmt"
)
func modify(map1 map[int]int) {
map1[10] = 900
}
type stu struct {
Name string
Age int
Address string
}
func main() {
map1 := make(map[int]int)
map1[1] = 90
map1[2] = 88
map1[10] = 12
map1[12] = 3
modify(map1)
// 说明map是引用类型
fmt.Println(map1)
students := make(map[string]stu, 10)
stu1 := stu{"Tom", 18, "北京"}
stu2 := stu{"Marry", 18, "上海"}
students["no1"] = stu1
students["no2"] = stu2
fmt.Println(students)
}
面向"对象"编程
Golang支持面向对象编程(OOP),但是和传统的面向对象变成有区别,并不是纯粹的面向对象语言,
Golang没有类(class),Go语言的结构体(struct)和其他编程语言的类(class)有同等的地位,可以理解为Golang是基于struct来实现OOP特性的
Golang面向对象编程非常简洁,去掉了传统的OOP语言的继承、方法重载、构造函数和析构函数、隐藏的this指针等等
Golang仍然有面向对象编程的继承,封装和多态的特性,只是实现的方式和其他OOP语言不一样,比如继承:Golang没有extends关键字,继承是通过匿名函数名字段来实现的
Golang面向对象(OOP)很优雅,OOP本身就是语言类型系统(type system)的一部分,通过过接口(interface)关联,耦合性低,也非常灵活,在Golang中面向接口编程是非常重要的特性
结构体
将一类事物的特征提取出来,形成一个新的数据类型,就是一个结构体
通过这个个结构体,我们可以创建多个变量
案例
package main
import "fmt"
type Cat struct {
Name string
Age int
Color string
}
func main() {
var cat1 Cat
cat1.Name = "tom"
cat1.Age = 12
cat1.Color = "blue"
fmt.Println(cat1)
}
结构体与结构体变量(实例)的区别和联系
结构体是自定义的数据类型,代表一类事物
结构体变量(实例)是具体的,实际的,代表一个具体变量
字段/属性
从概念或叫法上看,结构体的字段=属性=field
字段是结构体的一个组成部分,一般基本数据类型、数组,也可是引用类型
注意事项
字段声明语法同变量,字段名 字段类型
字段的类型可以为:基本类型、数组或引用类型
在创建一个结构体变量后,如果没有给字段赋值,都对应一个零值
package main
import "fmt"
type Person struct {
Name string
Age int
Scores [5]float64
ptr *int
slice []int
map1 map[string]string
}
func main() {
var p1 Person
fmt.Println(p1)
if p1.ptr == nil {
fmt.Println("ok1")
}
if p1.slice == nil {
fmt.Println("ok2")
}
if p1.map1 == nil {
fmt.Println("ok3")
}
p1.slice = make([]int, 1)
p1.slice[0] = 100
fmt.Println(p1)
}
不同结构体变量的字段是独立的,互不影响,一个结构体变量字段的更改,不影响另外一个。
创建结构体变量和访问结构体字段
方式一
var p1 Person
fmt.Print(p1)
方式二
var p1 Person
fmt.Print(p1)
方式三
//方式三
var p3 *Person = new(Person)
(*p3).Name = "smith"
(*p3).Age = 30
// go的设计者,为了程序员使用方柏霓,底层会对p3.Name进行处理
p3.Name = "li"
fmt.Println(p3)
方法四
var p4 *Person = &Person{}
fmt.Println(p4)
结构体的注意事项
结构体的所有字段在内存中是连续的
package main
import "fmt"
type Point struct {
x int
y int
}
type Rect struct {
leftUp, rightDown Point
}
type Rect2 struct {
leftUp, rightDown *Point
}
func main() {
r1 := Rect{Point{1, 2}, Point{3, 4}}
fmt.Printf("r1.leftUp.x 地址为%p\n", &r1.leftUp.x)
fmt.Printf("r1.leftUp.y 地址为%p\n", &r1.leftUp.y)
fmt.Printf("r1.rightDown.x 地址为%p\n", &r1.rightDown.x)
fmt.Printf("r1.rightDown.y 地址为%p\n", &r1.rightDown.y)
// r2有两个 *Point雷诺高兴,这两个*Point类型本身地址也是连续的
// 但他们指向的地址不一定是连续的
r2 := Rect2{&Point{10, 20}, &Point{30, 40}}
fmt.Printf("r2.leftUp 地址为%p\n", &r2.leftUp)
fmt.Printf("r2.rightDown 地址为%p\n", &r2.rightDown)
fmt.Printf("r2.leftUp.x 地址为%p\n", &r2.leftUp.x)
fmt.Printf("r2.leftUp.y 地址为%p\n", &r2.leftUp.y)
fmt.Printf("r2.rightDown.x 地址为%p\n", &r2.rightDown.x)
fmt.Printf("r2.rightDown.y 地址为%p\n", &r2.rightDown.y)
}
结构体是用户单独定义的类型,和其他类型进行转换时需要有完全相同的字段(名字、个数和类型)
package main
import "fmt"
type A struct {
Num int
}
type B struct {
Num int
}
func main() {
var a A
var b B
a = A(b)
fmt.Println(a,b)
}
结构体进行type重新定义(想当与取别名),Golang认为是新的数据类型,但是相互间可以强转
package main
import "fmt"
type Student struct {
Name string
Age int
}
type Stu Student
type integer int
func main() {
var stu1 Student
var stu2 Stu
stu2 = Stu(stu1)
fmt.Println(stu1, stu2)
var i integer = 10
var j int = 20
j = int(i)
fmt.Println(i, j)
}
struct的每个字段山,可以写一个tag,该tag可以通过反射机制获取,常见的使用场景就是序列号和反序列化
将struct变量进行json处理
package main
import (
"encoding/json"
"fmt"
)
type Monster struct {
Name string `json:"name"`
Age int `json:"age"`
Skill string `json:"skill"`
}
func main() {
monster := Monster{"牛魔王", 500, "芭蕉扇"}
// json.Marshal函数中使用了反射
jsonMonster, err := json.Marshal(monster)
if err != nil {
fmt.Println("json 处理错误", err)
}
fmt.Println("jsonMonster", string(jsonMonster))
}
方法
在某些情况下,我们需要声明(定义)方法。
Golang中的方法是作用在指定数据类型上的,因此自定义类型,都可以有方法,而不是struct
type A struct{
Num int
}
func (a A)test(){
fmt.Println(a.Num)
}
func (a A)test(){}表示A结构体有一方法,方法名test
(a A)体现test方法是和A类型绑定的
package main
import "fmt"
type Person struct {
Name string
}
func (p Person) test() {
fmt.Println("test()", p.Name)
}
func main() {
var p Person
p.Name = "tom"
p.test()
}
快速入门
func main() {
var p Person
p.Name = "tom"
p.speak()
p.jisuan()
p.jisuan2(100)
res := p.getsum(10, 20)
fmt.Println("res=", res)
}
给Person结构体添加speak方法,输出XX是一个b( ̄▽ ̄)d
func (p Person) speak() {
fmt.Println(p.Name, "是一个goodman")
}
给Person结构体添加jisaun方法,可以计算1+...+1000的结果
func (p Person) jisuan() {
res := 0
for i := 1; i < 1000; i++ {
res += i
}
fmt.Println(p.Name, "计算的结果=", res)
}
给Person结构体jisuan2方法,该方法可以接受一个数n,计算1+...+n的结果
func (p Person) jisuan() {
res := 0
for i := 1; i < 1000; i++ {
res += i
}
fmt.Println(p.Name, "计算的结果=", res)
}
func (p Person) jisuan2(n int) {
res := 0
for i := 1; i < n; i++ {
res += i
}
fmt.Println(p.Name, "计算的结果=", res)
}
给Person结构体添加getSum方法,可以计算两个数的和,并返回结果
func (p Person) getsum(n1 int, n2 int) int {
return n1 + n2
}
方法的调用和传参机制原理
方法的调用和传参机制和函数基本一样,不一样的地方是方法调用时,会将调用方法的变量,当作实参传递给方法
在通过一个变量去调用方法时,其调用机制和函数一样
不一样的地方时,变量调用方法时,该变量本身也会作为一个参数传递到方法(如果变量是值类型,则进行值拷贝,如果变量是引用类型,则进行地址拷贝)
案例
package main
import "fmt"
type Cricle struct {
radius float64
}
func (c Cricle) area() float64 {
return 3.14 * c.radius * c.radius
}
func main() {
var c Cricle
c.radius = 4.0
res := c.area()
fmt.Println("面积是=",res)
}
方法的声明(定义)
func (recrvier type) methodName(参数列表)(返回值列表){
方法体
return 返回值
}
参数列表:表示方法的输入
recever type:表示这个方法和type这个类型进行绑定,或者说该方法作用于type类型
receiver type:type可以是结构体,也可以是其他自定义类型
receiver:就是type类型的一个变量,比如:Person结构体的一个变量
参数列表:表示方法的输入
返回值列表:表示返回的值,可以有多个
方法的主体,表示为了实现某一功能代码块
return 语句不是必须的
方法的注意事项和细节讨论
结构体类型是值类型,在方法调用中,遵守值类型的传递机制,是值拷贝传递方式
如程序员希望在方法中,修改结构体变量的值,可以通过结构体指针的方式来处理
Golang中的方法是作用在指定的数据类型上的,和数据类型绑定,因此自定义类型,都可以有方法,而不仅仅是struct,比如int,float32等都可以有方法
方法的访问范围控制的规则,和函数一样,方法名首字母小写,只能在本包访问,方法首字母大写,可以在本包和其他包访问
如果一个变量实现了String()这个方法,那么fmt.Println默认会调用这个变量的String()进行输出
方法和函数的区别
调用方式不一样
函数调用方式: 函数名(实参列表)
方法的调用方式: 变量.方法名(实参列表)
对于普通函数,接收者为值类型是,不能将指针类型的数据直接传递,反之亦然
对于方法(如struct的方法),接收者为值类型时,可以直接使用指针类型的变量调用方法,反过来同样可以。
package main
import "fmt"
type Person struct {
Name string
}
func test01(p Person) {
fmt.Println(p.Name)
}
func test02(p *Person) {
fmt.Println(p.Name)
}
func (p Person) test03() {
fmt.Println("test03() =", p.Name)
}
func main() {
p := Person{"tom"}
test01(p)
test02(&p)
p.test03()
// 值拷贝,从形式上看是传入地址,但本质上仍是值拷贝
(&p).test03()
}