golang(5):struct & 链表 & 二叉树 & 接口
struct : 结构体
// 1. 用来自定义复杂数据结构
// 2. struct里面可以包含多个字段(属性)
// 3. struct类型可以定义方法,注意和函数的区分
// 4. struct类型是值类型
// 5. struct类型可以嵌套
// 6. Go语言没有class类型,只有struct类型
struct的定义
struct 声明:
type 标识符 struct {
field1 type
field2 type
} // 示例:
type Student struct {
Name string
Age int
Score int
}
struct定义的三种形式:
. var stu Student
. var stu *Student = new (Student)
. var stu *Student = &Student{} ) 其中 和 返回的都是指向结构体的指针,访问形式如下:
// 标准形式:
(*stu).Name
(*stu).Age // 简写形式:
stu.Name
stu.Age
struct的内存布局:struct中的所有字段在内存是连续的,布局如下:
示例代码:
package main import "fmt" type Student struct {
Name string
Age int // int 占64个字节
score float32 // 首字母小写表示不能被外部的包引用
} func main(){
// 结构体的定义方式1:
var stu Student
stu.Name = "neozheng"
stu.Age =
stu.score =
fmt.Println(stu) fmt.Printf("Name:%p\n",&stu.Name) // 打印地址
fmt.Printf("Age:%p\n",&stu.Age)
fmt.Printf("score:%p\n",&stu.score) // 结构体的定义方式2:
var stu2 *Student= &Student{
Name: "NEO",
Age: ,
}
fmt.Println(stu2.Name) // 结构体的定义方式3:
var stu3 = Student{
Name: "zheng",
Age: ,
}
fmt.Println(stu3)
} // 运行结果如下:
[root@NEO example01_struct01_store]# go run main/main.go
{neozheng }
Name:0xc00000c060
Age:0xc00000c070
score:0xc00000c078
NEO
{zheng }
[root@NEO example01_struct01_store]#
链表定义
type Student struct {
Name string // 数据域
Next *Student // 指针域,指向下一个节点
} 每个节点包含下一个节点的地址,这样把所有的节点串起来了,通常把链表中的第一个节点叫做链表头
链表尾插法
// 示例代码:
package main import "fmt" type Student struct {
Name string
Age int
Score float32
next *Student
} func scanLinklist(p *Student){ // 形参p是Student型的指针,指针参数需要传入一个地址
for p != nil {
fmt.Println(*p)
p = p.next // 这是简写,标准写法是 p = (*p).next
}
} func main(){
var head Student
head.Name = "neo"
head.Age =
head.Score = var stu1 Student
stu1.Name = "stu1"
stu1.Age =
stu1.Score = head.next = &stu1 var stu2 Student
stu2.Name = "stu2"
stu2.Age =
stu2.Score = stu1.next = &stu2 scanLinklist(&head)
} // 运行结果如下:
[root@NEO example02_linkedlist01]# go run main/main.go
{neo 0xc000058150}
{stu1 0xc000058180}
{stu2 <nil>}
[root@NEO example02_linkedlist01]#
链表头插法
// 示例代码:
package main import (
"fmt"
"math/rand"
) type Student struct {
Name string
Age int
Score float32
next *Student
} func scanLinklist(p *Student){ // 形参p是Student型的指针,指针参数需要传入一个地址
for p != nil {
fmt.Println(*p)
p = p.next // 这是简写,标准写法是 p = (*p).next
}
} func headInsert(head *Student) *Student {
for i := ; i <; i++{
stu := Student{
Name: fmt.Sprintf("stu1%d",i),
Age: rand.Intn(),
Score: rand.Float32() * ,
}
stu.next = head
head = &stu
}
return head
} func main(){
var head *Student = new(Student) // 定义一个指针,并为该指针分配内存空间
head.Name = "neo"
head.Age =
head.Score = head = headInsert(head) // 通过返回值的方式,重新给 head 赋值
scanLinklist(head)
} // 运行结果如下:
[root@NEO example02_linkedlist_head]# go run main/main.go
{stu19 21.855305 0xc000058330}
{stu18 29.310184 0xc000058300}
{stu17 46.888985 0xc0000582d0}
{stu16 38.06572 0xc0000582a0}
{stu15 81.36399 0xc000058270}
{stu14 30.091187 0xc000058240}
{stu13 15.651925 0xc000058210}
{stu12 68.682304 0xc0000581e0}
{stu11 43.77142 0xc0000581b0}
{stu10 94.05091 0xc000058180}
{neo <nil>}
[root@NEO example02_linkedlist_head]#
// 引用类型的变量指向的值在函数内修改后,该引用变量指向的值在函数外也会改变;但是,以上述指针 head 为例,假如 head 在函数内改变了,在函数外 head 并没有改变(而且在函数内并没有修改 head指针对应的值,如 *head = ...) // 上述代码也可改成如下:
func headInsert(head **Student) { // **Student --> Student指针的指针;需要传入指针这个变量的地址
for i := ; i <; i++{
stu := Student{
Name: fmt.Sprintf("stu1%d",i),
Age: rand.Intn(),
Score: rand.Float32() * ,
}
stu.next = *head // *head --> 指针的内存地址;这个 *head 就代表函数外的那个 head
*head = &stu // 改变指针变量的值(指针的内存地址)
}
} func main(){
var head *Student = new(Student) // head 是一个指针变量(地址),这个地址也有自己的内存地址
head.Name = "neo"
head.Age =
head.Score = headInsert(&head) // 传入 head 这个指针的地址
scanLinklist(head)
} // 链表头一直在变化,所以目的是当在函数内修改 指向链表头的那个指针(指向链表头的那个地址) 时,函数外的相应指针(地址)也要改变;所以函数的形参是 指针的指针,函数值参时是 指针的地址(指向这个指针的地址),即 二级指针
删除节点
// 示例代码:(不考虑删除头节点 )
func delNode(p *Student){
var prev = p
for p != nil {
if (p.Name == "stu16") {
prev.next = p.next
break
}
prev = p // 把此次循环中的节点保存下来,以作为下次循环中的 上一个节点
p = p.next
}
}
添加节点
// 示例代码:
func addNode(p *Student, newNode *Student){
for p != nil {
if (p.Name == "stu16") {
newNode.next = p.next
p.next = newNode
break
}
p = p.next
}
} ... func main(){
... newNode := &Student{
Name:"MAPLE",
Age:,
Score: ,
}
addNode(head, newNode)
scanLinklist(head)
}
双链表定义
type Student struct {
Name string
Next *Student
Prev *Student
} // 如果有两个指针分别指向前一个节点和后一个节点,我们叫做双链表
二叉树定义
type Student struct {
Name string
left* Student
right* Student
} // 如果每个节点有两个指针分别用来指向左子树和右子树,我们把这样的结构叫做二叉树
示例代码:
// 示例代码:
package main import "fmt" type Student struct {
Name string
Age int
Score float32
left *Student
right *Student
} func trans(root *Student) {
if root == nil {
return
}
fmt.Println(*root) trans(root.left)
trans(root.right) } func main() {
var root *Student = new(Student) root.Name = "stu01"
root.Age =
root.Score = var left1 *Student = new(Student)
left1.Name = "stu02"
left1.Age =
left1.Score = root.left = left1 var right1 *Student = new(Student)
right1.Name = "stu04"
right1.Age =
right1.Score = root.right = right1 var left2 *Student = new(Student)
left2.Name = "stu03"
left2.Age =
left2.Score = left1.left = left2 trans(root)
} // 运行结果如下:
[root@NEO example02_linkedlist_binTree]# go run main/main.go
{stu01 0xc000058150 0xc000058180}
{stu02 0xc0000581b0 <nil>}
{stu03 <nil> <nil>}
{stu04 <nil> <nil>}
[root@NEO example02_linkedlist_binTree]#
结构体是用户单独定义的类型,不能和其他类型进行强制转换
type Student struct {
Number int
} type Stu Student // 给 Student 类型起一个别名 Stu;别名类型和原来的类型是不同的类型 var a Student
a = Student{} var b Stu
a = b // 这一步会报错,因为 Stu 和 Student 是不同的类型,它们只是字段一模一样;想要解决这个问题可以把 b 强转为 Student 类型: a = Student(b)
golang中的struct没有构造函数(如 python 类中的 __init__ 方法),一般可以使用工厂模式来解决这个问题
// 示例代码:
Package model
type student struct {
Name stirng
Age int
} func NewStudent(name string, age int) *student {
return &student{
Name:name,
Age:age,
}
} Package main
S := new (student) // student 是私有的,在其它包里面不能导入
S := model.NewStudent(“tony”, )
struct 中的 tag
// 我们可以为struct中的每个字段,写上一个tag。这个tag可以通过反射的机制获取到,最常用的场景就是json序列化和反序列化
type student struct {
Name stirng "this is name field"
Age int "this is age field"
} // 示例代码:
// 示例1:
package main import (
"fmt"
"encoding/json"
) type Student struct{
name string
age int
score int
} func main(){
var stu Student = Student{
name: "stu01",
age: ,
score: ,
} data,err := json.Marshal(stu) // 用 json 来打包;json.Marshal() 返回值是 []byte 和 error ,[]byte 即 字符数组(其中每个元素是byte,ascii码) if err != nil {
fmt.Println("json encode stu failed",err)
}
fmt.Println(data) // data 是一个字符数组,这样打印出来是 ascii码
fmt.Println(string(data)) // 把字符数组转换为 string 型
} // 运行结果如下:
[root@NEO example01_struct02_tag]# go run main/main.go
[ ]
{} // 为空的原因: json.Marshal() 打包的时候是在另外一个包里面,在另外一个包里面则无法访问这个包结构体里面那些首字母小写的 字段(首字母小写的字段没有导出权限)
[root@NEO example01_struct02_tag]# // tag 使用的示例代码:
package main import (
"fmt"
"encoding/json"
) type Student struct{
Name string `json:"student_name"` // tag:`key-value` 的形式;key表示是哪个包里面会读这个tag,value表示这个包读取这个字段时把这个字段打包成什么名字(这个tag是给 json 用的)
Age int `json:"age"`
Score int `json:"score"`
} func main(){
var stu Student = Student{
Name: "stu01",
Age: ,
Score: ,
} data,err := json.Marshal(stu) // 用 json 来打包;json.Marshal() 返回值是 []byte 和 error ,[]byte 即 字符数组(中每个元素是byte,ascii码) if err != nil {
fmt.Println("json encode stu failed",err)
}
fmt.Println(data) // data 是一个字符数组,这样打印出来是 ascii码
fmt.Println(string(data)) // 把字符数组转换为 string 型
} // 运行结果如下:
[root@NEO example01_struct02_tag]# go run main/main.go
[ ]
{"student_name":"stu01","age":,"score":}
[root@NEO example01_struct02_tag]#
匿名字段:结构体中字段可以没有名字,即匿名字段
// 示例代码:
package main import (
"fmt"
"time"
) type Car struct {
Name string
Age int
} type Train struct {
Car // 只有类型,没有字段爽口:匿名字段(该匿名字段也是匿名结构体)
Start time.Time
int // 也是匿名字段
} func main(){
var t Train t.int = // 匿名字段可以通过 t.类型 的方式直接访问
t.Car.Name = "" // 如果匿名字段也是匿名结构体,则可简写为: t.Name = "001"
t.Age = fmt.Println(t)
} // 运行结果如下:
[root@NEO example01_struct03_anonymousField]# go run main/main.go
{{ } -- :: + UTC }
[root@NEO example01_struct03_anonymousField]#
匿名字段冲突处理
// 示例1:
type Car struct {
Name string
Age int // 和 Train 中的字段同名
} type Train struct {
Car // 匿名结构体中也有 Age 这个字段
Start time.Time
Age int // 如果 Train 中也有 Age 字段,则 t.Age 指向的是 Train 中的 Age (t 为 Train 类型)
} // 示例2:
type Car1 struct {
Name string
Age int
} type Car2 struct {
Name string
Age int
} type Train struct {
Car1
Car2
} // 此时要是再引用匿名字段中的值,就必须指明 是 Car1 中的字段 还是 Car2 中的字段
// t.Car1.Name = "001"
// t.Car2.Age = 10
// Train 也有 struct 的多重继承
方法:Golang中的方法是作用在特定类型的变量上,因此自定义类型,都可以有方法,而不仅仅是struct
定义:func (reciever type) methodName(参数列表)(返回值列表){}
// (reciever type) type 表示该方法作用于哪个结构体,reciever 表示该结构体的一个实例 // 示例代码:
package main import "fmt" type integer int // 自定义 integer 类型 func (p integer) print() {
fmt.Println("p is", p)
} func (p *integer) set(b integer) { // p 就相当于 python 中的 self;因为要修改 p 的值,所以要传入指针
*p = b
} type Student struct {
Name string
Age int
Score int
sex int
} func (p *Student) init(name string, age int, score int) { // 传入指针
p.Name = name
p.Age = age
p.Score = score
fmt.Println(*p)
} func (p Student) get() Student {
return p
} func main() {
var stu Student
stu.init("stu", , ) // stu.init()是简写形式;此处标准写法应该是: (&stu).init(),但当p传入指针时,go会自动把 stu 转化为 (&stu) stu1 := stu.get()
fmt.Println(stu1) var a integer
a =
a.print() a.set()
a.print()
} // 运行结果如下:
[root@NEO example01_struct04_method]# go run main/main.go
{stu }
{stu }
p is
p is
[root@NEO example01_struct04_method]# 方法和函数的区别
)函数调用: function(variable, 参数列表)
)方法:variable.function(参数列表) 指针receiver vs 值receiver
本质上和函数的值传递和地址传递是一样的 方法的访问控制,通过大小写控制
继承
// 如果一个struct嵌套了另一个匿名结构体,那么这个结构可以直接访问匿名结构体的方法,从而实现了继承。(go 中通过匿名结构体实现继承)
组合和匿名字段
如果一个struct嵌套了另一个匿名结构体,那么这个结构可以直接访问匿名结构体的方法,从而实现了继承。
// 如果一个struct嵌套了另一个有名结构体,那么这个模式就叫组合。
示例代码:
// 示例代码:
package main import "fmt" type Car struct { // 基类
weight int
name string
} func (p *Car) Run() {
fmt.Println("running")
} type Bike struct {
Car // Bike 继承 Car
wheel int
} type Train struct {
c Car // 组合
} func main() {
var bike Bike
bike.weight =
bike.name = "bike"
bike.wheel = fmt.Println(bike)
bike.Run() var train Train
train.c.weight = // 组合
train.c.name = "train"
train.c.Run()
} // 运行结果如下:
[root@NEO example01_struct05_inherit]# go run main/main.go
{{ bike} }
running
running
[root@NEO example01_struct05_inherit]# 多重继承
如果一个struct嵌套了多个匿名结构体,那么这个结构可以直接访问多个匿名结构体的方法,从而实现了多重继承。
实现String() 方法:
// 如果一个变量实现了String()这个方法,那么 fmt.Printf 默认会调用这个变量的String()进行输出。
// 示例代码:
package main import "fmt" type Car struct { // 基类
weight int
name string
} type Train struct {
Car
} func (p Train) String() string {
str := fmt.Sprintf("name=[%s] weight=[%d]",p.name,p.weight)
return str
} func main() {
var train Train
train.weight =
train.name = "train" fmt.Printf("%s\n",train) // fmt.Printf() 中的 %s 会自动调用 train 的 String()方法
} // 运行结果如下:
[root@NEO example01_struct06_string]# go run main/main.go
name=[train] weight=[]
[root@NEO example01_struct06_string]#
接口
// 定义:Interface类型可以定义一组方法,但是这些方法的具体流程在接口中不需要实现(具体的类型去实现这些方法)。并且interface不能包含任何变量。
type example interface{
Method1(参数列表) 返回值列表
Method2(参数列表) 返回值列表
…
} // interface类型默认是一个指针 // 示例代码1:
package main import (
"fmt"
) type Test interface { // 定义一种类型;interface 也是一种类型
Print()
} type Student struct {
name string
age int
score int
} // 任何类型只要实现了这个函数(如下面的 Print()),它就现实了这个接口
func (p *Student) Print() { // 给 Student 类型定义一种方法,方法名是 接口类型Test 中的 Print()
fmt.Printf("name:%s age:%d score:%d\n",p.name,p.age,p.score)
} func main(){
var t Test
var stu Student = Student{
name:"stu01",
age: ,
score: ,
} // 一个类型实现了这个接口,那么这个类型就能给这个接口赋值
t = &stu // 因为是 Student 的指针实现了 Print() 函数,所以应该把 &stu 赋值给 t t.Print()
} // 运行结果如下:
[root@NEO example03_interface]# go run main/main.go
name:stu01 age: score:
[root@NEO example03_interface]# // t 是接口类型,因为 stu 实现了这个接口,所以 t 可以指向 stu
// 只要一个具体的类型实现了这个接口,那这个具体的类型就可以用接口来代表(接口可以用具体的类型来赋值),但反过来接口不能赋值给具体的类型 // 示例代码2:
package main import "fmt" type People struct {
name string
age int
} type Test interface {
Print()
Sleep()
} type Student struct {
name string
age int
score int
} func (p Student) Print() {
fmt.Println("name:", p.name)
fmt.Println("age:", p.age)
fmt.Println("score:", p.score)
} func (p Student) Sleep() {
fmt.Println("student sleep")
} func (people People) Print() {
fmt.Println("name:", people.name)
fmt.Println("age:", people.age)
} func (p People) Sleep() {
fmt.Println("people sleep")
} func main() { var t Test
fmt.Println(t)
//t.Print() // 这行代码会报错 ---> t 还没有初始化,不能调用其中的函数 var stu Student = Student{
name: "stu1",
age: ,
score: ,
} t = stu // 初始化该结构体;将对象复制给接口,会发生拷贝,而接口内部存储的是指向这个复制品的指针,即无法修改复制品的状态,也无法获取指针
t.Print()
t.Sleep() var people People = People{
name: "people",
age: ,
} t = people // 要想 people 能够赋值给 t ,则需要 People 全部实现 t 接口中的函数
t.Print()
t.Sleep() fmt.Println("t:", t)
} // 运行结果如下:
[root@NEO main]# go run main.go
<nil> // t 是一个空地址
name: stu1
age:
score:
student sleep
name: people
age:
people sleep
t: {people }
[root@NEO main]#
接口实现
a. Golang中的接口,不需要显示的实现。只要一个变量,含有接口类型中的所有方法,那么这个变量就实现这个接口。因此,golang中没有implement类似的关键字
b. 如果一个变量含有了多个interface类型的方法,那么这个变量就实现了多个接口。
c. 如果一个变量只含有了1个interface的方部分方法,那么这个变量没有实现这个接口。
多态:一种事物的多种形态,都可以按照统一的接口进行操作
接口嵌套:
// 一个接口可以嵌套在另外的接口,如下所示:
type ReadWrite interface {
Read(b Buffer) bool
Write(b Buffer) bool
}
type Lock interface {
Lock()
Unlock()
}
type File interface {
ReadWrite
Lock
Close()
}
类型断言
// 类型断言,由于接口是一般类型,不知道具体类型,如果要转成具体类型可以采用以下方法进行转换:
// 示例1:
var t int
var x interface{}
x = t
y = x.(int) //转成int // 示例2:
var t int
var x interface{}
x = t
y, ok = x.(int) //转成int,带检查
空接口,Interface{}
// 空接口没有任何方法,所以所有类型都实现了空接口。
var a int
var b interface{}
b = a
golang(5):struct & 链表 & 二叉树 & 接口的更多相关文章
- Golang之Struct(二叉树定义)
接招吧,看代码: package main import "fmt" //二叉树结构体 //如果每个节点有两个指针,分别用来指向左子树和右子树,我们把这样的结构叫做二叉树 type ...
- GoLang获取struct的tag
GoLang获取struct的tag内容:beego的ORM中也通过tag来定义参数的. 获取tag的内容是利用反射包来实现的.示例代码能清楚的看懂! package main import ( &q ...
- C#学习单向链表和接口 IList<T>
C#学习单向链表和接口 IList<T> 作者:乌龙哈里 时间:2015-11-04 平台:Window7 64bit,Visual Studio Community 2015 参考: M ...
- C# 链表 二叉树 平衡二叉树 红黑树 B-Tree B+Tree 索引实现
链表=>二叉树=>平衡二叉树=>红黑树=>B-Tree=>B+Tree 1.链表 链表结构是由许多节点构成的,每个节点都包含两部分: 数据部分:保存该节点的实际数据. 地 ...
- Golang中Struct与DB中表字段通过反射自动映射 - sqlmapper
Golang中操作数据库已经有现成的库"database/sql"可以用,但是"database/sql"只提供了最基础的操作接口: 对数据库中一张表的增删改查 ...
- golang自定义struct字段标签
原文链接: https://sosedoff.com/2016/07/16/golang-struct-tags.html struct是golang中最常使用的变量类型之一,几乎每个地方都有使用,从 ...
- golang(6): 接口 & 反射
接口详解 // 举例:sort包中的 Sort 函数,如下: func Sort(data Interface) Sort sorts data. It makes one call to data. ...
- Golang从合并链表聊递归
从合并链表聊递归 递归是工程师最常见的一种解决问题的方式,但是有时候不容易真正掌握.有人说是看起来很简单,自己写起来会费点劲. 最著名的例子就是斐波那契数列(Fibonacci sequence),通 ...
- golang拾遗:指针和接口
这是本系列的第一篇文章,golang拾遗主要是用来记录一些遗忘了的.平时从没注意过的golang相关知识.想做本系列的契机其实是因为疫情闲着在家无聊,网上冲浪的时候发现了zhuihu上的go语言爱好者 ...
随机推荐
- LeetCode 23. 合并K个排序链表(Merge k Sorted Lists)
题目描述 合并 k 个排序链表,返回合并后的排序链表.请分析和描述算法的复杂度. 示例: 输入: [ 1->4->5, 1->3->4, 2->6 ] 输出: ...
- 微信小程序之阻止冒泡事件
众所周知,在微信小程序给标签绑定点击方法大家都会想到 "bindtap" 但是在页面中会遇到 点击 会冒泡而触发其他元素的时间发生 那么怎么办呢 就把引发冒泡事件的始作俑者的 bi ...
- GitHub:Python
ylbtech-GitHub:Python 1.返回顶部 2.返回顶部 3.返回顶部 4.返回顶部 5.返回顶部 1. https://github.com/python 2. 6 ...
- OGG-01201
OGG-01201 Table of Contents 1. OGG-01201 1.1. 案例1 1.2. 案例2 1 OGG-01201 这种错误,出现的场景之一是 direct load 加载数 ...
- 自定义可拖动的Toast
package com.loaderman.toastdemo; import android.content.Context; import android.graphics.PixelFormat ...
- JSP 传统标签extends TagSupport
1.控制JSP页面某一部分内容是否执行 public int doStartTag() 返回EVAL_BODY_INCLUDE,执行 返回SKIP_BODY,不执行 2.控制整个JSP页面是否执行 p ...
- web容器启动加载WebApplicationContext和初始化DispatcherServlet
原文地址:http://blog.csdn.net/zghwaicsdn/article/details/51186915 ContextLoaderListener监听器,加载ROOT WebApp ...
- MSSQL字符串分割
CREATE FUNCTION dbo.f_splitstr( @str varchar(8000) )RETURNS @r TABLE(id int IDENTITY(1, 1), value va ...
- Eclipse 4.11 Debug jar包代码时进入空心J
代码调试时,进入jar包中的时候,会出现如下的情况超级影响代码调试 断点打在上面的地方,但是却进入到了空心J的那个地方了. 解决办法:去掉勾选即可. 我是这么解决的.
- PI薄膜相关的基本理论
一.耐电晕的基本理论 在电场作用下,绝缘材料聚酰亚胺薄膜的部分区域发生放电短路的现象称为局部放电 根据局部放电发生部位的不同,可分为绝缘材料内部的局部放电.表面的局部放电.发生在导体边缘而周围气体被击 ...