golang - 结构体、链表、二叉树、别名、构造函数

在golang中并没有class的概念,如果真要说起来就是struct了。

struct是一种自定义复杂的类型结构,可以包含多个字段(属性),可以定义方法,可以嵌套,而且struct是值类型。

与函数一样,struct如果要被外部访问到:结构体的名称和其中的字段首字母也需要大写。

声明
struct包含三种声明方式,如实例代码:

    //定义一个学生结构体,有点类似其他语言的类
    type Students struct {
        isStudent bool
        Name      string
        Age       int
        score     int
    }
    //var student Students //声明方式1
    //var student *Students = new(Students) //声明方式2
    var student *Students = &Students{} //声明方式3
    student.Name = "zhangsan"
    student.isStudent = true //赋值
    student.Age = 23
    student.score = 90
    fmt.Printf("struct声明:name=%s,isStudent=%t,age=%d,score=%d\n", student.Name, student.isStudent, student.Age, student.score)
(
*student).isStudent = false fmt.Printf("struct声明:name=%s,isStudent=%t,age=%d,score=%d\n", (*student).Name, (*student).isStudent, (*student).Age, (*student).score)

返回

struct声明:name=zhangsan,isStudent=true,age=23,score=90
struct声明:name=zhangsan,isStudent=false,age=23,score=90

值得注意的是,在go中(*student).与student.的两种访问形式的结果是一样的,区别在于后者是前者的简化版。

同时上面的声明方式2new的用法还是一样,返回指针,被分配的内存空间不需要自己释放,只管使用即可。

内存布局

struct的内存布局中的所有字段在内存里是连续的

    type Students struct {
        Name  string
        Age   int
        Score float32
    }
    var student Students
    student.Name = "zhangsan"
    student.Age = 23
    student.Score = 90

    fmt.Println(student)
    fmt.Printf("Name:%p\n", &student.Name)   //Name: 0xc00000c0 60
    fmt.Printf("Age:%p\n", &student.Age)     //Age:  0xc00000c0 70
    fmt.Printf("Score:%p\n", &student.Score) //Score:0xc00000c0 78

返回

{zhangsan 23 90}
Name:0xc00000c0a0
Age:   0xc00000c0b0
Score:0xc00000c0b8

a0、b0、b8,由此可见,struct中的所有字段属性都是根据字节大小连续排列的。

 初始化

三种初始化的方式,变量和指针:

    type Students struct {
        Name  string
        Age   int
        Score float32
    }
    var student = Students{
        Name: "tom",
        Age:  2,
    }

    var student2 Students
    student2.Name = "zhangsan"
    student2.Age = 23
    student2.Score = 90

    var student3 *Students = &Students{
        Name:  "tom",
        Age:   2,
        Score: 90.2,
    }

    fmt.Printf("初始化1:Name=%s,Age=%d,Score=%f\n", student.Name, student.Age, student.Score)
    fmt.Printf("初始化2:Name=%s,Age=%d,Score=%f\n", student2.Name, student2.Age, student2.Score)
    fmt.Printf("初始化3:Name=%s,Age=%d,Score=%.1f\n", student3.Name, student3.Age, student3.Score)

返回

初始化1:Name=tom,Age=2,Score=0.000000
初始化2:Name=zhangsan,Age=23,Score=90.000000
初始化3:Name=tom,Age=2,Score=90.2

与前面声明的一样,(*student).Name与student.Name是等同的,所以简便起见student.Name

链表

链表是每个节点包含下一个节点的地址(通常使用的是指针字段),这样把所有的节点串联起来,形成一个长度不固定、复杂的链式结构。

链表头:链表中的第一个节点

链表又分为单链表和双链表即循环单双链表:

单链表是每个节点只存在一个指针字段,只能从头向后走。

双链表是每个节点存在两个指针字段,一个指向后一个节点;另一个指向前一个节点。

循环单双链表是链表的尾节点指向链表头。

 

 

链表的常规操作:

链表尾部插入法:

type Students struct {
    Name  string
    Age   int
    Score float32
    next  *Students
}

func() {
        fmt.Println("链表尾部插入法:", )
        //定义一个链表
        //创建一个头节点
        var head Students
        head.Name = "head"
        head.Age = 18
        head.Score = 100

        //定义一个尾节点
        var student1 Students
        student1.Name = "student1"
        student1.Age = 18
        student1.Score = 100

        head.next = &student1

        var student2 Students
        student2.Name = "student2"
        student2.Age = 18
        student2.Score = 100

        trans(&head) //传进去一个指向同一个内存空间的地址
        student1.next = &student2

        trans(&head) //传进去一个指向同一个内存空间的地址
    }()

//模块化遍历链表
func trans(p *Students) {
    for p != nil {
        fmt.Println(*p)
        if p.next == nil {
            break
        }
        p = p.next
    }
    fmt.Println()
}

返回

链表尾部插入法:
{head 18 100 0xc00008c030}
{student1 18 100 <nil>}

{head 18 100 0xc00008c030}
{student1 18 100 0xc00008c060}
{student2 18 100 <nil>}

链表尾部遍历插入法:

type Students struct {
    Name  string
    Age   int
    Score float32
    next  *Students
}

    func() {
        fmt.Println("链表尾部遍历插入法:", )
        //定义一个链表
        //创建一个头节点
        var head Students
        head.Name = "head"
        head.Age = 18
        head.Score = 100

        insetTail(&head)

        trans(&head)
    }()

//尾部遍历插入链表
func insetTail(p *Students) {
    var tail = p //记录尾节点,通过尾节点来新增节点
    for i := 0; i < 10; i++ {
        student := &Students{
            Name:  fmt.Sprintf("student%d", i),
            Age:   rand.Intn(100),
            Score: rand.Float32() * 100,
        }
        tail.next = student
        tail = student //重新定义尾节点
    }
}

返回

链表尾部遍历插入法:
{head 18 100 0xc000078180}
{student0 81 94.05091 0xc0000781b0}
{student1 47 43.77142 0xc0000781e0}
{student2 81 68.682304 0xc000078210}
{student3 25 15.651925 0xc000078240}
{student4 56 30.091187 0xc000078270}
{student5 94 81.36399 0xc0000782a0}
{student6 62 38.06572 0xc0000782d0}
{student7 28 46.888985 0xc000078300}
{student8 11 29.310184 0xc000078330}
{student9 37 21.855305 <nil>}

链表头不遍历插入法:

方式一:变量声明

//定义一个链表结构体
type Students struct {
    Name  string
    Age   int
    Score float32
    next  *Students
}

func() {
        fmt.Println("链表头部遍历插入法:", )
        //定义一个链表
        //创建一个头节点
        var head Students //方式一
        head.Name = "head"
        head.Age = 18
        head.Score = 100

        for i := 0; i < 10; i++ {
            student := Students{
                Name:  fmt.Sprintf("student%d", i),
                Age:   rand.Intn(100),
                Score: rand.Float32() * 100,
            }
            student.next = &head //方式一
            head = student //方式一,错误
        }

        trans(&head) //方式一
    }()

//模块化遍历链表
func trans(p *Students) {
    for p != nil {
        fmt.Println(*p)
        if p.next == nil {
            break
        }
        p = p.next
        time.Sleep(time.Second)
    }
    fmt.Println()
}

返回

链表头部遍历插入法:
{student9 37 21.855305 0xc00006c180}
{student9 37 21.855305 0xc00006c180}
{student9 37 21.855305 0xc00006c180}
{student9 37 21.855305 0xc00006c180}
{student9 37 21.855305 0xc00006c180}
{student9 37 21.855305 0xc00006c180}
^Csignal: interrupt

通过返回可以看出,链表只有一个节点,并且地址都是一样,why?明明有循环赋值呀。

这是因为:上面代码这里(head = student)是变量赋值操作,其实就是copy的过程,copy之后值都是一样的,如herd.next=>student.next=>student.next=>.....,这样循环下去对应的都是student自己,所以会造成死循环。

而且,当遍历这个链表时就会死循环最后一个。可见变量copy并不能改变指向,这样也就无法形成一个完整的链表结构。
所以,要用指针来改变指向进行头部插入。如下面的方式二、三

方式二、三:

//定义一个链表结构体
type Students struct {
    Name  string
    Age   int
    Score float32
    next  *Students
}

    func() {
        fmt.Println("链表头部遍历插入法:", )
        //定义一个链表
        //创建一个头节点
        var head *Students = &Students{} //方式二
        //var head *Students = new(Students) //方式三
        head.Name = "head"
        head.Age = 18
        head.Score = 100

        for i := 0; i < 10; i++ {
            student := Students{
                Name:  fmt.Sprintf("student%d", i),
                Age:   rand.Intn(100),
                Score: rand.Float32() * 100,
            }
            student.next = head //方式二、三
            head = &student //方式二、三//使用指针的话,改变指向就ok。
        }
        trans(head) //方式二、三

    }()

//模块化遍历链表
func trans(p *Students) {
    for p != nil {
        fmt.Println(*p)
        if p.next == nil {
            break
        }
        p = p.next
    }
    fmt.Println()
}

返回

链表头部遍历插入法:
{student9 37 21.855305 0xc00008c1e0}
{student8 11 29.310184 0xc00008c1b0}
{student7 28 46.888985 0xc00008c180}
{student6 62 38.06572 0xc00008c150}
{student5 94 81.36399 0xc00008c120}
{student4 56 30.091187 0xc00008c0f0}
{student3 25 15.651925 0xc00008c0c0}
{student2 81 68.682304 0xc00008c090}
{student1 47 43.77142 0xc00008c060}
{student0 81 94.05091 0xc00008c030}
{head 18 100 <nil>}

这样就可以进行头部遍历插入了。

删除指定节点:

//定义一个链表结构体
type Students struct {
    Name  string
    Age   int
    Score float32
    next  *Students
}

    func() {
        fmt.Println("链表头部遍历插入法:", )
        //定义一个链表
        //创建一个头节点
        var head *Students = &Students{} //方式二
        head.Name = "head"
        head.Age = 18
        head.Score = 100

        for i := 0; i < 10; i++ {
            student := Students{
                Name:  fmt.Sprintf("student%d", i),
                Age:   rand.Intn(100),
                Score: rand.Float32() * 100,
            }
            student.next = head //方式二、三
            head = &student //方式二、三//使用指针的话,改变指向就ok。
        }

        trans(head) //方式二、三

        //----------------------删除指定节点----------------------
        deleteNode(head)

        trans(head)
    }()

//模块化遍历链表
func trans(p *Students) {
    for p != nil {
        fmt.Println(*p)
        if p.next == nil {
            break
        }
        p = p.next
    }
    fmt.Println()
}

返回

链表头部遍历插入法:
{student9 37 21.855305 0xc000078330}
{student8 11 29.310184 0xc000078300}
{student7 28 46.888985 0xc0000782d0}
{student6 62 38.06572 0xc0000782a0}
{student5 94 81.36399 0xc000078270}
{student4 56 30.091187 0xc000078240}
{student3 25 15.651925 0xc000078210}
{student2 81 68.682304 0xc0000781e0}
{student1 47 43.77142 0xc0000781b0}
{student0 81 94.05091 0xc000078180}
{head 18 100 <nil>}

链表删除指定节点:
{student9 37 21.855305 0xc000078330}
{student8 11 29.310184 0xc000078300}
{student7 28 46.888985 0xc0000782a0}
{student5 94 81.36399 0xc000078270}
{student4 56 30.091187 0xc000078240}
{student3 25 15.651925 0xc000078210}
{student2 81 68.682304 0xc0000781e0}
{student1 47 43.77142 0xc0000781b0}
{student0 81 94.05091 0xc000078180}
{head 18 100 <nil>}

删除后插入节点:

//定义一个链表结构体
type Students struct {
    Name  string
    Age   int
    Score float32
    next  *Students
}

    func() {
        fmt.Println("链表头部遍历插入法:", )
        //定义一个链表
        //创建一个头节点
        var head *Students = &Students{} //方式二
        head.Name = "head"
        head.Age = 18
        head.Score = 100

        for i := 0; i < 10; i++ {
            student := Students{
                Name:  fmt.Sprintf("student%d", i),
                Age:   rand.Intn(100),
                Score: rand.Float32() * 100,
            }
            student.next = head //方式二、三
            head = &student //方式二、三//使用指针的话,改变指向就ok。
        }

        trans(head) //方式二、三

        //----------------------删除指定节点----------------------
        deleteNode(head)
        
        //----------------------再插入一个节点---------------------------
        var newNode *Students = new(Students)
        newNode.Name = "newNode"
        newNode.Score = 1213
        newNode.Age = 13
        addNode(head, newNode)

        trans(head)
    }()

//模块化遍历链表
func trans(p *Students) {
    for p != nil {
        fmt.Println(*p)
        if p.next == nil {
            break
        }
        p = p.next
    }
    fmt.Println()
}

func deleteNode(p *Students) {
    fmt.Println("链表删除指定节点:", )
    var prev *Students = p //存放上一个链表节点
    for p.next != nil {
        if p.Name == "student6" { //开始删除
            prev.next = p.next //修改上一个节点next
            break
        }
        //没有找到
        prev = p   //上个节点代存点,重新赋值prev
        p = p.next //指向下一个节点
    }
    //删除头节点会有问题
}

func addNode(p *Students, newNode *Students) {
    for p.next != nil {
        if p.Name == "student9" { //开始添加
            newNode.next = p.next //新节点需要和后面的节点连接起来
            p.next = newNode
            break
        }
        p = p.next
    }
}

 返回

链表头部遍历插入法:
{student9 37 21.855305 0xc000076330}
{student8 11 29.310184 0xc000076300}
{student7 28 46.888985 0xc0000762d0}
{student6 62 38.06572 0xc0000762a0}
{student5 94 81.36399 0xc000076270}
{student4 56 30.091187 0xc000076240}
{student3 25 15.651925 0xc000076210}
{student2 81 68.682304 0xc0000761e0}
{student1 47 43.77142 0xc0000761b0}
{student0 81 94.05091 0xc000076180}
{head 18 100 <nil>}

链表删除指定节点:
链表添加指定节点:
{student9 37 21.855305 0xc0000765a0}
{newNode 13 1213 0xc000076330}
{student8 11 29.310184 0xc000076300}
{student7 28 46.888985 0xc0000762a0}
{student5 94 81.36399 0xc000076270}
{student4 56 30.091187 0xc000076240}
{student3 25 15.651925 0xc000076210}
{student2 81 68.682304 0xc0000761e0}
{student1 47 43.77142 0xc0000761b0}
{student0 81 94.05091 0xc000076180}
{head 18 100 <nil>}

二叉树

如果每个节点有两个指针分别用来指向左子树和右子树,就是二叉树

type name struct {
    Name  string
    Age   int
    left  *name 
    right *name 
}

一个简单的栗子来实现二叉树:

 

 

 

//定义二叉树结构体
type Tree struct {
    Name  string
    Age   int
    Score float32
    left  *Tree
    right *Tree
}
func test5() {
    //定义根节点
    var root *Tree = new(Tree)
    root.Name = "stu01"
    root.Age = 13
    root.Score = 100
    //root.left = nil
    //root.right = nil

    //定义左子树
    var left1 *Tree = new(Tree)
    left1.Name = "stu02"
    left1.Age = 13
    left1.Score = 100

    root.left = left1

    //定义右子树
    var right1 *Tree = new(Tree)
    right1.Name = "stu04"
    right1.Age = 13
    right1.Score = 100

    root.right = right1

    var left2 *Tree = new(Tree)
    left2.Name = "stu03"
    left2.Age = 13
    left2.Score = 100

    left1.left = left2
    transTree(root)
}
//模块化递归遍历二叉树
func transTree(root *Tree) {
    if root == nil {
        return
    }

    //前序遍历:先遍历根节点,再遍历左右子树
    fmt.Println(root)
    transTree(root.left)
    transTree(root.right)

    //中序遍历:先遍历左子树,再遍历根节点、右子树
    //transTree(root.left)
    //fmt.Println(root)
    //transTree(root.right)

    //后序遍历:先遍历左子树,再遍历右子树,最后根节点
    //transTree(root.left)
    //transTree(root.right)
    //fmt.Println(root)
}

返回

&{stu01 13 100 0xc0000741b0 0xc0000741e0}
&{stu02 13 100 0xc000074210 <nil>}
&{stu03 13 100 <nil> <nil>}
&{stu04 13 100 <nil> <nil>}

使用递归方式,可以很简便的遍历出二叉树结构。

根据返回,可以看出是根据一定的顺序来遍历的,这里就要说下三种不同的遍历方式:

前序遍历:先遍历根节点,再遍历左右子树

&{stu01 13 100 0xc0000601b0 0xc0000601e0}
&{stu02 13 100 0xc000060210 <nil>}
&{stu03 13 100 <nil> <nil>}
&{stu04 13 100 <nil> <nil>}

中序遍历:先遍历左子树,再遍历根节点、右子树

&{stu03 13 100 <nil> <nil>}
&{stu02 13 100 0xc000078210 <nil>}
&{stu01 13 100 0xc0000781b0 0xc0000781e0}
&{stu04 13 100 <nil> <nil>}

后序遍历:先遍历左子树,再遍历右子树,最后根节点

&{stu03 13 100 <nil> <nil>}
&{stu02 13 100 0xc000078210 <nil>}
&{stu04 13 100 <nil> <nil>}
&{stu01 13 100 0xc0000781b0 0xc0000781e0}

别名

在goang中是可以为类型定义别名的,使用的方式还是和别定义的一样。但是定义后的类型和原类型不同,只能使用原来类型强制转换进行赋值。

    type integer int    //定义别名
    var a integer = 100
    var b int = 19
    b = int(a)
    fmt.Println(b)

    type alias struct {    //定义一个结构体
        Number int
    }
    type aliass alias    //定义别名
    var aa alias
    aa = alias{29}
    fmt.Println(aa)
    var bb aliass
    aa = alias(bb)
    fmt.Println(aa)

返回

100
{29}
{0}

构造函数

在golang中的struct是没有构造函数的,一般可以使用工厂模式来解决这个问题。

 

为类型定义别名, 注意: 定义后的类型和原来的类型不同, 只能使用原来类型强制转换进行赋值

以上就是golang中的结构体,struct是一个很复杂的数据结构,甚至里边包含指针的指针的变量,理解起来确实蛮抽象的。

目前个人理解整理就是这么多,有问题的地方还望指出


 

 

posted @ 2020-05-22 13:34  谦逊的铅笔  阅读(485)  评论(0编辑  收藏  举报