16、Golang 教程 - 结构体链表

概念:结构体是自定义复杂数据结构,struct 里面可以包含多个字段(属性),struct 类型可以定义方法,和函数有区分,struct 属于值类型,且可以做嵌套,Go 中没有 Class 类型,只有 struct 类型。

示例:回顾结构体定义

 package main

import "fmt"

type Car struct {

    // 定义属性,大写可跨包调用
    Name string
    Color string
    next Person // 嵌套结构体
}

type Person struct {

    Owner string
    Age uint
}

func main() {

    // 定义结构体变量 c 属于 Car
    var c Car
    c.Name = "baoma"
    c.Color = "yellow"
    c.next.Owner = "zc"
    c.next.Age = 25
    fmt.Println(c)
}

// 结果
{

     baoma yellow {

     zc 25}}

struct 定义的三种形式:其中 2 和 3 都是返回结构体的指针。

 1. var stu Student
2. var stu *Student = new(Student)
3. var stu *Student = &Student{

     }

调用属性

 stu.Name    stu.Age    stu.Score
or
(*stu).Name    (*stu).Age    (*stu).Score

复杂数据类型和基本数据类型的区别

 /* 1. 基本数据类型是系统定义的,& 直接显示内存地址。
   2. 复杂数据类型(结构体等)是自定义的,其中可能包含很多类型的基本数据类型,为了和基本数据类型区分,使用 & 会显示 &{数据内容}。
   3. * 代表变量为指针类型,** 为二级指针(指向各个内存地址),& 取地址。
   */
package main

import "fmt"

type Student struct {

    Name   string
    Age    int
}

func main() {

    // 创建一个结构体
    var stu1 *Student = &Student{

     }
    stu1.Name = "stu1"
    stu1.Age = 20

    fmt.Println(stu1)
    fmt.Println(&stu1)
    fmt.Println(&*stu1)
    fmt.Println(*stu1)
    fmt.Printf("%p\n",stu1)       // %p 强制显示内存地址
    fmt.Printf("%p\n",&*stu1)
    fmt.Printf("%p\n",&Student{

     })
}

// 结果
&{

     stu1 20}
0xc000006028
&{

     stu1 20}
{

     stu1 20}
0xc000004480    // stu1 的地址
0xc000004480    // stu1 等于 &*stu1
0xc000004500

1. 存储方式

 package main

import "fmt"

type Student struct {

    Name string
    Age int
    Score float32
    next *Student   // 链表
}

func main() {

    // 实体
    var head Student
    head.Name = "test"
    head.Age = 20
    head.Score = 80
    fmt.Println(head)
}

// 结果
{

     test 20 80 <nil>}

值类型存储方式地址空间连续

 package main

import "fmt"

type Student struct {

    Name string
    Age int32
    Score float32
}

func main() {

    var stu *Student = &Student {

        Name: "zhangsan",
        Age: 18,
        Score: 90,
    }

    fmt.Printf("Name:%p\n",&stu.Name)
    fmt.Printf("Age:%p\n",&stu.Age)
    fmt.Printf("Score:%p\n",&stu.Score)
}

// 结果,地址是 16 进制
Name:0xc000004480
Age:0xc000004490
Score:0xc000004494

2. 链表更新

2.1 链表定义

链表方式串联结构体。链表大小不固定,地址不连续,长度不固定,链表中第一个元素称为头部,最后一个元素指针指向 nil。链表分类为:单链表、双链表、循环单链表、循环双链表。

 package main

import "fmt"

type Student struct {

    Name    string
    Age     int
    Score   float32
    next    *Student    // 单链,指向下一个结构体地址
}

func main() {

    // 头部结构节点
    var head Student
    head.Name = "head"
    head.Age = 18
    head.Score = 100

    // 第二个结构体节点
    var stu1 Student
    stu1.Name = "stu1"
    stu1.Age = 20
    stu1.Score = 90

    // 将结构体串联
    head.next = &stu1

    // 定义指针指向链表头部
    var p *Student = &head

    // 遍历链表:链表下个节点不为空则继续执行下去,将各个节点(结构体)信息输出
    // p 指针是指向下一个结构体地址,加 * 就是下一个结构体
    for p != nil {

        fmt.Println(*p)
        // p 变更为下一个结构体地址
        p = p.next
    }
}

// 结果
// head 的 next 存放的是 stu1 的地址
// stu1 的 next 没有下一个,为 nil 
{

     head 18 100 0xc000078330}
{

     stu1 20 90 <nil>}
 package main

import "fmt"

type Student struct {

    Name    string
    Age     int
    Score   float32
    next    *Student    // 单链,下一个
}

func main() {

    // 头部结构节点
    var head Student
    head.Name = "head"
    head.Age = 18
    head.Score = 100

    // 第二个结构体节点
    var stu1 Student
    stu1.Name = "stu1"
    stu1.Age = 20
    stu1.Score = 90

    // 第一个指针
    head.next = &stu1

    // 第三个结构体节点
    var stu2 Student
    stu2.Name = "stu2"
    stu2.Age = 30
    stu2.Score = 80

    // 第二个指针
    stu1.next = &stu2

    var p *Student = &head
    for p != nil {

        fmt.Println(*p)
        // p 变更为下一个结构体地址
        p = p.next
    }
}

// 结果
{

     head 18 100 0xc000078330}
{

     stu1 20 90 0xc000078360}
{

     stu2 30 80 <nil>}

2.2 将遍历链表封装成函数

 package main

import "fmt"

type Student struct {

    Name    string
    Age     int
    Score   float32
    next    *Student    // 单链,下一个
}

func main() {

    // 头部结构节点
    var head Student
    head.Name = "head"
    head.Age = 18
    head.Score = 100

    // 第二个结构体节点
    var stu1 Student
    stu1.Name = "stu1"
    stu1.Age = 20
    stu1.Score = 90

    // 第一个指针
    head.next = &stu1

    // 第三个结构体节点
    var stu2 Student
    stu2.Name = "stu2"
    stu2.Age = 30
    stu2.Score = 80

    // 第二个指针
    stu1.next = &stu2

    // 调用遍历链表函数,传第一个结构体地址
    Req(&head)
}

// 遍历链表
func Req(p *Student) {

    for p != nil {

        fmt.Println(*p)
        p = p.next
    }
}

2.3 尾部添加元素(结构体节点)

 package main

import (
    "fmt"
    "math/rand"
)

type Students struct {

    Name string
    Age int
    Score float32
    next *Students
}

func main() {

    var stu1 Students
    stu1.Name = "stu1"
    stu1.Age = 25
    stu1.Score = 62

    var stu2 Students
    stu2.Name = "stu2"
    stu2.Age = 30
    stu2.Score = 100

    stu1.next = &stu2

    // 声明尾部变量
    var tail = &stu2
    // 尾部批量添加节点
    for i:=3;i<10;i++ {

        // 节点定义
        var stu Students = Students {

            Name: fmt.Sprintf("stu%d", i),
            Age: rand.Intn(100),
            Score: rand.Float32()*100,
        }
        // 生成结构体串联
        tail.next = &stu
        tail = &stu
    }

    Req(&stu1)
}

func Req(p *Students) {

    for p != nil {

        fmt.Println(*p)
        p = p.next
    }
}
 {

     stu1 25 62 0xc000078360}
{

     stu2 30 100 0xc000078390}
{

     stu3 81 94.05091 0xc0000783c0}
{

     stu4 47 43.77142 0xc0000783f0}
{

     stu5 81 68.682304 0xc000078420}
{

     stu6 25 15.651925 0xc000078450}
{

     stu7 56 30.091187 0xc000078480}
{

     stu8 94 81.36399 0xc0000784b0}
{

     stu9 62 38.06572 <nil>}

定义成函数

 package main

import (
    "fmt"
    "math/rand"
)

type Students struct {

    Name string
    Age int
    Score float32
    next *Students
}

func main() {

    var stu1 Students
    stu1.Name = "stu1"
    stu1.Age = 25
    stu1.Score = 62

    var stu2 Students
    stu2.Name = "stu2"
    stu2.Age = 30
    stu2.Score = 100

    stu1.next = &stu2

    // 声明尾部变量
    insertTail(&stu2)

    // 遍历结构体元素
    Req(&stu1)
}

// 尾部添加元素
func insertTail(tail *Students) {

    for i:=3;i<10;i++ {

        // 节点定义
        var stu Students = Students {

            Name: fmt.Sprintf("stu%d", i),
            Age: rand.Intn(100),
            Score: rand.Float32()*100,
        }
        // 生成结构体串联
        tail.next = &stu
        tail = &stu
    }
}

// 遍历结构体元素
func Req(p *Students) {

    for p != nil {

        fmt.Println(*p)
        p = p.next
    }
}

2.4 头部添加元素

 package main

import (
    "fmt"
    "math/rand"
)

type Student struct {

    Name   string
    Age    int
    Score  float32
    next    *Student
}

func main() {

    // 创建一个头部结构体
    var head *Student = &Student{

     }
    head.Name = "syhj"
    head.Age = 20
    head.Score = 100

    // 定义添加元素循环
    for i:=0;i<5;i++ {

        var stu = Student {

            Name: fmt.Sprintf("stu%d", i),
            Age: rand.Intn(100),
            Score:  rand.Float32()*100,
        }
        stu.next = head
        head = &stu
    }

    // 循环输出
    for head != nil {

        fmt.Println(*head)
        head = head.next
    }
}
 {

     stu4 56 30.091187 0xc000078420}
{

     stu3 25 15.651925 0xc0000783f0}
{

     stu2 81 68.682304 0xc0000783c0}
{

     stu1 47 43.77142 0xc000078390}
{

     stu0 81 94.05091 0xc000078360}
{

     syhj 20 100 <nil>}

定义成函数

 package main

import (
    "fmt"
    "math/rand"
)

type Student struct {

    Name   string
    Age    int
    Score  float32
    next    *Student
}

func main() {

    // 创建一个头部结构体
    var stu1 *Student = &Student{

     }
    stu1.Name = "stu1"
    stu1.Age = 20
    stu1.Score = 100

    // 头部添加元素
    insertHead(&stu1)

    // 循环遍历输出
    Req(stu1)
}

// 头部插入节点
// p 为二级指针,调整 p 指向的一级地址
// 地址是一个值,其对应了一个内存空间,是系统分配的,无法修改的,只能修改二级指针的指向
func insertHead(p **Student) {

    for i:=0;i<5;i++ {

        var stu = Student {

            Name: fmt.Sprintf("stu%d", i),
            Age: rand.Intn(100),
            Score:  rand.Float32()*100,
        }
        // 指向下个节点
        stu.next = *p
        *p = &stu
    }
}

// 遍历结构体元素
func Req(p *Student) {

    for p != nil {

        fmt.Println(*p)
        p = p.next
    }
}
 {

     stu4 56 30.091187 0xc000078420}
{

     stu3 25 15.651925 0xc0000783f0}
{

     stu2 81 68.682304 0xc0000783c0}
{

     stu1 47 43.77142 0xc000078390}
{

     stu0 81 94.05091 0xc000078360}
{

     stu1 20 100 <nil>}

重点:值类型如果想要外部数据和函数处理结果同步,两种方法:传参指针、return 返回值。单纯的调用函数体结果会被覆盖。

递归方式

 // 递归可能存在压栈问题,其消耗的内存资源要比二级指针高
func insertHead(p *Student,i int) {

    var stu Student = Student {

        Name: fmt.Sprintf("stu%d",i),
        Age: rand.Intn(100),
    }
    if i < 5 {

        i++
        stu.next = p
        p = &stu
        insertHead(p, i)
    } else {

        Req(p)  // 遍历元素
    }
}

2.5 插入元素

 package main

import (
    "fmt"
    "math/rand"
)

type Student struct {

    Name   string
    Age    int
    Score  float32
    next    *Student
}

func main() {

    // 创建一个头部结构体
    var stu1 *Student = &Student{

     }
    stu1.Name = "stu1"
    stu1.Age = 20
    stu1.Score = 100

    // 头部添加元素
    insertHead(&stu1)

    // 定义新节点
    var newNode *Student = &Student{

     }
    newNode.Name = "nowNode"
    newNode.Age = 35
    newNode.Score = 88
    // 调用指定位置插入新节点
    addNode(stu1, newNode)

    // 循环遍历输出
    Req(stu1)
}

// 头部插入节点
func insertHead(p **Student) {

    for i:=0;i<10;i++ {

        var stu = Student {

            Name: fmt.Sprintf("stu%d", i),
            Age: rand.Intn(100),
            Score:  rand.Float32()*100,
        }
        stu.next = *p
        *p = &stu
    }
}

// 中间插入节点
func addNode(p *Student, newNode *Student) {

    for p != nil {

        if p.Name == "stu6" {

            // 嫁接下一个节点
            newNode.next = p.next
            p.next = newNode
        }
        // 插入节点指向下个节点
        p = p.next
    }
}

// 遍历结构体元素
func Req(p *Student) {

    for p != nil {

        fmt.Println(*p)
        p = p.next
    }
}
 {

     stu9 37 21.855305 0xc0000b8510}
{

     stu8 11 29.310184 0xc0000b84e0}
{

     stu7 28 46.888985 0xc0000b84b0}
{

     stu6 62 38.06572 0xc0000b8570}
{

     nowNode 35 88 0xc0000b8480}
{

     stu5 94 81.36399 0xc0000b8450}
{

     stu4 56 30.091187 0xc0000b8420}
{

     stu3 25 15.651925 0xc0000b83f0}
{

     stu2 81 68.682304 0xc0000b83c0}
{

     stu1 47 43.77142 0xc0000b8390}
{

     stu0 81 94.05091 0xc0000b8360}
{

     stu1 20 100 <nil>}

2.6 删除元素

 package main

import (
    "fmt"
    "math/rand"
)

type Student struct {

    Name   string
    Age    int
    Score  float32
    next    *Student
}

func main() {

    // 创建一个头部结构体
    var stu1 *Student = &Student{

     }
    stu1.Name = "stu1"
    stu1.Age = 20
    stu1.Score = 100

    // 头部添加元素
    insertHead(&stu1)

    // 定义新节点
    var newNode *Student = &Student{

     }
    newNode.Name = "nowNode"
    newNode.Age = 35
    newNode.Score = 88
    // 调用指定位置插入新节点
    addNode(stu1, newNode)

    // 调用删除节点
    delNode(stu1)

    // 循环遍历输出
    Req(stu1)
}

// 头部插入节点
func insertHead(p **Student) {

    for i:=0;i<10;i++ {

        var stu = Student {

            Name: fmt.Sprintf("stu%d", i),
            Age: rand.Intn(100),
            Score:  rand.Float32()*100,
        }
        stu.next = *p
        *p = &stu
    }
}

// 中间插入节点
func addNode(p *Student, newNode *Student) {

    for p != nil {

        if p.Name == "stu6" {

            // 嫁接下一个节点
            newNode.next = p.next
            p.next = newNode
        }
        // 插入节点指向下个节点
        p = p.next
    }
}

// 删除节点
func delNode(p *Student) {

    var prev *Student = p
    for p != nil {

        if p.Name == "stu6" {

            prev.next = p.next
            break
        }
        // 平移
        // 前节点赋值
        prev = p
        // 后节点赋值
        p = p.next
    }
}

// 遍历结构体元素
func Req(p *Student) {

    for p != nil {

        fmt.Println(*p)
        p = p.next
    }
}
 {

     stu9 37 21.855305 0xc000078510}
{

     stu8 11 29.310184 0xc0000784e0}
{

     stu7 28 46.888985 0xc000078570}
{

     nowNode 35 88 0xc000078480}    // stu6 被删除
{

     stu5 94 81.36399 0xc000078450}
{

     stu4 56 30.091187 0xc000078420}
{

     stu3 25 15.651925 0xc0000783f0}
{

     stu2 81 68.682304 0xc0000783c0}
{

     stu1 47 43.77142 0xc000078390}
{

     stu0 81 94.05091 0xc000078360}
{

     stu1 20 100 <nil>}