一、struct简介
go语言中没有像类的概念,但是可以通过结构体struct实现oop(面向对象编程)。struct的成员(也叫属性或字段)可以是任何类型,如普通类型、复合类型、函数、map、interface、struct等,所以我们可以理解为go语言中的“类”。
二、struct详解
struct定义
在定义struct成员时候区分大小写,若首字母大写则该成员为公有成员(对外可见),否则是私有成员(对外不可见)。
<span style="color: #008000;">//<span style="color: #008000;">示例
type Student <span style="color: #0000ff;">struct<span style="color: #000000;"> {
name <span style="color: #0000ff;">string<span style="color: #000000;">
age <span style="color: #0000ff;">int<span style="color: #000000;">
Class <span style="color: #0000ff;">string<span style="color: #000000;">
}
声明与初始化
在struct中,无论使用的是指针的方式声明还是普通方式,访问其成员都使用".",在访问的时候编译器会自动把 stu2.name 转为 (*stu2).name。
struct分配内存使用new,返回的是指针。
struct没有构造函数,但是我们可以自己定义“构造函数”。
struct是我们自己定义的类型,不能和其他类型进行强制转换。
type Student <span style="color: #0000ff;">struct<span style="color: #000000;"> {
name <span style="color: #0000ff;">string<span style="color: #000000;">
age <span style="color: #0000ff;">int<span style="color: #000000;">
Class <span style="color: #0000ff;">string<span style="color: #000000;">
}
func main() {
<span style="color: #0000ff;">var<span style="color: #000000;"> stu1 Student
stu1.age = <span style="color: #800080;">22<span style="color: #000000;">
stu1.name = <span style="color: #800000;">"<span style="color: #800000;">wd<span style="color: #800000;">"<span style="color: #000000;">
stu1.Class = <span style="color: #800000;">"<span style="color: #800000;">class1<span style="color: #800000;">"<span style="color: #000000;">
fmt.Println(stu1.name) <span style="color: #008000;">//<span style="color: #008000;">wd
<span style="color: #0000ff;">var</span> stu2 *Student = <span style="color: #0000ff;">new</span><span style="color: #000000;">(Student)
stu2.name </span>= <span style="color: #800000;">"</span><span style="color: #800000;">jack</span><span style="color: #800000;">"</span><span style="color: #000000;">
stu2.age </span>= <span style="color: #800080;">33</span><span style="color: #000000;">
fmt.Println(stu2.name,(</span>*stu2).name)<span style="color: #008000;">//</span><span style="color: #008000;">jack jack</span>
<span style="color: #0000ff;">var</span> stu3 *Student = &Student{ name:<span style="color: #800000;">"</span><span style="color: #800000;">rose</span><span style="color: #800000;">"</span>,age:<span style="color: #800080;">18</span>,Class:<span style="color: #800000;">"</span><span style="color: #800000;">class3</span><span style="color: #800000;">"</span><span style="color: #000000;">}
fmt.Println(stu3.name,(</span>*stu3).name) <span style="color: #008000;">//</span><span style="color: #008000;">rose rose</span>
<span style="color: #000000;">
}
自定义构造函数
type Student <span style="color: #0000ff;">struct<span style="color: #000000;"> {
name <span style="color: #0000ff;">string<span style="color: #000000;">
age <span style="color: #0000ff;">int<span style="color: #000000;">
Class <span style="color: #0000ff;">string<span style="color: #000000;">
}
func Newstu(name1 <span style="color: #0000ff;">string,age1 <span style="color: #0000ff;">int,class1 <span style="color: #0000ff;">string) *<span style="color: #000000;">Student {
<span style="color: #0000ff;">return &<span style="color: #000000;">Student{name:name1,age:age1,Class:class1}
}
func main() {
stu1 := Newstu(<span style="color: #800000;">"<span style="color: #800000;">wd<span style="color: #800000;">",<span style="color: #800080;">22,<span style="color: #800000;">"<span style="color: #800000;">math<span style="color: #800000;">"<span style="color: #000000;">)
fmt.Println(stu1.name) <span style="color: #008000;">//<span style="color: #008000;"> wd
}
tag
tag可以为结构体的成员添加说明或者标签便于使用,这些说明可以通过反射获取到。
在前面提到了,结构体中的成员首字母小写对外不可见,但是我们把成员定义为首字母大写这样与外界进行数据交互会带来极大的不便,此时tag带来了解决方法。
应用场景示例,json序列化操作:
<span style="color: #800000;">"<span style="color: #800000;">encoding/json<span style="color: #800000;">"
<span style="color: #800000;">"<span style="color: #800000;">fmt<span style="color: #800000;">"<span style="color: #000000;">
)
type Student <span style="color: #0000ff;">struct<span style="color: #000000;"> {
Name <span style="color: #0000ff;">string json:<span style="color: #800000;">"</span><span style="color: #800000;">name</span><span style="color: #800000;">"</span><span style="color: #000000;">
Age <span style="color: #0000ff;">int json:<span style="color: #800000;">"</span><span style="color: #800000;">age</span><span style="color: #800000;">"</span><span style="color: #000000;">
}
func main() {
<span style="color: #0000ff;">var stu = Student{Name:<span style="color: #800000;">"<span style="color: #800000;">wd<span style="color: #800000;">",Age:<span style="color: #800080;">22<span style="color: #000000;">}
data,err :=<span style="color: #000000;"> json.Marshal(stu)
<span style="color: #0000ff;">if err !=<span style="color: #000000;"> nil{
fmt.Println(<span style="color: #800000;">"<span style="color: #800000;">json encode Failed err:<span style="color: #800000;">"<span style="color: #000000;">,err)
<span style="color: #0000ff;">return<span style="color: #000000;">
}
fmt.Println(<span style="color: #0000ff;">string(data)) <span style="color: #008000;">//<span style="color: #008000;">{"name":"wd","age":22}
<span style="color: #000000;">
}
匿名成员(字段、属性)
结构体中,每个成员不一定都有名称,也允许字段没有名字,即匿名成员。
匿名成员的一个重要作用,可以用来实现oop中的继承。
同一种类型匿名成员只允许最多存在一个。
当匿名成员是结构体时,且两个结构体中都存在相同字段时,优先选择最近的字段。
type Person <span style="color: #0000ff;">struct<span style="color: #000000;"> {
Name <span style="color: #0000ff;">string<span style="color: #000000;">
Age <span style="color: #0000ff;">int<span style="color: #000000;">
}
type Student <span style="color: #0000ff;">struct<span style="color: #000000;"> {
score <span style="color: #0000ff;">string<span style="color: #000000;">
Age <span style="color: #0000ff;">int<span style="color: #000000;">
Person
}
func main() {
<span style="color: #0000ff;">var stu = <span style="color: #0000ff;">new<span style="color: #000000;">(Student)
stu.Age = <span style="color: #800080;">22 <span style="color: #008000;">//<span style="color: #008000;">优先选择Student中的Age
fmt.Println(stu.Person.Age,stu.Age)<span style="color: #008000;">//<span style="color: #008000;"> 0,22
}
继承、多继承
当结构体中的成员也是结构体时,该结构体就继承了这个结构体,继承了其所有的方法与属性,当然有多个结构体成员也就是多继承。
访问父结构中属性也使用“.”,但是当子结构体中存在和父结构中的字段相同时候,只能使用:"子结构体.父结构体.字段"访问父结构体中的属性,如上面示例的stu.Person.Age
继承结构体可以使用别名,访问的时候通过别名访问,如下面示例man1.job.Salary:
type Person <span style="color: #0000ff;">struct<span style="color: #000000;"> {
Name <span style="color: #0000ff;">string<span style="color: #000000;">
Age <span style="color: #0000ff;">int<span style="color: #000000;">
}
type Teacher <span style="color: #0000ff;">struct<span style="color: #000000;"> {
Salary <span style="color: #0000ff;">int<span style="color: #000000;">
Classes <span style="color: #0000ff;">string<span style="color: #000000;">
}
type man <span style="color: #0000ff;">struct<span style="color: #000000;"> {
sex <span style="color: #0000ff;">string<span style="color: #000000;">
job Teacher <span style="color: #008000;">//<span style="color: #008000;">别名,继承Teacher
Person <span style="color: #008000;">//<span style="color: #008000;">继承Person
<span style="color: #000000;">
}
func main() {
<span style="color: #0000ff;">var man1 = <span style="color: #0000ff;">new<span style="color: #000000;">(man)
man1.Age = <span style="color: #800080;">22<span style="color: #000000;">
man1.Name = <span style="color: #800000;">"<span style="color: #800000;">wd<span style="color: #800000;">"<span style="color: #000000;">
man1.job.Salary = <span style="color: #800080;">8500<span style="color: #000000;">
fmt.Println(man1,man1.job.Salary) <span style="color: #008000;">//<span style="color: #008000;">&{ {8500 } {wd 22}} 8500
}
结构体中的方法
go语言中的方法是作用在特定类型的变量上,因此自定义的类型都可以有方法,不仅仅是在结构体中。
go中的方法和传统的类的方法不太一样,方法和类并非组织在一起,传统的oop方法和类放在一个文件里面,而go语言只要在同一个包里就可,可分散在不同文件里。go的理念就是数据和实现分离,引用官方说法:“Methods are not mixed with the data definition (the structs): they are orthogonal to types; representation(data) and behavior (methods) are independent”
方法的调用通过recv.methodName(),其访问控制也是通过大小写区分。
type Person <span style="color: #0000ff;">struct<span style="color: #000000;"> {
Name <span style="color: #0000ff;">string<span style="color: #000000;">
Age <span style="color: #0000ff;">int<span style="color: #000000;">
}
func (p Person) Getname() <span style="color: #0000ff;">string{ <span style="color: #008000;">//<span style="color: #008000;">p代表结构体本身的实列,类似python中的self,这里p可以写为self
<span style="color: #000000;"> fmt.Println(p.Name)
<span style="color: #0000ff;">return<span style="color: #000000;"> p.Name
}
func main() {
<span style="color: #0000ff;">var person1 = <span style="color: #0000ff;">new<span style="color: #000000;">(Person)
person1.Age = <span style="color: #800080;">22<span style="color: #000000;">
person1.Name = <span style="color: #800000;">"<span style="color: #800000;">wd<span style="color: #800000;">"<span style="color: #000000;">
person1.Getname()<span style="color: #008000;">//<span style="color: #008000;"> wd
}
当有了结构的方法时候,我们可以自己定义其初始化方法,由于结构体是值类型,所以我们使用指针才能改变其存储的值。
type Person <span style="color: #0000ff;">struct<span style="color: #000000;"> {
Name <span style="color: #0000ff;">string<span style="color: #000000;">
Age <span style="color: #0000ff;">int<span style="color: #000000;">
}
func (self *Person) init(name <span style="color: #0000ff;">string,age <span style="color: #0000ff;">int<span style="color: #000000;">){
self.Name =<span style="color: #000000;"> name
self.Age =<span style="color: #000000;"> age
}
func main() {
<span style="color: #0000ff;">var person1 = <span style="color: #0000ff;">new<span style="color: #000000;">(Person)
person1.init(<span style="color: #800000;">"<span style="color: #800000;">wd<span style="color: #800000;">",<span style="color: #800080;">22<span style="color: #000000;">)
<span style="color: #008000;">//<span style="color: #008000;">(&person1).init("wd",22)
fmt.Println(person1)<span style="color: #008000;">//<span style="color: #008000;">&{wd 22}
}
如果实现了结构体中的String方法,在使用fmt打印时候会调用该方法,类似与python中的__str__方法.
type Person <span style="color: #0000ff;">struct<span style="color: #000000;"> {
Name <span style="color: #0000ff;">string<span style="color: #000000;">
Age <span style="color: #0000ff;">int<span style="color: #000000;">
}
func (self *Person) String() <span style="color: #0000ff;">string<span style="color: #000000;">{
<span style="color: #0000ff;">return<span style="color: #000000;"> self.Name
}
func main() {
<span style="color: #0000ff;">var person1 = <span style="color: #0000ff;">new<span style="color: #000000;">(Person)
person1.Name = <span style="color: #800000;">"<span style="color: #800000;">wd<span style="color: #800000;">"<span style="color: #000000;">
person1.Age = <span style="color: #800080;">22<span style="color: #000000;">
fmt.Println(person1)<span style="color: #008000;">//<span style="color: #008000;"> wd
}
内存分布
go中的结构体内存布局和c结构体布局类似,每个成员的内存分布是连续的,在以下示例中通过反射进行进一步说明:
<span style="color: #800000;">"<span style="color: #800000;">fmt<span style="color: #800000;">"
<span style="color: #800000;">"<span style="color: #800000;">reflect<span style="color: #800000;">"<span style="color: #000000;">
)
type Student <span style="color: #0000ff;">struct<span style="color: #000000;"> {
Name <span style="color: #0000ff;">string<span style="color: #000000;">
Age int64
wight int64
high int64
score int64
}
func main() {
<span style="color: #0000ff;">var stu1 = <span style="color: #0000ff;">new<span style="color: #000000;">(Student)
fmt.Printf(<span style="color: #800000;">"<span style="color: #800000;">%p\n<span style="color: #800000;">",&<span style="color: #000000;">stu1.Name)
fmt.Printf(<span style="color: #800000;">"<span style="color: #800000;">%p\n<span style="color: #800000;">",&<span style="color: #000000;">stu1.Age)
fmt.Printf(<span style="color: #800000;">"<span style="color: #800000;">%p\n<span style="color: #800000;">",&<span style="color: #000000;">stu1.wight)
fmt.Printf(<span style="color: #800000;">"<span style="color: #800000;">%p\n<span style="color: #800000;">",&<span style="color: #000000;">stu1.high)
fmt.Printf(<span style="color: #800000;">"<span style="color: #800000;">%p\n<span style="color: #800000;">",&<span style="color: #000000;">stu1.score)
typ :=<span style="color: #000000;"> reflect.TypeOf(Student{})
fmt.Printf(<span style="color: #800000;">"<span style="color: #800000;">Struct is %d bytes long\n<span style="color: #800000;">"<span style="color: #000000;">,typ.Size())
<span style="color: #008000;">//<span style="color: #008000;"> We can run through the fields in the structure in order
n :=<span style="color: #000000;"> typ.NumField()
<span style="color: #0000ff;">for i := <span style="color: #800080;">0; i < n; i++<span style="color: #000000;"> {
field :=<span style="color: #000000;"> typ.Field(i)
fmt.Printf(<span style="color: #800000;">"<span style="color: #800000;">%s at offset %v,size=%d,align=%d\n<span style="color: #800000;">"<span style="color: #000000;">,field.Name,field.Offset,field.Type.Size(),field.Type.Align())
}
}
<span style="color: #008000;">//<span style="color: #008000;">结果
<span style="color: #800080;">0xc42007a180
<span style="color: #800080;">0xc42007a190
<span style="color: #800080;">0xc42007a198
<span style="color: #800080;">0xc42007a1a0
<span style="color: #800080;">0xc42007a1a8<span style="color: #000000;">
Struct <span style="color: #0000ff;">is <span style="color: #800080;">48 bytes <span style="color: #0000ff;">long<span style="color: #000000;">
Name at offset <span style="color: #800080;">0,size=<span style="color: #800080;">16,align=<span style="color: #800080;">8<span style="color: #000000;">
Age at offset <span style="color: #800080;">16,size=<span style="color: #800080;">8,align=<span style="color: #800080;">8<span style="color: #000000;">
wight at offset <span style="color: #800080;">24,align=<span style="color: #800080;">8<span style="color: #000000;">
high at offset <span style="color: #800080;">32,align=<span style="color: #800080;">8<span style="color: #000000;">
score at offset <span style="color: #800080;">40,align=<span style="color: #800080;">8
在以上结果中,可以看到内存地址的偏移总是以8字节偏移(使用的是int64,刚好是8字节),在观察其内存地址,也是连续的,所以go语言中的结构体内存布局是连续的。如下图:
三、使用struct实现链表
链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。
链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。
链表有很多种不同的类型:单向链表,双向链表以及循环链表。
下面以单链表为例,使用go语言实现:
单链表
单链表:每个节点包含下一个节点的地址,这样把所有节点都串起来的链式数据数据结构叫做链表,通常把链表中的第一个节点叫做表头。
使用struct定义单链表:
为了方便,数据区域这里使用int
链表遍历
链表的遍历是通过移动指针进行遍历,当指针到最好一个节点时,其next指针为nil
type Node <span style="color: #0000ff;">struct<span style="color: #000000;"> {
data <span style="color: #0000ff;">int<span style="color: #000000;">
next *<span style="color: #000000;">Node
}
func Shownode(p Node){ <span style="color: #008000;">//<span style="color: #008000;">遍历
<span style="color: #0000ff;">for p !=<span style="color: #000000;"> nil{
fmt.Println(<span style="color: #000000;">p)
p=p.next <span style="color: #008000;">//<span style="color: #008000;">移动指针
<span style="color: #000000;"> }
}
func main() {
<span style="color: #0000ff;">var head = <span style="color: #0000ff;">new<span style="color: #000000;">(Node)
head.data = <span style="color: #800080;">1
<span style="color: #0000ff;">var node1 = <span style="color: #0000ff;">new<span style="color: #000000;">(Node)
node1.data = <span style="color: #800080;">2<span style="color: #000000;">
head.next </span>=<span style="color: #000000;"> node1
</span><span style="color: #0000ff;">var</span> node2 = <span style="color: #0000ff;">new</span><span style="color: #000000;">(Node)
node2.data </span>= <span style="color: #800080;">3</span><span style="color: #000000;">
node1.next </span>=<span style="color: #000000;"> node2
Shownode(head)
}
<span style="color: #008000;">//<span style="color: #008000;">{1 0xc42000e1e0}
<span style="color: #008000;">//<span style="color: #008000;">{2 0xc42000e1f0}
<span style="color: #008000;">//<span style="color: #008000;">{3
插入节点
单链表的节点插入方法一般使用头插法或者尾插法。
头插法:每次插入在链表的头部插入节点。
type Node <span style="color: #0000ff;">struct<span style="color: #000000;"> {
data <span style="color: #0000ff;">int<span style="color: #000000;">
next *<span style="color: #000000;">Node
}
func Shownode(p Node){ <span style="color: #008000;">//<span style="color: #008000;">遍历
<span style="color: #0000ff;">for p !=<span style="color: #000000;"> nil{
fmt.Println(<span style="color: #000000;">p)
p=p.next <span style="color: #008000;">//<span style="color: #008000;">移动指针
<span style="color: #000000;"> }
}
func main() {
<span style="color: #0000ff;">var head = <span style="color: #0000ff;">new<span style="color: #000000;">(Node)
head.data = <span style="color: #800080;">0
<span style="color: #0000ff;">var tail *<span style="color: #000000;">Node
tail = head <span style="color: #008000;">//<span style="color: #008000;">tail用于记录头节点的地址,刚开始tail的的指针指向头节点
<span style="color: #0000ff;">for i :=<span style="color: #800080;">1 ;i<<span style="color: #800080;">10;i++<span style="color: #000000;">{
<span style="color: #0000ff;">var node =<span style="color: #000000;"> Node{data:i}
node.next = tail <span style="color: #008000;">//<span style="color: #008000;">将新插入的node的next指向头节点
tail = &node <span style="color: #008000;">//<span style="color: #008000;">重新赋值头节点
<span style="color: #000000;"> }
Shownode(tail) </span><span style="color: #008000;">//</span><span style="color: #008000;">遍历结果</span>
<span style="color: #000000;">}
<span style="color: #008000;">//<span style="color: #008000;">{9 0xc42007a240}
<span style="color: #008000;">//<span style="color: #008000;">{8 0xc42007a230}
<span style="color: #008000;">//<span style="color: #008000;">{7 0xc42007a220}
<span style="color: #008000;">//<span style="color: #008000;">{6 0xc42007a210}
<span style="color: #008000;">//<span style="color: #008000;">{5 0xc42007a200}
<span style="color: #008000;">//<span style="color: #008000;">{4 0xc42007a1f0}
<span style="color: #008000;">//<span style="color: #008000;">{3 0xc42007a1e0}
<span style="color: #008000;">//<span style="color: #008000;">{2 0xc42007a1d0}
<span style="color: #008000;">//<span style="color: #008000;">{1 0xc42007a1c0}
<span style="color: #008000;">//<span style="color: #008000;">{0
尾插法:每次插入节点在尾部,这也是我们较为习惯的方法。
type Node <span style="color: #0000ff;">struct<span style="color: #000000;"> {
data <span style="color: #0000ff;">int<span style="color: #000000;">
next *<span style="color: #000000;">Node
}
func Shownode(p Node){ <span style="color: #008000;">//<span style="color: #008000;">遍历
<span style="color: #0000ff;">for p !=<span style="color: #000000;"> nil{
fmt.Println(<span style="color: #000000;">p)
p=p.next <span style="color: #008000;">//<span style="color: #008000;">移动指针
<span style="color: #000000;"> }
}
func main() {
<span style="color: #0000ff;">var head = <span style="color: #0000ff;">new<span style="color: #000000;">(Node)
head.data = <span style="color: #800080;">0
<span style="color: #0000ff;">var tail <span style="color: #000000;">Node
tail = head <span style="color: #008000;">//<span style="color: #008000;">tail用于记录最末尾的节点的地址,刚开始tail的的指针指向头节点
<span style="color: #0000ff;">for i :=<span style="color: #800080;">1 ;i<<span style="color: #800080;">10;i++<span style="color: #000000;">{
<span style="color: #0000ff;">var node =<span style="color: #000000;"> Node{data:i}
(tail).next = &<span style="color: #000000;">node
tail = &<span style="color: #000000;">node
}
Shownode(head) </span><span style="color: #008000;">//</span><span style="color: #008000;">遍历结果</span>
<span style="color: #000000;">}
<span style="color: #008000;">//<span style="color: #008000;">{0 0xc42007a1c0}
<span style="color: #008000;">//<span style="color: #008000;">{1 0xc42007a1d0}
<span style="color: #008000;">//<span style="color: #008000;">{2 0xc42007a1e0}
<span style="color: #008000;">//<span style="color: #008000;">{3 0xc42007a1f0}
<span style="color: #008000;">//<span style="color: #008000;">{4 0xc42007a200}
<span style="color: #008000;">//<span style="color: #008000;">{5 0xc42007a210}
<span style="color: #008000;">//<span style="color: #008000;">{6 0xc42007a220}
<span style="color: #008000;">//<span style="color: #008000;">{7 0xc42007a230}
<span style="color: #008000;">//<span style="color: #008000;">{8 0xc42007a240}
<span style="color: #008000;">//<span style="color: #008000;">{9