go语言之行--结构体(struct)详解、链表
一、struct简介
go语言中没有像类的概念,但是可以通过结构体struct实现oop(面向对象编程)。struct的成员(也叫属性或字段)可以是任何类型,如普通类型、复合类型、函数、map、interface、struct等,所以我们可以理解为go语言中的“类”。
二、struct详解
struct定义
在定义struct成员时候区分大小写,若首字母大写则该成员为公有成员(对外可见),否则是私有成员(对外不可见)。
- type struct_variable_type struct {
- member member_type
- member member_type
- .....
- member member_type
- }
- //示例
- type Student struct {
- name string
- age int
- Class string
- }
声明与初始化
- var stu1 Student
- var stu2 *Student= &Student{} //简写stu2 := &Student{}
- var stu3 *Student = new(Student) //简写stu3 := new(Student)
struct使用
在struct中,无论使用的是指针的方式声明还是普通方式,访问其成员都使用".",在访问的时候编译器会自动把 stu2.name 转为 (*stu2).name。
struct分配内存使用new,返回的是指针。
struct没有构造函数,但是我们可以自己定义“构造函数”。
struct是我们自己定义的类型,不能和其他类型进行强制转换。
- package main
- import "fmt"
- type Student struct {
- name string
- age int
- Class string
- }
- func main() {
- var stu1 Student
- stu1.age =
- stu1.name = "wd"
- stu1.Class = "class1"
- fmt.Println(stu1.name) //wd
- var stu2 *Student = new(Student)
- stu2.name = "jack"
- stu2.age =
- fmt.Println(stu2.name,(*stu2).name)//jack jack
- var stu3 *Student = &Student{ name:"rose",age:,Class:"class3"}
- fmt.Println(stu3.name,(*stu3).name) //rose rose
- }
自定义构造函数
以下是通过工厂模式自定义构造函数方法
- package main
- import "fmt"
- type Student struct {
- name string
- age int
- Class string
- }
- func Newstu(name1 string,age1 int,class1 string) *Student {
- return &Student{name:name1,age:age1,Class:class1}
- }
- func main() {
- stu1 := Newstu("wd",,"math")
- fmt.Println(stu1.name) // wd
- }
tag
tag可以为结构体的成员添加说明或者标签便于使用,这些说明可以通过反射获取到。
在前面提到了,结构体中的成员首字母小写对外不可见,但是我们把成员定义为首字母大写这样与外界进行数据交互会带来极大的不便,此时tag带来了解决方法。
- type Student struct {
- Name string "the name of student"
- Age int "the age of student"
- Class string "the class of student"
- }
应用场景示例,json序列化操作:
- package main
- import (
- "encoding/json"
- "fmt"
- )
- type Student struct {
- Name string `json:"name"`
- Age int `json:"age"`
- }
- func main() {
- var stu = Student{Name:"wd",Age:}
- data,err := json.Marshal(stu)
- if err != nil{
- fmt.Println("json encode failed err:",err)
- return
- }
- fmt.Println(string(data)) //{"name":"wd","age":22}
- }
匿名成员(字段、属性)
结构体中,每个成员不一定都有名称,也允许字段没有名字,即匿名成员。
匿名成员的一个重要作用,可以用来实现oop中的继承。
同一种类型匿名成员只允许最多存在一个。
当匿名成员是结构体时,且两个结构体中都存在相同字段时,优先选择最近的字段。
- package main
- import "fmt"
- type Person struct {
- Name string
- Age int
- }
- type Student struct {
- score string
- Age int
- Person
- }
- func main() {
- var stu = new(Student)
- stu.Age = //优先选择Student中的Age
- fmt.Println(stu.Person.Age,stu.Age)// 0,22
- }
继承、多继承
当结构体中的成员也是结构体时,该结构体就继承了这个结构体,继承了其所有的方法与属性,当然有多个结构体成员也就是多继承。
访问父结构中属性也使用“.”,但是当子结构体中存在和父结构中的字段相同时候,只能使用:"子结构体.父结构体.字段"访问父结构体中的属性,如上面示例的stu.Person.Age
继承结构体可以使用别名,访问的时候通过别名访问,如下面示例man1.job.Salary:
- package main
- import "fmt"
- type Person struct {
- Name string
- Age int
- }
- type Teacher struct {
- Salary int
- Classes string
- }
- type man struct {
- sex string
- job Teacher //别名,继承Teacher
- Person //继承Person
- }
- func main() {
- var man1 = new(man)
- man1.Age =
- man1.Name = "wd"
- man1.job.Salary =
- fmt.Println(man1,man1.job.Salary) //&{ {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(),其访问控制也是通过大小写区分。
方法定义,其中recv代表方法作用的结构体:
- func (recv type) methodName(parameter_list) (return_value_list) { … }
- package main
- import "fmt"
- type Person struct {
- Name string
- Age int
- }
- func (p Person) Getname() string{ //p代表结构体本身的实列,类似python中的self,这里p可以写为self
- fmt.Println(p.Name)
- return p.Name
- }
- func main() {
- var person1 = new(Person)
- person1.Age =
- person1.Name = "wd"
- person1.Getname()// wd
- }
当有了结构的方法时候,我们可以自己定义其初始化方法,由于结构体是值类型,所以我们使用指针才能改变其存储的值。
- package main
- import "fmt"
- type Person struct {
- Name string
- Age int
- }
- func (self *Person) init(name string ,age int){
- self.Name = name
- self.Age = age
- }
- func main() {
- var person1 = new(Person)
- person1.init("wd",)
- //(&person1).init("wd",22)
- fmt.Println(person1)//&{wd 22}
- }
如果实现了结构体中的String方法,在使用fmt打印时候会调用该方法,类似与python中的__str__方法.
- package main
- import "fmt"
- type Person struct {
- Name string
- Age int
- }
- func (self *Person) String() string{
- return self.Name
- }
- func main() {
- var person1 = new(Person)
- person1.Name = "wd"
- person1.Age =
- fmt.Println(person1)// wd
- }
内存分布
go中的结构体内存布局和c结构体布局类似,每个成员的内存分布是连续的,在以下示例中通过反射进行进一步说明:
- package main
- import (
- "fmt"
- "reflect"
- )
- type Student struct {
- Name string
- Age int64
- wight int64
- high int64
- score int64
- }
- func main() {
- var stu1 = new(Student)
- fmt.Printf("%p\n",&stu1.Name)
- fmt.Printf("%p\n",&stu1.Age)
- fmt.Printf("%p\n",&stu1.wight)
- fmt.Printf("%p\n",&stu1.high)
- fmt.Printf("%p\n",&stu1.score)
- typ := reflect.TypeOf(Student{})
- fmt.Printf("Struct is %d bytes long\n", typ.Size())
- // We can run through the fields in the structure in order
- n := typ.NumField()
- for i := ; i < n; i++ {
- field := typ.Field(i)
- fmt.Printf("%s at offset %v, size=%d, align=%d\n",
- field.Name, field.Offset, field.Type.Size(),
- field.Type.Align())
- }
- }
- //结果
- 0xc42007a180
- 0xc42007a190
- 0xc42007a198
- 0xc42007a1a0
- 0xc42007a1a8
- Struct is bytes long
- Name at offset , size=, align=
- Age at offset , size=, align=
- wight at offset , size=, align=
- high at offset , size=, align=
- score at offset , size=, align=
在以上结果中,可以看到内存地址的偏移总是以8字节偏移(使用的是int64,刚好是8字节),在观察其内存地址,也是连续的,所以go语言中的结构体内存布局是连续的。如下图:
三、使用struct实现链表
链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。
链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。
链表有很多种不同的类型:单向链表,双向链表以及循环链表。
下面以单链表为例,使用go语言实现:
单链表
单链表:每个节点包含下一个节点的地址,这样把所有节点都串起来的链式数据数据结构叫做链表,通常把链表中的第一个节点叫做表头。
使用struct定义单链表:
为了方便,数据区域这里使用int
- type Node struct {
- data int
- next *node
- }
链表遍历
链表的遍历是通过移动指针进行遍历,当指针到最好一个节点时,其next指针为nil
- package main
- import "fmt"
- type Node struct {
- data int
- next *Node
- }
- func Shownode(p *Node){ //遍历
- for p != nil{
- fmt.Println(*p)
- p=p.next //移动指针
- }
- }
- func main() {
- var head = new(Node)
- head.data =
- var node1 = new(Node)
- node1.data =
- head.next = node1
- var node2 = new(Node)
- node2.data =
- node1.next = node2
- Shownode(head)
- }
- //{1 0xc42000e1e0}
- //{2 0xc42000e1f0}
- //{3 <nil>}
插入节点
单链表的节点插入方法一般使用头插法或者尾插法。
头插法:每次插入在链表的头部插入节点。
- package main
- import "fmt"
- type Node struct {
- data int
- next *Node
- }
- func Shownode(p *Node){ //遍历
- for p != nil{
- fmt.Println(*p)
- p=p.next //移动指针
- }
- }
- func main() {
- var head = new(Node)
- head.data =
- var tail *Node
- tail = head //tail用于记录头节点的地址,刚开始tail的的指针指向头节点
- for i := ;i<;i++{
- var node = Node{data:i}
- node.next = tail //将新插入的node的next指向头节点
- tail = &node //重新赋值头节点
- }
- Shownode(tail) //遍历结果
- }
- //{9 0xc42007a240}
- //{8 0xc42007a230}
- //{7 0xc42007a220}
- //{6 0xc42007a210}
- //{5 0xc42007a200}
- //{4 0xc42007a1f0}
- //{3 0xc42007a1e0}
- //{2 0xc42007a1d0}
- //{1 0xc42007a1c0}
- //{0 <nil>}
尾插法:每次插入节点在尾部,这也是我们较为习惯的方法。
- package main
- import "fmt"
- type Node struct {
- data int
- next *Node
- }
- func Shownode(p *Node){ //遍历
- for p != nil{
- fmt.Println(*p)
- p=p.next //移动指针
- }
- }
- func main() {
- var head = new(Node)
- head.data =
- var tail *Node
- tail = head //tail用于记录最末尾的节点的地址,刚开始tail的的指针指向头节点
- for i := ;i<;i++{
- var node = Node{data:i}
- (*tail).next = &node
- tail = &node
- }
- Shownode(head) //遍历结果
- }
- //{0 0xc42007a1c0}
- //{1 0xc42007a1d0}
- //{2 0xc42007a1e0}
- //{3 0xc42007a1f0}
- //{4 0xc42007a200}
- //{5 0xc42007a210}
- //{6 0xc42007a220}
- //{7 0xc42007a230}
- //{8 0xc42007a240}
- //{9 <nil>}
go语言之行--结构体(struct)详解、链表的更多相关文章
- Go语言中的结构体 (struct)
Golang官方称Go语言的语法相对Java语言而言要简洁很多,但是简洁背后也灵活了很多,所以很多看似很简单的代码上的细节稍不注意就会产生坑.本文主要对struct结构体的相关的语法进行总结和说明. ...
- 结构体指针,C语言结构体指针详解
结构体指针,可细分为指向结构体变量的指针和指向结构体数组的指针. 指向结构体变量的指针 前面我们通过“结构体变量名.成员名”的方式引用结构体变量中的成员,除了这种方法之外还可以使用指针. 前面讲过,& ...
- inode结构体成员详解
概述:inode译成中文就是索引节点,它用来存放档案及目录的基本信息,包含时间.档名.使用者及群组等.inode分为内存中的inode和文件系统中的inode,为了避免混淆,我们称前者为VFS ino ...
- Solidity的自定义结构体深入详解
一.结构体定义 结构体,Solidity中的自定义类型.我们可以使用Solidity的关键字struct来进行自定义.结构体内可以包含字符串,整型等基本数据类型,以及数组,映射,结构体等复杂类型.数组 ...
- IPv4地址结构体sockaddr_in详解
sockaddr_in结构体定义 struct sockaddr_in { sa_family_t sin_family; //地址族(Address Family) uint16_t sin_por ...
- struct stat结构体的详解和用法
[cpp] view plaincopy //! 需要包含de头文件 #include <sys/types.h> #include <sys/stat.h> S_ISLNK( ...
- python之struct详解
python之struct详解 2018-05-23 18:20:29 醉小义 阅读数 20115更多 分类专栏: python 版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议 ...
- python struct详解
转载:https://www.cnblogs.com/gala/archive/2011/09/22/2184801.html 有的时候需要用python处理二进制数据,比如,存取文件,socket操 ...
- Swift语言精要 - 浅谈结构体(Struct)
CGRect, CGSize, CGPoint这些是 . String, Int, Array, Dictionary这些我们经常用的也是结构体(Struct). 那么结构体(Struct)到底是什么 ...
随机推荐
- Android实用代码
1.展开.收起状态栏 public static final void collapseStatusBar(Context ctx) { Object sbservice = ...
- View的layout机制
View框架的工作流程为:测量每个View大小(measure)-->把每个View放置到相应的位置(layout)-->绘制每个View(draw). 源代码分析 在View的源代码中, ...
- 2018-9 Java.lang.StackOverflowError
问题: Java.lang.StackOverflowError at com.**Logger.**.**.StringFilter.isValueNull(StringFilter.java:81 ...
- 从零自学Java-7.使用数组存储信息
1.创建数组: 2.设置数组的大小: 3.为数组元素赋值: 4.修改数组中的信息: 5.创建多维数组: 6.数组排序. 程序SpaceRemover:显示输入字符串,并将其中所有的空格字符替换为句点字 ...
- [POWERSHELL] [.net 3.5] [Windows Server] 在Windows Server上安装.NET3.5
Install-WindowsFeature Net-Framework-Core -source \\network\share\sxs
- Jboss EAP 6 EJB调用常见问题
1. 调用EJB的三种方法 调用EAP 6 EJB的第一种方法,使用JBoss API,如下: Properties p = new Properties(); p.put("remote. ...
- excel表格中添加单引号的方法
今天碰到需要插入大量数据的excel表格,其中有很多文本,需要添加单引号. 方法如下: 左边是原始数据,右边是我即将添加单引号的空白区域. 第一步:在需要添加的位置输入= 第二步:输入等号之后点击需要 ...
- [Python_4] Python 面向对象(OOP)
0. 说明 Python 面向对象(OOP) 笔记.迭代磁盘文件.析构函数.内置方法.多重继承.异常处理 参考 Python面向对象 1. 面向对象 # -*-coding:utf-8-*- &quo ...
- 第二次SDN上机作业
SDN第二次作业 1.安装floodlight fatter树在floodlight上的连接显示 2.生成拓扑并连接控制器floodlight,利用控制器floodlight查看图形拓扑 floodl ...
- 细数垃圾邮箱客户端 Live Mail 的BUG
以前用XP系统,里面自带的有outlook,使用中还行,不过bug也不少,常见的如 1.查找,邮件多了后,常常查找不到: 2.有时收件箱什么的突然空白,或部分邮件不见了(2G大小限制,超过了就不能做移 ...