Golang 操作 Map 错误记录(指针类型变量赋值问题)

一、错误代码示例

package main

import (
	"fmt"
	"time"
)

type TestUser struct {
	Name string
	Age  int
}

type TestData struct {
	Data map[string]interface{}
	Sort int
}

var TestChan chan *TestData

func init() {
	TestChan = make(chan *TestData, 100)
	Start()
}

func main() {
	arr := make([]*TestUser, 0)

	a := &TestUser{Name: "aaa", Age: 12}
	arr = append(arr, a)

	b := &TestUser{Name: "bbb", Age: 13}
	arr = append(arr, b)

	c := &TestUser{Name: "ccc", Age: 14}
	arr = append(arr, c)

	data := map[string]interface{}{"test": "指针Map容器"}
	for k, user := range arr {
		miniData := data
		miniData["user_name"] = user.Name
		miniData["user_age"] = user.Age
		d := &TestData{Data: miniData, Sort: k}
		fmt.Println("通道加入数据", d)
		TestChan <- d
	}

	time.Sleep(5 * time.Second)
}

func Start() {
	go func() {
		for {
			data := <-TestChan
			fmt.Println("通道取出数据", data)
		}
	}()
}

打印结果:
通道加入数据 &{map[descr:指针Map容器 user_age:12 user_name:aaa] 0}
通道加入数据 &{map[descr:指针Map容器 user_age:13 user_name:bbb] 1}
通道加入数据 &{map[descr:指针Map容器 user_age:14 user_name:ccc] 2}
通道取出数据 &{map[descr:指针Map容器 user_age:14 user_name:ccc] 0}
通道取出数据 &{map[descr:指针Map容器 user_age:14 user_name:ccc] 1}
通道取出数据 &{map[descr:指针Map容器 user_age:14 user_name:ccc] 2}

二、错误现象分析

上面的错误代码是按照实际遇到问题的业务代码逻辑写出来的,如果耐心看完上面的例子,应该能看出,加入 Channel 前打印的数据和从 Channel 取出来的数据不一致,而这里逻辑很简单,只是加入了一次 Channel,取出的数据就变了。

仔细观察,从 Channel 取出的数据,user_age 和 uer_name 都是循环最后一次遍历赋的值,为什么会这样?

三、错误原因说明

知识点:golang 中操作 map 、切片时,都是操作的变量对应的内存地址。

例子中的 data 参数类型为 map[string]interface{},将 data 赋值给 miniData 时,因为 golang 会操作对应的内存地址,这里就是直接把 data 变量的内存地址赋值给 miniData,所以操作 miniData 时,实际就是操作的 data。

所以在每次循环时,代码效果解释如下:

miniData := data                  
// data地址赋值给miniData 

miniData["user_name"] = user.Name 
// 等价于 data["user_name"] = user.Name 

miniData["user_age"] = user.Age
// 等价于 data["user_name"] = user.Name 

d := &TestData{Data: miniData, Sort: k}
// 等价于 d := &TestData{Data: data, Sort: k}

TestChan <- d
// 加入 Channel 中的数据,Data 值同样是最开始那个 data 的内存地址

取数据时:

data := <-TestChan

取数据的结果要看执行速度的情况;

如果上面到第二次循环时,此时 Channel 就取出一条数据,那对应打印的 Data 信息应该为循环第二次赋值的 data 数据值,即:&{map[descr:指针Map容器 user_age:13 user_name:bbb] 1};

当前案例打印结果很明显是 Channel 数据全部添加后,再从 Channel 依次取出数据,所以结果都为第三次循环修改的数据,user_age:14 user_name:ccc。

四、解决方案

知道错误原因,解决方案就很明了了,新建一个 Map 容器,把需要赋值的实际数据传入 Map,修改如下:

package main

import (
	"fmt"
	"time"
)

type TestUser struct {
	Name string
	Age  int
}

type TestData struct {
	Data map[string]interface{}
	Sort int
}

var TestChan chan *TestData

func init() {
	TestChan = make(chan *TestData, 100)
	Start()
}

func main() {
	arr := make([]*TestUser, 0)

	a := &TestUser{Name: "aaa", Age: 12}
	arr = append(arr, a)

	b := &TestUser{Name: "bbb", Age: 13}
	arr = append(arr, b)

	c := &TestUser{Name: "ccc", Age: 14}
	arr = append(arr, c)

	data := map[string]interface{}{"test": "指针Map容器"}
	for k, user := range arr {
		// ------------------修改---------------
		miniData := make(map[string]interface{}, 1)
		for k, v := range data {
			miniData[k] = v
		}
		// ------------------修改----------------
		miniData["user_name"] = user.Name
		miniData["user_age"] = user.Age

		d := &TestData{Data: miniData, Sort: k}
		fmt.Println("通道加入数据", d)
		TestChan <- d
	}

	time.Sleep(5 * time.Second)
}

func Start() {
	go func() {
		for {
			data := <-TestChan
			fmt.Println("通道取出数据", data)
		}
	}()
}


打印结果:
通道加入数据 &{map[test:指针Map容器 user_age:12 user_name:aaa] 0}
通道加入数据 &{map[test:指针Map容器 user_age:13 user_name:bbb] 1}
通道加入数据 &{map[test:指针Map容器 user_age:14 user_name:ccc] 2}
通道取出数据 &{map[test:指针Map容器 user_age:12 user_name:aaa] 0}
通道取出数据 &{map[test:指针Map容器 user_age:13 user_name:bbb] 1}
通道取出数据 &{map[test:指针Map容器 user_age:14 user_name:ccc] 2}

五、总结

golang 的变量类型可以分为 值类型 与 指针类型 两种:

  • 值类型 变量直接指向存在内存中的值,数据类型包括 int 系列、float 系列、bool、 string 、数组 和 结构体 struct;
  • 指针类型 变量指向的是内存地址,数据类型包括 指针、slice 切片、map、管道 chan、interface。

操作指针类型变量时,一定要注意当前案例赋值传值的问题!

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 在 Golang 中,可以使用指针操作 map。当我们传递 map 作为参数时,实际上是传递了 map 的一个副本。如果需要修改原始 map,可以传递 map指针,或者在函数内部返回 map指针。 以下是一个使用 map 指针的示例代码: ```go package main import "fmt" func main() { m := make(map[string]int) m["a"] = 1 m["b"] = 2 // 传递 map指针 addValue(&m, "c", 3) fmt.Println(m) // 输出:map[a:1 b:2 c:3] // 返回 map指针 m2 := createMap() fmt.Println(m2) // 输出:map[x:10 y:20 z:30] } func addValue(m *map[string]int, key string, value int) { (*m)[key] = value } func createMap() *map[string]int { m := make(map[string]int) m["x"] = 10 m["y"] = 20 m["z"] = 30 return &m } ``` 在上面的示例中,我们定义了两个函数,`addValue` 和 `createMap`,分别演示了如何传递 map指针和返回 map指针。在 `addValue` 函数中,我们使用 `*` 操作符来获取 map指针,并修改了原始的 map。在 `createMap` 函数中,我们使用 `&` 操作符来返回 map指针。 ### 回答2: 在Go语言中,map是一种无序的键值对集合,可以用于存储和检索数据。默认情况下,map是通过值传递的,即将map赋值给另一个变量时,会复制一份map的副本。但我们也可以使用指针操作map,从而可以在函数间共享和修改同一个map。 使用指针操作map有两种常见的方式。一种是直接声明一个指向map指针变量,然后通过间接引用来操作map。例如: ```go var m *map[string]int // 声明指向map指针变量 m = &map[string]int{} // 创建一个空的map,并将其地址赋值指针变量 (*m)["key"] = 123 // 通过指针间接引用map,并修改其值 ``` 另一种方式是使用make函数创建一个map,并将其指针传递给函数。例如: ```go func modifyMap(m *map[string]int) { (*m)["key"] = 123 // 通过指针间接引用map,并修改其值 } func main() { m := make(map[string]int) // 创建一个map modifyMap(&m) // 将map指针传递给函数 } ``` 需要注意的是,在使用指针操作map时,要确保map已经被初始化。否则,会发生运行时错误。另外,由于map本身就是引用类型,因此大部分情况下,直接使用map而不是指针就可以满足需求,而且更加简单和安全。只有在需要在函数间传递和修改同一个map时,才需要使用指针操作map。 ### 回答3: Golang中的map是一种无序的键值对集合,而map指针则是指向map的内存地址的指针。 在使用map时,可以通过var关键字声明一个map变量,并使用make函数来初始化该map。而如果需要使用map指针,可以通过将map变量的地址赋值指针变量来实现。 下面是一个示例代码: ``` package main import "fmt" func main() { // 声明并初始化map myMap := make(map[string]int) myMap["apple"] = 2 myMap["banana"] = 3 // 声明并初始化map指针 var myMapPtr *map[string]int myMapPtr = &myMap // 使用map指针修改map (*myMapPtr)["apple"] = 5 // 输出修改后的值 fmt.Println(myMap["apple"]) } ``` 在上述代码中,首先使用make函数初始化了一个map变量myMap,并给其中的键"apple"和"banana"分别赋值。然后,使用var关键字声明了一个map指针myMapPtr,并将myMymap的地址赋值给了myMapPtr。接下来,使用map指针修改了map中键"apple"的值为5。最后,输出了map中键"apple"的值,结果为5。 总结来说,Golangmap指针允许我们通过指针来修改和访问map的值,更灵活地操作Map的数据。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值