关于golang struct 中的 slice 无法原子赋值的问题
作者:ahfuzhang
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 无法原子赋值内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!