一、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)详解、链表的更多相关文章

  1. Go语言中的结构体 (struct)

    Golang官方称Go语言的语法相对Java语言而言要简洁很多,但是简洁背后也灵活了很多,所以很多看似很简单的代码上的细节稍不注意就会产生坑.本文主要对struct结构体的相关的语法进行总结和说明. ...

  2. 结构体指针,C语言结构体指针详解

    结构体指针,可细分为指向结构体变量的指针和指向结构体数组的指针. 指向结构体变量的指针 前面我们通过“结构体变量名.成员名”的方式引用结构体变量中的成员,除了这种方法之外还可以使用指针. 前面讲过,& ...

  3. inode结构体成员详解

    概述:inode译成中文就是索引节点,它用来存放档案及目录的基本信息,包含时间.档名.使用者及群组等.inode分为内存中的inode和文件系统中的inode,为了避免混淆,我们称前者为VFS ino ...

  4. Solidity的自定义结构体深入详解

    一.结构体定义 结构体,Solidity中的自定义类型.我们可以使用Solidity的关键字struct来进行自定义.结构体内可以包含字符串,整型等基本数据类型,以及数组,映射,结构体等复杂类型.数组 ...

  5. IPv4地址结构体sockaddr_in详解

    sockaddr_in结构体定义 struct sockaddr_in { sa_family_t sin_family; //地址族(Address Family) uint16_t sin_por ...

  6. struct stat结构体的详解和用法

    [cpp] view plaincopy //! 需要包含de头文件 #include <sys/types.h> #include <sys/stat.h> S_ISLNK( ...

  7. python之struct详解

    python之struct详解 2018-05-23 18:20:29 醉小义 阅读数 20115更多 分类专栏: python   版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议 ...

  8. python struct详解

    转载:https://www.cnblogs.com/gala/archive/2011/09/22/2184801.html 有的时候需要用python处理二进制数据,比如,存取文件,socket操 ...

  9. Swift语言精要 - 浅谈结构体(Struct)

    CGRect, CGSize, CGPoint这些是 . String, Int, Array, Dictionary这些我们经常用的也是结构体(Struct). 那么结构体(Struct)到底是什么 ...

随机推荐

  1. js 匿名函数立即执行问题

    js立即执行函数写法理解 这篇真的写得很清楚了,不光括号可以将函数声明转换成函数表达式然后立即执行,!,+,-,=也都可以转换,但是可能会带来意外的结果,因此一般都用括号实现. 还有关于for (va ...

  2. ConcurrentDictionary的用法

    private static ConcurrentDictionary<Guid, string> dictDbNames = new ConcurrentDictionary<Gu ...

  3. 用JavaScript写弹窗

    每个弹窗的标识var x =0; var idzt = new Array(); var Window = function(config){ ID不重复 idzt[x] = "zhuti& ...

  4. ngnix https

    server {            listen       80;#端口号        server_name  www.xxxx.net;#本机                charset ...

  5. Vue -- 项目报错整理(1):RangeError: Maximum call stack size exceeded

    这几天项目运行报了个错: Uncaught RangeError: Maximum call stack size exceeded,刚开始看到 "returnNodeParameter&q ...

  6. Scala包的使用

    package big.data.analyse.scala.classes /** * Created by zhen on 2018/9/15. */ object Packages { def ...

  7. Security Software Engineer

    Security Software Engineer Are you excited to be part of the VR revolution and work on cutting edge ...

  8. Linux 辅助命令

    0. 说明 记录在 Linux 使用过程中的一些有帮助的命令 1. 命令集合 [1.1 错误输出重定向] # 将错误信息重定向到 /dev/null source /xxx >/dev/null ...

  9. MySQL日常运维操作---持续更新

    1.查看当前连接数: 这些参数都是什么意思呢? Threads_cached ##mysql管理的线程池中还有多少可以被复用的资源 Threads_connected ##打开的连接数 Threads ...

  10. DAU、UV、独立IP、PV的区别和联系

    基本概念 DAU(Daily Active User)日活跃用户数量.常用于反映网站.互联网应用或网络游戏的运营情况.DAU通常统计一日(统计日)之内,登录或使用了某个产品的用户数(去除重复登录的用户 ...