深入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)

大家都在看

  • margin-top塌陷

    一、问题描述 ​ 在两个及以上的盒子嵌套时候,内部的盒子设置的 margin-top 的效果会加到最外边的盒子上,导致内部的盒子margin-top设置失败。 – 示例…

    Linux 2023年6月14日
    094
  • 软件工程 结构法方法 第2篇随笔

    建立系统的功能模型图 首先:建立系统环境图,确定系统边界 其中: 数据流为:销售的商品,日销售额等;三个输入流,三个输出流 ​ 数据源为:营业员,经理,收款员 ​ 数据潭为:经理,…

    Linux 2023年6月7日
    093
  • Kubernetes 使用kubeadm创建集群

    实践环境 CentOS-7-x86_64-DVD-1810 Docker 19.03.9 Kubernetes version: v1.20.5 开始之前 1台Linux操作或更多…

    Linux 2023年5月27日
    0141
  • 学习一下 SpringCloud (五)– 配置中心 Config、消息总线 Bus、链路追踪 Sleuth、配置中心 Nacos

    (1) 相关博文地址: 学习一下 SpringCloud (一)– 从单体架构到微服务架构、代码拆分(maven 聚合): https://www.cnblogs.com/l-y…

    Linux 2023年6月14日
    0119
  • 软件定义网络第一次作业

    配置结果 如何pip解决下载过慢问题 实验环境配置 环境安装截图如下 安装环境过程中一些问题的解决 github连接不上 在hosts文件中加上以下语句 140.82.114.3 …

    Linux 2023年6月7日
    095
  • docker compose容器编排

    Docker Compose (可简称Compose)是一个定义与运行复杂应用程序的 Docker 工具,是 Docker 官方 &#x7F16;&#x6392;&…

    Linux 2023年6月8日
    098
  • mycat2 读写分离配置(详解)

    mycat2相对mycat1来说升级还挺多的,但是全网资料太少了,这里尽可能详细的将读写分离说清楚,目前这套配置已经在我司生产环境应用,日UV6W左右,暂时没发现问题。 1.1下载…

    Linux 2023年6月6日
    097
  • 幸运的袋子 附加动图演示!

    幸运的袋子_牛客题霸_牛客网 (nowcoder.com) 厄运的袋子 用到了深度遍历 递归回溯法 这里假设一个例子: 1 1 1 2 2 3 4 5 7 8 因为要确认是否辛运,…

    Linux 2023年6月13日
    084
  • Linux(进阶篇)

    一、进程 1 进程和内存管理 1.1 进程和线程的区别 线程是程序执行的最小单位,而进程是操作系统分配资源的最小单位 一个进程由一个或多个线程组成,线程是一个进程中代码的不同执行路…

    Linux 2023年6月7日
    081
  • 013 Linux 搞懂「文件所属者更改及权限的赋予」从未如此简单(chmod、chgrp、chown)

    01 一图详解「ls -l」 02 两种符号区分表示文件和目录 03 三种访问权限及表示 04 四种符号表示文件所属者用户 05 三个变更文件所属者及修改所属者权限的命令 06 工…

    Linux 2023年5月27日
    089
  • Tensorflow-逻辑斯蒂回归

    1.交叉熵 逻辑斯蒂回归这个模型采用的是交叉熵,通俗点理解交叉熵 推荐一篇文章讲的很清楚: 因此,交叉熵越低,这个策略就越好,最低的交叉熵也就是使用了真实分布所计算出来的信息熵,因…

    Linux 2023年6月6日
    079
  • 卷积神经网络(简单)

    1.反向传播BP 反向传播(Backpropagation)是”误差反向传播”的简称,是一种与最优化方法,用来训练人工神经网络的常见方法。 简单来说就是: …

    Linux 2023年6月6日
    0116
  • PyTorch介绍-保存和加载模型

    本节我们将会看到如何保存模型状态、加载和运行模型预测 import torch import torchvision.models as models 保存和加载模型权重 PyTo…

    Linux 2023年6月14日
    091
  • jmeter 安装与环境变量配置

    安装jmeter首先要安装与jmeter版本兼容的JDK,安装完成JDK后才能安装jmeter,JDK可以自行在官网下载或者通过360软件管家进行下载。 1、下载安装JDK 安装完…

    Linux 2023年6月8日
    088
  • 在Linux下的文件IO操作

    系统调用 为什么用户程序不能直接访问系统内核提供的服务,为了更好地保护内核空间,程序的运行空间被划分为内核空间和用户空间(俗称内核状态和用户模式),它们在不同的级别上逻辑上是相互隔…

    Linux 2023年5月27日
    085
  • 使用Retrofit上传图片

    Retrofit使用协程发送请求参考文章 :https://www.cnblogs.com/sw-code/p/14451921.html 导入依赖 app的build文件中加入:…

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