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语言爱好者 ...
随机推荐
- ActiveXObject常用方法
function getusername() { var WshNetwork = new ActiveXObject("WScript.Network"); alert(&quo ...
- github搜索不到代码的问题
Hi team, Please check the following three query url :https://github.com/Konctantin/GreyMagic/search? ...
- javascript循环语句
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...
- MVP模式入门案例
随着UI创建技术的功能日益增强,UI层也履行着越来越多的职责.为了更好地细分视图(View)与模型(Model)的功能,让View专注于处理数据的可视化以及与用户的交互,同时让Model只关系数据的处 ...
- Flask中session实现原理
前言 flask_session是flask框架实现session功能的一个插件,用来替代flask自带的session实现机制,flask默认的session信息保存在cookie中,不够安全和灵活 ...
- iOS杂记-告警清理
NS_ASSUME_NONNULL_BEGIN @interface Robot : NSObject @property (copy,readonly) NSString *name; - (nul ...
- Web jsp开发学习——数据库的另一种连接方式(配置静态数据库连接池)
1.导包 2.找到sever里的sever.xml,配置静态数据库连接池 <Context docBase="bookstore" path="/booksto ...
- 【转】Linux curl命令详解
[From]https://www.cnblogs.com/duhuo/p/5695256.html 命令:curl 在Linux中curl是一个利用URL规则在命令行下工作的文件传输工具,可以说是一 ...
- Debian系统软件安装
查看已安装软件 dpkg -l | grep -i name apt-get remove name 建议用root安装,有一些工具,使用非root用户安装后,仍然不识别命令,可能跟权限有关. net ...
- 【.NET】关于.NET前后台提示框的那点事
前言 关于提示框,或多或少都用到过,提示框常见方式两种:js原生alert() 和 div模拟弹层:下面以一个常见的需求业务场景来展现提示框的那点事: 正文内容 客户:需求方: 小白:实现方(全权负责 ...