(转)struct & 链表 & 二叉树 & 接口

原文:https://www.cnblogs.com/neozheng/p/11247866.html

 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定义的三种形式:

复制代码
1. var stu Student
2. var stu *Student = new (Student)
3. var stu *Student = &Student{}

1) 其中 2 和 3 返回的都是指向结构体的指针,访问形式如下:
    // 标准形式:
    (*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 = 18
    stu.score = 80
    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: 20,
    }
    fmt.Println(stu2.Name)

    // 结构体的定义方式3:
    var stu3 = Student{
    Name: "zheng",
        Age: 20,
    }
    fmt.Println(stu3)
}    

// 运行结果如下:
[root@NEO example01_struct01_store]# go run main/main.go
{neozheng 18 80}
Name:0xc00000c060
Age:0xc00000c070
score:0xc00000c078
NEO
{zheng 20 0}
[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 = 18
    head.Score = 100

    var stu1 Student
    stu1.Name = "stu1"
    stu1.Age = 23
    stu1.Score = 80

    head.next = &stu1

    var stu2 Student
    stu2.Name = "stu2"
    stu2.Age = 23
    stu2.Score = 80

    stu1.next = &stu2

    scanLinklist(&head)
}

// 运行结果如下:
[root@NEO example02_linkedlist01]# go run main/main.go
{neo 18 100 0xc000058150}
{stu1 23 80 0xc000058180}
{stu2 23 80 <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 := 0; i <10; i++{
    stu := Student{
        Name: fmt.Sprintf("stu1%d",i),
        Age: rand.Intn(100),
        Score: rand.Float32() * 100,
    }
    stu.next = head
    head = &stu
    }
    return head
}

func main(){
    var head *Student = new(Student)     // 定义一个指针,并为该指针分配内存空间
    head.Name = "neo"
    head.Age = 18
    head.Score = 100
    
    head =  headInsert(head)     // 通过返回值的方式,重新给 head 赋值
    scanLinklist(head)
}

// 运行结果如下:
[root@NEO example02_linkedlist_head]# go run main/main.go
{stu19 37 21.855305 0xc000058330}
{stu18 11 29.310184 0xc000058300}
{stu17 28 46.888985 0xc0000582d0}
{stu16 62 38.06572 0xc0000582a0}
{stu15 94 81.36399 0xc000058270}
{stu14 56 30.091187 0xc000058240}
{stu13 25 15.651925 0xc000058210}
{stu12 81 68.682304 0xc0000581e0}
{stu11 47 43.77142 0xc0000581b0}
{stu10 81 94.05091 0xc000058180}
{neo 18 100 <nil>}
[root@NEO example02_linkedlist_head]# 
// 引用类型的变量指向的值在函数内修改后,该引用变量指向的值在函数外也会改变;但是,以上述指针  head 为例,假如 head 在函数内改变了,在函数外 head 并没有改变(而且在函数内并没有修改 head指针对应的值,如 *head = ...)

// 上述代码也可改成如下:
func headInsert(head **Student) {    // **Student --> Student指针的指针;需要传入指针这个变量的地址
    for i := 0; i <10; i++{
    stu := Student{
        Name: fmt.Sprintf("stu1%d",i),
        Age: rand.Intn(100),
        Score: rand.Float32() * 100,
    }
    stu.next = *head    // *head --> 指针的内存地址;这个 *head 就代表函数外的那个 head
    *head = &stu    // 改变指针变量的值(指针的内存地址)
    }
}

func main(){
    var head *Student = new(Student)     // head 是一个指针变量(地址),这个地址也有自己的内存地址
    head.Name = "neo"
    head.Age = 18
    head.Score = 100
    
    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:17,
        Score: 85,
    }
    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 = 18
    root.Score = 100

    var left1 *Student = new(Student)
    left1.Name = "stu02"
    left1.Age = 18
    left1.Score = 100

    root.left = left1

    var right1 *Student = new(Student)
    right1.Name = "stu04"
    right1.Age = 18
    right1.Score = 100

    root.right = right1

    var left2 *Student = new(Student)
    left2.Name = "stu03"
    left2.Age = 18
    left2.Score = 100

    left1.left = left2

    trans(root)
}

// 运行结果如下:
[root@NEO example02_linkedlist_binTree]# go run main/main.go
{stu01 18 100 0xc000058150 0xc000058180}
{stu02 18 100 0xc0000581b0 <nil>}
{stu03 18 100 <nil> <nil>}
{stu04 18 100 <nil> <nil>}
[root@NEO example02_linkedlist_binTree]# 
复制代码

 

结构体是用户单独定义的类型,不能和其他类型进行强制转换

复制代码
type Student struct {
    Number int
}

type Stu Student     // 给 Student 类型起一个别名 Stu;别名类型和原来的类型是不同的类型

var a Student
a = Student{30}

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”, 20)
复制代码

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: 18,
    score: 80,
    }

    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
[123 125]
{}            // 为空的原因: 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: 18,
    Score: 80,
    }

    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
[123 34 115 116 117 100 101 110 116 95 110 97 109 101 34 58 34 115 116 117 48 49 34 44 34 97 103 101 34 58 49 56 44 34 115 99 111 114 101 34 58 56 48 125]
{"student_name":"stu01","age":18,"score":80}
[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 = 200     // 匿名字段可以通过 t.类型 的方式直接访问
    t.Car.Name = "001"    // 如果匿名字段也是匿名结构体,则可简写为: t.Name = "001"
    t.Age = 18

    fmt.Println(t)
}

// 运行结果如下:
[root@NEO example01_struct03_anonymousField]# go run main/main.go
{{001 18} 0001-01-01 00:00:00 +0000 UTC 200}
[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", 10, 200)    // stu.init()是简写形式;此处标准写法应该是: (&stu).init(),但当p传入指针时,go会自动把 stu 转化为 (&stu)

    stu1 := stu.get()
    fmt.Println(stu1)

    var a integer
    a = 100
    a.print()

    a.set(1000)
    a.print()
}

// 运行结果如下:
[root@NEO example01_struct04_method]# go run main/main.go
{stu 10 200 0}
{stu 10 200 0}
p is 100
p is 1000
[root@NEO example01_struct04_method]# 


方法和函数的区别
1)函数调用: function(variable, 参数列表)
2)方法: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 = 100
    bike.name = "bike"
    bike.wheel = 2

    fmt.Println(bike)
    bike.Run()

    var train Train
    train.c.weight = 100    // 组合
    train.c.name = "train"
    train.c.Run()
}

// 运行结果如下:
[root@NEO example01_struct05_inherit]# go run main/main.go
{{100 bike} 2}
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 = 100    
    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=[100]
[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: 18,
    score: 100,
    }

    // 一个类型实现了这个接口,那么这个类型就能给这个接口赋值
    t = &stu      // 因为是 Student 的指针实现了 Print() 函数,所以应该把 &stu 赋值给 t

    t.Print()
}

// 运行结果如下:
[root@NEO example03_interface]# go run main/main.go
name:stu01 age:18 score:100
[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:   20,
        score: 200,
    }

    t = stu        // 初始化该结构体;将对象复制给接口,会发生拷贝,而接口内部存储的是指向这个复制品的指针,即无法修改复制品的状态,也无法获取指针
    t.Print()
    t.Sleep()

    var people People = People{
        name: "people",
        age:  100,
    }

    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: 20
score: 200
student sleep
name: people
age: 100
people sleep
t: {people 100}
[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

 

Code your future.
posted @ 2023-10-09 18:01  liujiacai  阅读(6)  评论(0编辑  收藏  举报