Golang通脉之结构体

Golang通脉之结构体,第1张

概述Go语言中的基础数据类型可以表示一些事物的基本属性,但是要表达一个事物的全部或部分属性时,这时候再用单一的基本数据类型明显就无法满足需求了,Go语言提供了一种自定义数据类型,可以封装多个基本数据类型,

Go语言中的基础数据类型可以表示一些事物的基本属性,但是要表达一个事物的全部或部分属性时,这时候再用单一的基本数据类型明显就无法满足需求了,Go语言提供了一种自定义数据类型,可以封装多个基本数据类型,这种数据类型叫结构体,英文名称struct。 也就是可以通过struct来定义自己的类型了。

Go语言中通过struct来实现面向对象。

结构体的定义

Go 语言中数组可以存储同一类型的数据,但在结构体中我们可以为不同项定义不同的数据类型。 结构体是由一系列具有相同类型或不同类型的数据构成的数据集合。

使用typestruct关键字来定义结构体,具体代码格式如下:

@H_301_20@type struct_variable_type struct { member deFinition; member deFinition; ... member deFinition;}

其中:

struct_variable_type:标识自定义结构体的名称,在同一个包内不能重复。member:表示结构体字段名。结构体中的字段名必须唯一。deFinition:表示结构体字段的具体类型。

定义一个Person结构体:

@H_301_20@type person struct { name string city string age int8}

同样类型的字段可以写在一行,

@H_301_20@type person1 struct { name,city string age int8}

这样就拥有了一个person的自定义类型,它有namecityage三个字段。使用这个person结构体就能够很方便的在程序中表示和存储人的信息了。

语言内置的基础数据类型是用来描述一个值的,而结构体是用来描述一组值的。比如一个人有名字、年龄和居住城市等,本质上是一种聚合型的数据类型

结构体实例化

只有当结构体实例化时,即结构体声明后,才会真正地分配内存。也就是必须实例化后才能使用结构体的字段。

结构体本身也是一种类型,可以像声明内置类型一样使用var关键字声明结构体类型。

@H_301_20@var 结构体实例 结构体类型基本实例化@H_301_20@type Books struct { Title string author string subject string book_ID int}func main() { var book1 Books /* 声明 book1 为 Books 类型 */ /* book 1 描述 */ Book1.Title = "Go 语言" Book1.author = "Go大佬" Book1.subject = "Go" Book1.book_ID = 6495407}

通过.来访问结构体的字段(成员变量)。

匿名结构体

在定义一些临时数据结构等场景下还可以使用匿名结构体。

@H_301_20@func main() { var user struct{name string; Age int} user.name = "张三" user.Age = 20 fmt.Printf("%#v\n",user)}创建指针类型结构体

还可以通过使用new关键字对结构体进行实例化,得到的是结构体的地址:

@H_301_20@var p = new(person)fmt.Printf("%T\n",p) //*main.personfmt.Printf("p=%#v\n",p) //p=&main.person{name:"",city:"",age:0}

可以看出p是一个结构体指针。

需要注意的是在Go语言中支持对结构体指针直接使用.来访问结构体的成员。

@H_301_20@var p = new(person)p.name = "张三"p.age = 18p.city = "深圳"fmt.Printf("p=%#v\n",p) //p=&main.person{name:"张三",city:"深圳",age:18}取结构体的地址实例化

使用&对结构体进行取地址 *** 作相当于对该结构体类型进行了一次new实例化 *** 作。

@H_301_20@book := &Books{}book.Title = "Java"book.author = "Java大佬"book.subject = "Java 语言"book.book_ID = 6495700

book.Title= "Java"其实在底层是(*book).Title= "Java",这是Go语言实现的语法糖。

结构体初始化

没有初始化的结构体,其成员变量都是对应其类型的零值。

@H_301_20@type person struct { name string city string age int8}func main() { var p person fmt.Printf("p=%#v\n",p) //p=main.person{name:"",age:0}}使用键值对初始化

使用键值对对结构体进行初始化时,键对应结构体的字段,值对应该字段的初始值。

@H_301_20@p := person{ name: "张三",city: "",age: 18,}fmt.Printf("p=%#v\n",p) //p=main.person{name:"张三",age:18}

也可以对结构体指针进行键值对初始化,例如:

@H_301_20@p := &person{ name: "张三",city: "深圳",age:18}

当某些字段没有初始值的时候,该字段可以不写。此时,没有指定初始值的字段的值就是该字段类型的零值。

@H_301_20@p := &person{ city: "深圳",age:0}使用值的列表初始化

初始化结构体的时候可以简写,也就是初始化的时候不写键,直接写值:

@H_301_20@p := &person{ "张三","深圳",18,age:18}

使用这种格式初始化时,需要注意:

必须初始化结构体的所有字段。初始值的填充顺序必须与字段在结构体中的声明顺序一致。该方式不能和键值初始化方式混用。结构体内存布局

结构体占用一块连续的内存。

@H_301_20@type test struct { a int8 b int8 c int8 d int8}n := test{ 1,2,3,4,}fmt.Printf("n.a %p\n",&n.a)fmt.Printf("n.b %p\n",&n.b)fmt.Printf("n.c %p\n",&n.c)fmt.Printf("n.d %p\n",&n.d)

输出:

@H_301_20@n.a 0xc0000a0060n.b 0xc0000a0061n.c 0xc0000a0062n.d 0xc0000a0063

关于Go语言中的内存对齐:在 Go 中恰到好处的内存对齐

@H_403_206@空结构体

空结构体是不占用空间的。

@H_301_20@var v struct{}fmt.Println(unsafe.Sizeof(v)) // 0空结构体的作用

因为空结构体不占据内存空间,因此被广泛作为各种场景下的占位符使用。一是节省资源,二是空结构体本身就具备很强的语义,即这里不需要任何值,仅作为占位符。

实现集合(Set)

Go 语言标准库没有提供 Set 的实现,通常使用 map 来代替。事实上,对于集合来说,只需要 map 的键,而不需要值。即使是将值设置为 bool 类型,也会多占据 1 个字节,那假设 map 中有一百万条数据,就会浪费 1MB 的空间。

因此,将 map 作为集合(Set)使用时,可以将值类型定义为空结构体,仅作为占位符使用即可。

@H_301_20@type Set map[string]struct{}func (s Set) Has(key string) bool { _,ok := s[key] return ok}func (s Set) Add(key string) { s[key] = struct{}{}}func (s Set) Delete(key string) { delete(s,key)}func main() { s := make(Set) s.Add("Tom") s.Add("Sam") fmt.Println(s.Has("Tom")) fmt.Println(s.Has("Jack"))}不发送数据的信道(channel)@H_301_20@func worker(ch chan struct{}) { <-ch fmt.Println("do something") close(ch)}func main() { ch := make(chan struct{}) go worker(ch) ch <- struct{}{}}

有时候使用 channel 不需要发送任何的数据,只用来通知子协程(goroutine)执行任务,或只用来控制协程并发度。这种情况下,使用空结构体作为占位符就非常合适了。

仅包含方法的结构体@H_301_20@type Door struct{}func (d Door) open() { fmt.Println("Open the door")}func (d Door) Close() { fmt.Println("Close the door")}

在部分场景下,结构体只包含方法,不包含任何的字段。例如上面的 Door,在这种情况下,Door 事实上可以用任何的数据结构替代:

@H_301_20@type Door inttype Door bool

无论是 int 还是 bool 都会浪费额外的内存,因此这种情况下,声明为空结构体是最合适的。

面试题@H_301_20@type student struct { name string age int}func main() { m := make(map[string]*student) stus := []student{ {name: "张三",age: 18},{name: "李四",age: 23},{name: "王五",age: 25},} for _,stu := range stus { m[stu.name] = &stu } for k,v := range m { fmt.Println(k,"=>",v.name) }}//与Java的foreach一样,for range使用的是副本的方式。//for range在循环时,go会创建一个额外的变量去存储循环的元素,所以在每一次迭代中,该变量都会被重新赋值,//所以m[stu.name]=&stu实际上一致指向同一个指针, //最终该指针的值为遍历的最后一个struct的值拷贝。 就像想修改切片元素的属性://for _,stu := range stus {// stu.age = stu.age+10//}//也是不可行的。构造函数

Go语言的结构体没有构造函数,但可以自己实现。 因为struct是值类型,如果结构体比较复杂的话,值拷贝性能开销会比较大,所以构造函数返回的是结构体指针类型:

@H_301_20@func NewPerson(name,city string,age int8) *person { return &person{ name: name,city: city,age: age,}}

调用构造函数

@H_301_20@p := NewPerson("张三",18)fmt.Printf("%#v\n",p) //&main.person{name:"张三",age:18}结构体的匿名字段

可以用字段来创建结构,这些字段只包含一个没有字段名的类型。这些字段被称为匿名字段。

在类型中,使用不写字段名的方式,使用另一个类型

@H_301_20@type Human struct { name string age int weight int} type Student struct { Human // 匿名字段,那么默认Student就包含了Human的所有字段 speciality string} func main() { // 初始化一个学生 mark := Student{Human{"Mark",25,120},"Computer ScIEnce"} // 访问相应的字段 fmt.Println("His name is ",mark.name) fmt.Println("His age is ",mark.age) fmt.Println("His weight is ",mark.weight) fmt.Println("His speciality is ",mark.speciality) // 修改对应的备注信息 mark.speciality = "AI" fmt.Println("Mark changed his speciality") fmt.Println("His speciality is ",mark.speciality) // 修改年龄信息 fmt.Println("Mark become old") mark.age = 46 fmt.Println("His age is",mark.age) // 修改体重信息 fmt.Println("Mark is not an athlet anymore") mark.weight += 60 fmt.Println("His weight is",mark.weight)}

可以使用"."的方式进行调用匿名字段中的属性值
实际就是字段的继承
其中可以将匿名字段理解为字段名和字段类型都是同一个
基于上面的理解,所以可以mark.Human = Human{"marcus",55,220} mark.Human.age -= 1
若存在匿名字段中的字段与非匿名字段名字相同,则最外层的优先访问,就近原则
通过匿名访问和修改字段相当的有用,但是不仅仅是struct字段,所有的内置类型和自定义类型都是可以作为匿名字段的。

注意:这里匿名字段的说法并不代表没有字段名,而是默认会采用类型名作为字段名,结构体要求字段名称必须唯一,因此一个结构体中同种类型的匿名字段只能有一个。

嵌套结构体

一个结构体中可以嵌套包含另一个结构体或结构体指针。

@H_301_20@type Address struct { city,state string}type Person struct { name string age int address Address}func main() { var p Person p.name = "Naveen" p.age = 50 p.address = Address { city: "Chicago",state: "Illinois",} fmt.Println("name:",p.name) fmt.Println("Age:",p.age) fmt.Println("City:",p.address.city) fmt.Println("State:",p.address.state)}提升字段

在结构体中属于匿名结构体的字段称为提升字段,因为它们可以被访问,就好像它们属于拥有匿名结构字段的结构一样。理解这个定义是相当复杂的。

@H_301_20@type Address struct { city,state string}type Person struct { name string age int Address}func main() { var p Person p.name = "Naveen" p.age = 50 p.Address = Address{ city: "Chicago",p.city) //city is promoted fIEld fmt.Println("State:",p.state) //state is promoted fIEld}

运行结果

@H_301_20@name: Naveen Age: 50 City: Chicago State: Illinois

若存在匿名字段中的字段与非匿名字段名字相同,则最外层的优先访问,就近原则

嵌套结构体的字段名冲突

嵌套结构体内部可能存在相同的字段名。在这种情况下为了避免歧义需要通过指定具体的内嵌结构体字段名。

@H_301_20@//Address 地址结构体type Address struct { Province string City string CreateTime string}//Email 邮箱结构体type Email struct { Account string CreateTime string}//User 用户结构体type User struct { name string Gender string Address Email}func main() { var user User user.name = "张三" user.Gender = "男" // user.CreateTime = "2021" //ambiguous selector user.CreateTime user.Address.CreateTime = "2000" //指定Address结构体中的CreateTime user.Email.CreateTime = "2000" //指定Email结构体中的CreateTime}结构体的“继承”

Go语言中使用结构体也可以实现其他编程语言中面向对象的继承。

@H_301_20@//Animal 动物type Animal struct { name string}func (a *Animal) move() { fmt.Printf("%s会动!\n",a.name)}//Dog 狗type Dog struct { Feet int8 *Animal //通过嵌套匿名结构体实现继承}func (d *Dog) wang() { fmt.Printf("%s会汪汪汪~\n",d.name)}func main() { d1 := &Dog{ Feet: 4,Animal: &Animal{ //注意嵌套的是结构体指针 name: "乐乐",},} d1.wang() //乐乐会汪汪汪~ d1.move() //乐乐会动!}结构体字段的可见性

结构体中字段大写开头表示可公开访问(可以从其他包访问它),小写表示私有(仅在定义当前结构体的包中可访问)。

结构体与JsON序列化

JsON(JavaScript Object Notation) 是一种轻量级的数据交换格式。易于人阅读和编写。同时也易于机器解析和生成。JsON键值对是用来保存Js对象的一种方式,键/值对组合中的键名写在前面并用双引号""包裹,使用冒号:分隔,然后紧接着值;多个键值之间使用英文,分隔。

@H_301_20@//Student 学生type Student struct { ID int Gender string name string}//Class 班级type Class struct { Title string Students []*Student}func main() { c := &Class{ Title: "101",Students: make([]*Student,200),} for i := 0; i < 10; i++ { stu := &Student{ name: fmt.Sprintf("stu%02d",i),Gender: "男",ID: i,} c.Students = append(c.Students,stu) } //JsON序列化:结构体-->JsON格式的字符串 data,err := Json.Marshal(c) if err != nil { fmt.Println("Json marshal Failed") return } fmt.Printf("Json:%s\n",data) //JsON反序列化:JsON格式的字符串-->结构体 str := `{"Title":"101","Students":[{"ID":0,"Gender":"男","name":"stu00"},{"ID":1,"name":"stu01"},{"ID":2,"name":"stu02"},{"ID":3,"name":"stu03"},{"ID":4,"name":"stu04"},{"ID":5,"name":"stu05"},{"ID":6,"name":"stu06"},{"ID":7,"name":"stu07"},{"ID":8,"name":"stu08"},{"ID":9,"name":"stu09"}]}` c1 := &Class{} err = Json.Unmarshal([]byte(str),c1) if err != nil { fmt.Println("Json unmarshal Failed!") return } fmt.Printf("%#v\n",c1)}结构体标签(Tag)

Tag是结构体的元信息,可以在运行的时候通过反射的机制读取出来。 Tag在结构体字段的后方定义,由一对反引号包裹起来,具体的格式如下:

@H_301_20@`key1:"value1" key2:"value2"`

结构体tag由一个或多个键值对组成。键与值使用冒号分隔,值用双引号括起来。同一个结构体字段可以设置多个键值对tag,不同的键值对之间使用空格分隔。

注意事项: 为结构体编写Tag时,必须严格遵守键值对的规则。结构体标签的解析代码的容错能力很差,一旦格式写错,编译和运行时都不会提示任何错误,通过反射也无法正确取值。例如不要在key和value之间添加空格。

例如我们为Student结构体的每个字段定义Json序列化时使用的Tag:

@H_301_20@//Student 学生type Student struct { ID int `Json:"ID"` //通过指定tag实现Json序列化该字段时的key Gender string //Json序列化是默认使用字段名作为key name string //私有不能被Json包访问}func main() { s1 := Student{ ID: 1,name: "张三",} data,err := Json.Marshal(s1) if err != nil { fmt.Println("Json marshal Failed!") return } fmt.Printf("Json str:%s\n",data) //Json str:{"ID":1,"Gender":"男"}}@H_404_403@结构体比较

结构体是值类型,如果每个字段具有可比性,则是可比较的。如果它们对应的字段相等,则认为两个结构体变量是相等的。

@H_301_20@type name struct { firstname string lastname string}func main() { name1 := name{"Steve","Jobs"} name2 := name{"Steve","Jobs"} if name1 == name2 { fmt.Println("name1 and name2 are equal") } else { fmt.Println("name1 and name2 are not equal") } name3 := name{firstname:"Steve",lastname:"Jobs"} name4 := name{} name4.firstname = "Steve" if name3 == name4 { fmt.Println("name3 and name4 are equal") } else { fmt.Println("name3 and name4 are not equal") }}

运行结果

@H_301_20@name1 and name2 are equal name3 and name4 are not equal

如果结构变量包含的字段是不可比较的,那么结构变量是不可比较的:

@H_301_20@type image struct { data map[int]int}func main() { image1 := image{data: map[int]int{ 0: 155,}} image2 := image{data: map[int]int{ 0: 155,}} if image1 == image2 { fmt.Println("image1 and image2 are equal") }} 总结

以上是内存溢出为你收集整理的Golang通脉之结构体全部内容,希望文章能够帮你解决Golang通脉之结构体所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/1249969.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-06-07
下一篇 2022-06-07

发表评论

登录后才能评论

评论列表(0条)

保存