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>}