关于golang struct 中的 slice 无法原子赋值的问题

 更新时间:2024年01月20日 09:13:17   作者:ahfuzhang  
这篇文章主要介绍了为什么 golang struct 中的 slice 无法原子赋值的问题,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

golang struct 中的 slice 无法原子赋值

有这样一个结构体:

type MySt struct{
    Field []byte
}

我在数组排序中想要交换值:

func Swap(arr []MySt, i,j int){
    arr[i], arr[j] = arr[j], arr[i]
}

我猜测,就算其成员 Field 是引用类型,但是引用的指针也会交换,应该是没问题的。
实际测试这里复制错误了。

于是我换个写法:

func Swap(arr []MySt, i,j int){
    arr[i].Field, arr[j].Field = arr[j].Field, arr[i].Field
}

上面的代码仍然是不行。
猜测是编译期产生的代码不是类似 memcpy() 这种,而是逐个成员去交换,交换到指针这里时,无法做到原子的交换,从而出了问题。

改成下面的方法,终于对了:

func Swap(arr []MySt, i,j int){
    arr[i].Field, arr[j].Field = swapSlice(arr[j].Field, arr[i].Field)
}
func swapSlice(a, b []byte) ([]byte, []byte) {
	return b, a
}

仍然无法理解我为社么错了,求指教。

golang struct注意事项

 struct注意事项:

1.字段声明语法同变量,示例: 字段名  字段类型

2.字段的类型可以为:基本类型、数组或引用类型

3.在创建一个结构体变量后,如果没有给字段赋值,都对应一个零值(默认值),布尔类型是false,数值是0,字符串是""。                                                                                                                 

数组类型的默认值和它的元素类型相关,比如score[3]int 则为[0,0,0]                                     

指针,slice,和map的零值都为nil,即还没有分配空间

type Person struct {
	Name  string
	Age   int
	Score [5]float64
	ptr   *int
	slice []int
	map1  map[string]string
}
func main() {
	var p1 Person
	fmt.Println(p1)
    fmt.Println()
	if p1.ptr == nil {
		fmt.Println("ok1")
	}
	if p1.slice == nil {
		fmt.Println("ok2")
	}
	if p1.map1 == nil {
		fmt.Println("ok3")
	}
	//使用 slice 一定要先make
	p1.slice = make([]int, 10)
	p1.slice[0] = 100
	p1.map1 = make(map[string]string)
	p1.map1["key1"] = "tom"
	fmt.Println(p1)
}

 结果为:

4.不同结构体变量的字段是独立,互不影响,一个结构体变量字段的改变,不影响另外一个。因为结构体是值类型,不是引用类型。

type Monster struct {
	Name string
	Age  int
}
func main() {
	var monster1 Monster
	monster1.Name = "牛博文"
	monster1.Age = 500
	monster2 := monster1
	monster2.Name = "芜湖"
	fmt.Println("monster1 = ", monster1)
	fmt.Println("monster2 = ", monster2)
}

 结果:

monster2的改变并没有影响monster1。但如果想要monster2的改变影响monster1,那么应该把monster2变为指针:

monster2 := &monster1
	monster2.Name = "芜湖"
	fmt.Println("monster1 = ", monster1)
	fmt.Println("monster2 = ", *monster2)

 此时结果为:

5. 不能这样写:*p2.Name  会报错,因为.的运算符优先级比*高,应该:(*p2).Name

6. 结构体中所有字段在内存中是连续的 

r1 := Rect{Point{1, 2}, Point{3, 4}}
	fmt.Println(r1)
	//r1有四个int,在内存中是连续分布的
	//打印地址
	fmt.Printf("r1.leftUp.x的地址是 %p\n", &r1.leftUp.x)
	fmt.Printf("r1.leftUp.y的地址是 %p\n", &r1.leftUp.y)
	fmt.Printf("r1.righttUp.x的地址是 %p\n", &r1.rightUp.x)
	fmt.Printf("r1.rightUp.y的地址是 %p\n", &r1.rightUp.y)
	fmt.Println()
	//r2有两个 *Point类型,这两个*Point类型的本身地址是连续的,但是他们指向的地址不一定是连续的
	r2 := Rect2{&Point{10, 20}, &Point{30, 40}}
	//打印地址
	fmt.Printf("r2.leftUp 本身的地址是%p\n", &r2.leftUp)
	fmt.Printf("r2.rightUp 本身的地址是%p\n", &r2.rightUp)
	fmt.Printf("r2.leftUp 指向的地址是%p\n", r2.leftUp) 
	fmt.Printf("r2.rightUp 指向的地址是%p\n", r2.rightUp)
	fmt.Printf("r2.leftUp.x的地址是 %p\n", &r2.leftUp.x)
	fmt.Printf("r2.leftUp.y的地址是 %p\n", &r2.leftUp.y)
	fmt.Printf("r2.righttUp.x的地址是 %p\n", &r2.rightUp.x)
	fmt.Printf("r2.rightUp.y的地址是 %p\n", &r2.rightUp.y)

7. 结构体是用户单独定义的类型,和其他类型进行转换时需要有完全相同的字段(名字,个数,类) 

type A struct {
	num int
	str string
}
type B struct {
	num int
	str string
}
func main() {
	var a A
	var b B
	a = A(b)
	fmt.Println(a, b)
}     //输出结果为 {0 } {0 }

 8. 结构体进行type重新定义(相当于取别名), Golang认为是新的数据类型,但是相互之间可以强转

type Student struct {
	Name string
	Age  int
}
type Stu Student
func main() {
	var stu1 Student
	var stu2 Stu
	// stu2 = stu1    这样会报错,因为golang认为Stu是重新定义的结构体
	stu1 = Student(stu2)
	fmt.Println(stu1, stu2)
}    //输出结果为:{ 0} { 0}

9. struct的每个字段上,可以写上一个tag,该tag可以通过反射机制获取,常见的使用场景就是序列化和反序列化 

type Monsters struct {
	Name  string `json:"name"`   //`json:"name"`就是struct tag
	Age   int    `json:"age"`
	Skill string `json:"skill"`
}
func main() {
	//1. 创建一个monster变量
	monster := Monsters{"牛魔王", 500, "芭蕉扇"}
	//2. 将monster变量序列化为 json格式字串
    //  json.Marshal函数中使用了反射
	jsonMonster, err := json.Marshal(monster)
	if err != nil {
		fmt.Println("json 处理错误", err)
	}
	//如果age, name, skill首字母是小写,name返回空序列,所以必须要大写
	//但如果某些程序员或用户不习惯大写,非要小些,那么可以在struct定义的时候加上 `json:"name"`
	//注意:式键盘左上角的``,不是引号''
	fmt.Println("jsonMonster", jsonMonster)
	fmt.Println("jsonMonster", string(jsonMonster))
}

 创建struct实例的四种方式

 方式一:

type Person struct {
	Name string
	Age  int
}
func main() {
	p1 := Person{}
	p1.Name = "tom"
	p1.Age = 18
	fmt.Println(p1)     //{tom 18}
}

 方式二:

p2 := Person{"marry", 18}
	fmt.Println(p2)    //{marry 18}

 方式3:

// var person *person = new (Person)
	var p3 *Person = new(Person)
	//因为p3是一个指针,因此标准的给字段赋值
	(*p3).Name = "smith"
	//(*p3).Name = "smith" 也可以这样写 p3.Name = "smith"
	//原因:go的设计者为了程序员使用方便,底层会对p3.Name = "smith"进行处理
	//会给 p3 加上取值运算(*p3).Name = "smith"
	p3.Name = "amy"
	(*p3).Age = 30
	fmt.Println(*p3)   //{amy 30}

方式4:

// var person *Person = &person{}
	var person *Person = &Person{}
	//因为person是一个指针,因此标准的访问字段的方法
	//(*person).Name = "scott"
	//go的设计者为了程序员使用方便,也可以person.Name = "scott"
	//原因和上面一样,底层会对 person.Name = "scott"进行处理,会加上(*person)
	(*person).Name = "scott"
	person.Name = "scott~~"
	(*person).Age = 88
	person.Age = 10
	fmt.Println(*person)

第3种和第4种方式返回的是 结构体指针

结构体指针访问字段的标准方式应该是:(*结构体指针)字段名,比如:(*person).Name = "tom"

但go做了一个简化,也支持结构体指针.字段名,比如:preson.Name = "tom "。更符合程序员使用的习惯,go编译器底层对person.Name做了转化(*person).Name

到此这篇关于为什么 golang struct 中的 slice 无法原子赋值的文章就介绍到这了,更多相关golang struct  slice 无法原子赋值内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Golang定时器的2种实现方法与区别

    Golang定时器的2种实现方法与区别

    这篇文章主要给大家介绍了关于Golang定时器的2种实现方法与区别的相关资料,文中通过图文介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-02-02
  • golang croncli 定时器命令详解

    golang croncli 定时器命令详解

    定时器是执行任务时的常用功能,配置系统的定时任务太麻烦,所以就想用golang简单实现一个定时器命令,包括定时器命令格式、定时执行命令的相关知识,感兴趣的朋友跟随小编一起看看吧
    2022-03-03
  • 浅谈JWT在GO中的使用方法及原理

    浅谈JWT在GO中的使用方法及原理

    JWT是一种基于 JSON 的开放标准,用于在网络应用间传递声明,JWT被设计为可安全地将用户身份验证和授权数据作为 JSON 对象在各个应用程序之间传递,本文将详细给大家介绍JWT原理及在Go中的用法,需要的朋友可以参考下
    2023-05-05
  • Golang实现根据某个特定字段对结构体的顺序进行排序

    Golang实现根据某个特定字段对结构体的顺序进行排序

    这篇文章主要为大家详细介绍了Golang如何实现根据某个特定字段对结构体的顺序进行排序,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下
    2024-03-03
  • golang 切片截取参数方法详解

    golang 切片截取参数方法详解

    这篇文章主要介绍了golang 切片截取参数方法详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-01-01
  • Go语言中的goroutine和channel如何协同工作

    Go语言中的goroutine和channel如何协同工作

    在Go语言中,goroutine和channel是并发编程的两个核心概念,它们协同工作以实现高效、安全的并发执行,本文将详细探讨goroutine和channel如何协同工作,以及它们在并发编程中的作用和优势,需要的朋友可以参考下
    2024-04-04
  • go打包aar及flutter调用aar流程详解

    go打包aar及flutter调用aar流程详解

    这篇文章主要为大家介绍了go打包aar及flutter调用aar流程详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-03-03
  • go语言time.After()的作用

    go语言time.After()的作用

    time.After 是 Go 语言中的一个函数,用于返回一个定时器通道,该通道在指定时间后发送当前时间,这个功能常用于超时控制和延迟执行,本文就来详细的介绍一下,感兴趣的可以了解学习
    2024-10-10
  • RoaringBitmap原理及在Go中的使用详解

    RoaringBitmap原理及在Go中的使用详解

    这篇文章主要为大家介绍了RoaringBitmap原理及在Go中的使用详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-02-02
  • 一文了解Go语言io.Copy函数

    一文了解Go语言io.Copy函数

    这篇文章主要为大家介绍了Go语言io.Copy函数使用示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-07-07

最新评论