深入Go Map的使用技巧

原文链接:https://www.zhoubotong.site/post/60.html
之前写过一篇文章,Go map定义的几种方式以及修改技巧,今天发现还可以深入探讨下开发中容易被忽视遗漏的问题,

以下以map为例,演示大家日常开发中可能存在的问题。

Map的Value的赋值

我们来看下下面的代码编译会出现什么结果?

package main

import "fmt"

type Person struct {
    Name string
    Sex  int
}

func main() {

    u := make(map[string]Person) // 定义一个map ,值为上面Person的结构体

    person := Person{"乔峰", 0}

    u["p"] = person
    u["p"].Sex = 18 // 按道理上面u["p"] 已经赋值成了person即Person{"乔峰", 0}的结构体,使用结构体.key=value应该没毛病

    fmt.Println(u["p"])
}

输出:

./main.go:17:2: cannot assign to struct field u["p"].Sex in map

分析

为啥不能使用u[“p”].属性赋值?map[string]Person 的value是一个Person结构值,所以当使用u[“p”] = person,其实是一个值拷贝过程。

而u[“p”]则是一个值引用(注意:所有像 int、float、bool 和 string 这些基本类型都属于值类型,使用这些类型的变量是直接指向存在内存中的值。

Golang 中引用类型如指针,slice,map,channel,接口,函数等),再说简单点就是:

[值类型]

包括:int、float、bool、string、数组、结构体

值类型变量声明后,不管是否已经赋值,编译器就会为它分配内存,此时该值储存在栈上,比如j=i 时候修改某变量i/j的值不会影响另一个。

[引用类型]

包括:指针、slice切片、map、chan、interface

变量直接存放的就是一个内存地址值,这个地址值指向的空间存的才是值。所以修改器中一个,另外一个也会修改。

但是需要注意的是引用类型必须申请内存才可以使用,make()是给引用类型申请内存空间。

既然u[“p”]是值引用,那么对u[“p”].Sex = 18的修改当然是不允许滴了。

那要怎么解决赋值问题?这里我整理了两种思路来解决:
解决方案一

package main

import "fmt"

type Person struct {
    Name string
    Sex  int
}

func main() {

    u := make(map[string]Person) // 定义一个map ,值为上面Person的结构体
    person := Person{"乔峰", 0}
    u["p"] = person
    //u["p"].Sex = 18 // 按道理上面u["p"] 已经赋值成了person即Person{"乔峰", 0}的结构体
    //解决思路一
    tmp := u["p"] //使用临时变量将u["p"]即person 赋值给tmp,该操作为值拷贝
    tmp.Sex = 18  //tmp 具备struct
    u["p"] = tmp  //或者person =tmp 将tmp 赋值给u["p"]即person ,该操作为值拷贝复制回去覆盖
    fmt.Println(u["p"])
}

以上输出:

{乔峰 18}

上面tmp := u[“p”]是先做一次值拷贝,做出一个tmp副本,然后修改该副本,然后再次发生了一次值拷贝复制回去:u[“p”] = tmp,但是这种会在整个过程中发生2次结构体值拷贝,很明显这周搞法性能很差。

自己觉的都看不下去了,有什么更好的实现方案?接下来我们看下思路二:

package main

import "fmt"

type Person struct {
    Name string
    Sex  int
}

func main() {
    u := make(map[string]*Person) // 注意这里的变化:定义一个map ,值为上面Person的结构体指针
    person := Person{"乔峰", 0}
    u["p"] = &person
    u["p"].Sex = 18
    fmt.Println(u["p"])
}

上面我们将map的类型的value由Person值,改成Person指针。

这样我们实际上每次修改的都是指针所指向的Person空间,指针本身是常指针,不能修改,只读属性,但是指向的Person是可以随便修改的,

而且这里我们看到并不需要值拷贝。只是一个指针的赋值。代码明显精简了很多。

Map的遍历赋值

我们来看下下面的代码有什么问题?能说明原因吗?

package main

import "fmt"

type Person struct {
    Name string
    Sex  int
}

func main() {
    u := make(map[string]*Person) // 注意这里的变化:定义一个map ,值为上面Person的结构体指针

    //定义Person数组
    persons := []Person{
        {Name: "乔峰", Sex: 18},
        {Name: "鸠摩智", Sex: 23},
        {Name: "慕容复", Sex: 22},
    }

    //将数组依次添加到map中
    for _, person := range persons {
        u[person.Name] = &person
    }

    //打印map
    for k, v := range u {
        fmt.Println(k, "=>", v.Name)
    }
}

遍历结果出现非预期的结果。仔细看,结果是错误的:

乔峰 => 慕容复
鸠摩智 => 慕容复
慕容复 => 慕容复

我们发现map中的3个key都指向数组中最后一个结构体。

分析

for循环中,person是结构体的一个拷贝副本,所以u

[person.Name]=&person

实际上一直都是指向了同一个指针, 最终该指针的值为遍历的最后一个 struct的值拷贝

怎么改?

package main

import "fmt"

type Person struct {
    Name string
    Sex  int
}

func main() {
    u := make(map[string]*Person)

    //定义Person数组
    persons := []Person{
        {Name: "乔峰", Sex: 18},
        {Name: "鸠摩智", Sex: 23},
        {Name: "慕容复", Sex: 22},
    }

    // 遍历结构体数组,依次赋值给map
    for i := 0; i < len(persons); i++ {
        u[persons[i].Name] = &persons[i]
    }
    //&#x6253;&#x5370;map
    for k, v := range u {
        fmt.Println(k, "=>", v.Name)
    }
 }

输出:

&#x9E20;&#x6469;&#x667A; => &#x9E20;&#x6469;&#x667A;
&#x6155;&#x5BB9;&#x590D; => &#x6155;&#x5BB9;&#x590D;
&#x4E54;&#x5CF0; => &#x4E54;&#x5CF0;

通过上面的示例,希望能让大家少走弯路,避免踩坑。

Original: https://www.cnblogs.com/phpper/p/16497632.html
Author: 周伯通之草堂
Title: 深入Go Map的使用技巧

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/578375/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • 代码审计-PHP反序列化漏洞

    什么是序列化 序列化可以实现将对象压缩并格式化,方便数据的传输和存储。为什么要序列化?PHP 文件在执行结束时会把对象销毁,如果下次要引用这个对象的话就很麻烦,所以就有了对象序列化…

    Linux 2023年6月6日
    0121
  • [20211105]索引分裂 块清除 日志增加.txt

    [20211105]索引分裂 块清除 日志增加.txt –//题目起的有点怪,只是我昨天在测试时遇到的怪问题,我通过测试环境演示出来。–//当非主键索引发生…

    Linux 2023年6月13日
    097
  • JDK 环境变量配置

    一、环境准备 Windows10 jdk-9.0.1 二、下载合适的JDK版本,安装JDK 三、环境变量配置 1、右键桌面上”我的电脑”>>&#…

    Linux 2023年6月8日
    090
  • 【论文笔记】(FGSM)Explaining and Harnessing Adversarial Examples

    本文发表于 ICLR 2015,提出了经典的攻击方法 – FGSM(Fast Gradient Sign Method),这篇博客的第1-5节为重点部分,包括原文第5节…

    Linux 2023年6月7日
    0114
  • (十)redis源码解读

    一、redis工作机制 redis是 单线程,所有命令(set,get等)都会加入到队列中,然后一个个执行。 二、为什么redis速度快? 1、基于内存 2、redis协议resp…

    Linux 2023年5月28日
    0114
  • 如何隐藏shell脚本内容

    从事 Linux 开发的同学,经常需要编写 shell 脚本,有时脚本中会涉及到一些敏感内容,比如一些 IP 地址,用户名以及密码等,或者脚本中有一些关键的代码, 所有这些内容你都…

    Linux 2023年6月13日
    098
  • 2021 个人年度小结

    因为不用考研,所以大四一整年可以自由自在地学习一直以来想学却又没时间去学的东西。快乐的大四时光总是显得十分短暂,这篇博客主要用来总结过去一年所学的知识。 计算机组成原理 上的是哈尔…

    Linux 2023年6月7日
    0108
  • 实验

    编写程序实现以下功能 编写程序,打印99乘法表 将一面额为10元倍数的整钱( 输入一行字符,统计其中单词的个数。各单词之间用空格分隔,空格数可以是多个。 输入输出示例 Input …

    Linux 2023年6月7日
    0100
  • Android BLE 蓝牙开发——扫码枪基于BLESSED

    一、蓝牙模式HID与BLE 当扫码枪与手机连接时,通常采用的是 蓝牙HID(Human Interface Device)模式。本质上是一个把扫码枪作为一个硬件键盘,按照键盘协议把…

    Linux 2023年6月13日
    0111
  • 使用ipmitool配置ipmi(远程控制卡)

    404. 抱歉,您访问的资源不存在。 可能是网址有误,或者对应的内容被删除,或者处于私有状态。 代码改变世界,联系邮箱 contact@cnblogs.com 园子的商业化努力-困…

    Linux 2023年6月7日
    099
  • shell脚本echo打印错位

    问题描述 在脚本中使用curl命令请求Jenkins的API获取job的编号,随后将编号和其他字符串拼接后,使用echo命令打印出来,但打印后字符串错位了。 脚本大致如下: num…

    Linux 2023年6月8日
    0123
  • MySQL SUBSTRING_INDEX截取字符串

    一、SUBSTRING_INDEX 二、示例 Original: https://www.cnblogs.com/woods1815/p/16368248.htmlAuthor: …

    Linux 2023年6月13日
    082
  • SpringSecurity

    SpringSecurity 11.1 SpringSecurity简介 Spring 是一个非常流行和成功的 Java 应用开发框架。Spring Security 基于 Spr…

    Linux 2023年6月14日
    099
  • Linux磁盘管理

    一、磁盘管理 Linux 磁盘管理好坏直接关系到整个系统的性能问题。 Linux 磁盘管理常用的三个命令为 df、 du 和 fdisk。 df(英文全称:disk full):列…

    Linux 2023年5月27日
    0101
  • postgres 切换数据库提示remaining connection slots are reserved for non-replication superuser connections

    场景 使用下面命令在pg终端内,切换数据库时提示 \c db_name pg_user; # pg_user是非超级用户 报错 psql: FATAL: 53300: remain…

    Linux 2023年6月8日
    098
  • ffmpeg 格式转换

    1.学前知识 1.1视频码率值 码率公式: 码率(kbps)=文件大小(KB)*8/时间(秒) 所以码率和视频文件大小成正比的,不过码率超过一定值后,人眼是看不出效果的. 接下来,…

    Linux 2023年6月7日
    0135
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球