Go学习(三):Map、结构体

本文深入解析Go语言中map和结构体的使用,以及它们在JSON序列化中的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

1、map

1.1 map定义

1.2 map基本使用 

1.3  按照指定顺序遍历map

1.4 元素为map类型的切片

 1.5 值为切片类型的map

 2、结构体

2.1 类型别名和自定义类型

2.2 结构体

2.3 面试题:range值拷贝

2.3 构造函数

2.4 方法和接收者

 2.5 指针类型的接收者

2.6 结构体的匿名字段 

2.7 嵌套结构体

2.8 结构体的“继承” 

2.9 结构体与JSON序列化 json.Marshal

2.10 结构体标签(Tag) 

 2.11 删除map类型的结构体

1、map

1.1 map定义

 make(map[KeyType]ValueType, [cap])
 KeyType:表示键的类型。
 ValueType:表示键对应的值的类型。

1.2 map基本使用 

package main

import "fmt"

func main() {
	//1、初始化
	scoreMap := make(map[string]int, 8)
	scoreMap["张三"] = 90
	fmt.Println(scoreMap)
	fmt.Println(scoreMap["张三"])
	fmt.Printf("type of a:%T\n", scoreMap)
	//在声明的时候填充元素
	userInfo := map[string]string{
		"username": "pprof.cn",
		"password": "123456",
	}
	fmt.Println(userInfo)

	//2、判断某个键是否存在
	// 如果key存在ok为true,v为对应的值;不存在ok为false,v为值类型的零值
	v, ok := scoreMap["张三"]
	if ok {
		fmt.Println(v)
	} else {
		fmt.Println("查无此人")
	}
	v1, ok1 := scoreMap["小明"]
	if ok1 {
		fmt.Println(v1)
	} else {
		fmt.Println("查无此人")
	}
	//输出
	// 90
	// 查无此人

	//3、map的遍历
	scoreMap["小明"] = 100
	scoreMap["王五"] = 60
	for k, v := range scoreMap {
		fmt.Println(k, v)
	}
	// 遍历map时的元素顺序与添加键值对的顺序无关??这不是有关吗
	// 张三 90
	// 小明 100
	// 王五 60

	//4、删除键值对
	delete(scoreMap, "小明")//将小明:100从map中删除
	for k, v := range scoreMap {
		fmt.Println(k, v)
	}


}

1.3  按照指定顺序遍历map

package main

import (
	"fmt" 
	"math/rand"
	"time"
	"sort"
)

func main() {
    //需要借助string的sort
    rand.Seed(time.Now().UnixNano()) //初始化随机数种子

    var scoreMap = make(map[string]int, 200)

    for i := 0; i < 100; i++ {
        key := fmt.Sprintf("stu%02d", i) //生成stu开头的字符串
        value := rand.Intn(100)          //生成0~99的随机整数
        scoreMap[key] = value
    }
    //取出map中的所有key存入切片keys
    var keys = make([]string, 0, 200)
    for key := range scoreMap {
        keys = append(keys, key)
    }
    //对切片进行排序
    sort.Strings(keys)
    //按照排序后的key遍历map
    for _, key := range keys {
        fmt.Println(key, scoreMap[key])
    }
}

1.4 元素为map类型的切片

func main() {
    var mapSlice = make([]map[string]string, 3)
    for index, value := range mapSlice {
        fmt.Printf("index:%d value:%v\n", index, value)
    }
    fmt.Println("after init")
    // 对切片中的map元素进行初始化
    mapSlice[0] = make(map[string]string, 10)
    mapSlice[0]["name"] = "王五"
    mapSlice[0]["password"] = "123456"
    mapSlice[0]["address"] = "红旗大街"
    for index, value := range mapSlice {
        fmt.Printf("index:%d value:%v\n", index, value)
    }
}
输出结果:
index:0 value:map[]
index:1 value:map[]
index:2 value:map[]
after init
index:0 value:map[address:红旗大街 name:王五 password:123456]
index:1 value:map[]
index:2 value:map[]

 1.5 值为切片类型的map

func main() {
    var sliceMap = make(map[string][]string, 3)
    fmt.Println(sliceMap)
    fmt.Println("after init")
    key := "中国"
    value, ok := sliceMap[key]
    if !ok {
        value = make([]string, 0, 2)
    }
    value = append(value, "北京", "上海")
    sliceMap[key] = value
    fmt.Println(sliceMap)
}
输出结果
map[]
after init
map[中国:[北京 上海]]

 2、结构体

2.1 类型别名和自定义类型

自定义类型

 type MyInt int

类型别名

    type TypeAlias = Type
    type byte = uint8
    type rune = int32

区别: 

func main() {
    var a NewInt
    var b MyInt

    fmt.Printf("type of a:%T\n", a) //type of a:main.NewInt
    fmt.Printf("type of b:%T\n", b) //type of b:int
}

2.2 结构体

package main

import (
	"fmt"
)

// 1、结构体定义
type person struct {
	name string
	city string
	age  int8
}
type student struct {
	name string
	age  int
}


func main() {
	//2、实例化+初始化
	var p1 person
	p1.name = "pprof.cn"
	p1.city = "北京"
	p1.age = 18
	fmt.Printf("p1=%v\n", p1)  //p1={pprof.cn 北京 18}
	fmt.Printf("p1=%#v\n", p1) //p1=main.person{name:"pprof.cn", city:"北京", age:18}
	//3、匿名结构体
	var user struct {
		Name string
		Age  int
	}
	user.Name = "pprof.cn"
	user.Age = 18
	fmt.Printf("%#v\n", user)
	//4、指针类型结构体
	var p2 = new(person)
	p2.name = "测试"
	p2.age = 18
	p2.city = "北京"
	fmt.Printf("p2=%#v\n", p2) //p2=&main.person{name:"测试", city:"北京", age:18}
	//取结构体的地址实例化
	p3 := &person{}
	fmt.Printf("p3=%#v\n", p3) //p3=&main.person{name:"", city:"", age:0}

	//5、使用键值对初始化
	p5 := person{
		name: "pprof.cn",
		city: "北京",
		age:  18,
	}
	fmt.Printf("p5=%#v\n", p5) //p5=main.person{name:"pprof.cn", city:"北京", age:18}
	//初始化的时候不写键,直接写值
	// 1.必须初始化结构体的所有字段。
	// 2.初始值的填充顺序必须与字段在结构体中的声明顺序一致。
	// 3.该方式不能和键值初始化方式混用。

	//6、结构体内存布局
	type test struct {
		a int8
		b int8
		c int8
		d int8
	}
	n := test{
		1, 2, 3, 4,
	}
	fmt.Printf("n.a %p\n", &n.a)
	fmt.Printf("n.b %p\n", &n.b)
	fmt.Printf("n.c %p\n", &n.c)
	fmt.Printf("n.d %p\n", &n.d)
	//输出
	// n.a 0xc00000a160
	// n.b 0xc00000a161
	// n.c 0xc00000a162
	// n.d 0xc00000a163

}

2.3 面试题:range值拷贝

type student struct {
    name string
    age  int
}

func main() {
    m := make(map[string]*student)
	stus := []student{
		{name: "pprof.cn", age: 18},
		{name: "测试", age: 23},
		{name: "博客", age: 28},
	}
	fmt.Printf("%p\n", &stus)
	for _, stu := range stus {
		// fmt.Println(&stu)
		fmt.Printf("%p\n", &stu)
		m[stu.name] = &stu
	}
	fmt.Println(m)
	for k, v := range m {
		fmt.Println(v)
		fmt.Println(k, "=>", v.name)
	}
}

 是不期望的结果,原因:

在第一个for循环中,m的中插入的键值对的值都是&stu。由于range是值拷贝,也就是说最后stu中剩下的只有数组遍历的最后一个拷贝“博客”。m的每个值都是指向stu的内存地址。所以导致了这种结果。

range值拷贝:每次stu的值在变,但是&stu的地址是不变的

结果:
map[pprof.cn:0xc000008078 博客:0xc000008078 测试:0xc000008078]
&{博客 28}
pprof.cn => 博客
&{博客 28}
测试 => 博客
&{博客 28}
博客 => 博客

修改后:

func main() {
	m := make(map[string]*student)
	stus := []student{
		{name: "pprof.cn", age: 18},
		{name: "测试", age: 23},
		{name: "博客", age: 28},
	}
	for i, stu := range stus {
		m[stu.name] = &stus[i]
	}
	for k, v := range m {
		fmt.Println(v)
		fmt.Println(k, "=>", v.name)
	}
}

2.3 构造函数

Go语言的结构体没有构造函数,需要自己实现

func newPerson(name, city string, age int8) *person {
    return &person{
        name: name,
        city: city,
        age:  age,
    }
}
//调用
p9 := newPerson("pprof.cn", "测试", 90)

2.4 方法和接收者

Go语言中的方法(Method)是一种作用于特定类型变量的函数。这种特定类型变量叫做接收者(Receiver)。接收者的概念就类似于其他语言中的this或者 self。

方法与函数的区别是,函数不属于任何类型,方法属于特定的类型。

接收者的类型可以是任何类型,不仅仅是结构体,任何类型都可以拥有方法。

 func (接收者变量 接收者类型) 方法名(参数列表) (返回参数) {
        函数体
    }
1.接收者变量:接收者中的参数变量名在命名时,官方建议使用接收者类型名的第一个小写字母,而不是self、this之类的命名。例如,Person类型的接收者变量应该命名为 p,Connector类型的接收者变量应该命名为c等。
2.接收者类型:接收者类型和参数类似,可以是指针类型和非指针类型。
3.方法名、参数列表、返回参数:具体格式与函数定义相同。
type Person struct {
    name string
    age  int8
}

//NewPerson 构造函数
func NewPerson(name string, age int8) *Person {
    return &Person{
        name: name,
        age:  age,
    }
}

//MyInt 将int定义为自定义MyInt类型
type MyInt int

//SayHello 为MyInt添加一个SayHello的方法
func (m MyInt) SayHello() {
    fmt.Println("Hello, 我是一个int。")
}

//Dream Person做梦的方法,是属于person的
func (p Person) Dream() {
    fmt.Printf("%s的梦想是学好Go语言!\n", p.name)
}

func main() {
    p1 := NewPerson("测试", 25)
    p1.Dream()
    var m1 MyInt
    m1.SayHello() //Hello, 我是一个int。
    m1 = 100
    fmt.Printf("%#v  %T\n", m1, m1) //100  main.MyInt
}

 2.5 指针类型的接收者

指针类型的接收者由一个结构体的指针组成,由于指针的特性,调用方法时修改接收者指针的任意成员变量,在方法结束后,修改都是有效的。这种方式就十分接近于其他语言中面向对象中的this或者self。 例如我们为Person添加一个SetAge方法,来修改实例变量的年龄。

// 使用指针接收者
    func (p *Person) SetAge(newAge int8) {
        p.age = newAge
    }

2.6 结构体的匿名字段 

成员字段在声明时没有字段名而只有类型,这种没有名字的字段就称为匿名字段

//Person 结构体Person类型
type Person struct {
    string
    int
}

func main() {
    p1 := Person{
        "pprof.cn",
        18,
    }
    fmt.Printf("%#v\n", p1)        //main.Person{string:"pprof.cn", int:18}
    fmt.Println(p1.string, p1.int) //pprof.cn 18
}

2.7 嵌套结构体

//Address 地址结构体
type Address struct {
    Province string
    City     string
}

//User 用户结构体
type User struct {
    Name    string
    Gender  string
    Address Address
}

func main() {
    user1 := User{
        Name:   "pprof",
        Gender: "女",
        Address: Address{
            Province: "黑龙江",
            City:     "哈尔滨",
        },
    }
    fmt.Printf("user1=%#v\n", user1)//user1=main.User{Name:"pprof", Gender:"女", Address:main.Address{Province:"黑龙江", City:"哈尔滨"}}
}

2.8 结构体的“继承” 

Go语言中使用结构体也可以实现其他编程语言中面向对象的继承。

结构体中字段大写开头表示可公开访问,小写表示私有(仅在定义当前结构体的包中可访问)。

//Animal 动物
type Animal struct {
    name string
}

func (a *Animal) move() {
    fmt.Printf("%s会动!\n", a.name)
}

//Dog 狗
type Dog struct {
    Feet    int8
    *Animal //通过嵌套匿名结构体实现继承
}

func (d *Dog) wang() {
    fmt.Printf("%s会汪汪汪~\n", d.name)
}

func main() {
    d1 := &Dog{
        Feet: 4,
        Animal: &Animal{ //注意嵌套的是结构体指针
            name: "乐乐",
        },
    }
    d1.wang() //乐乐会汪汪汪~
    d1.move() //乐乐会动!
}

2.9 结构体与JSON序列化 json.Marshal

//JSON序列化:结构体-->JSON格式的字符串
    data, err := json.Marshal(c)
    if err != nil {
        fmt.Println("json marshal failed")
        return
    }
    fmt.Printf("json:%s\n", data)
    //JSON反序列化:JSON格式的字符串-->结构体
    str := `{"Title":"101","Students":[{"ID":0,"Gender":"男","Name":"stu00"},{"ID":1,"Gender":"男","Name":"stu01"},{"ID":2,"Gender":"男","Name":"stu02"},{"ID":3,"Gender":"男","Name":"stu03"},{"ID":4,"Gender":"男","Name":"stu04"},{"ID":5,"Gender":"男","Name":"stu05"},{"ID":6,"Gender":"男","Name":"stu06"},{"ID":7,"Gender":"男","Name":"stu07"},{"ID":8,"Gender":"男","Name":"stu08"},{"ID":9,"Gender":"男","Name":"stu09"}]}`
    c1 := &Class{}
    err = json.Unmarshal([]byte(str), c1)
    if err != nil {
        fmt.Println("json unmarshal failed!")
        return
    }
    fmt.Printf("%#v\n", c1)

2.10 结构体标签(Tag) 

type Student struct {
    ID     int    `json:"id"` //通过指定tag实现json序列化该字段时的key
    Gender string //json序列化是默认使用字段名作为key
    name   string //私有不能被json包访问
}

 2.11 删除map类型的结构体

    ce := make(map[int]student)
    ce[1] = student{1, "xiaolizi", 22}
    ce[2] = student{2, "wang", 23}
    fmt.Println(ce)
    delete(ce, 2)
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值