zoukankan      html  css  js  c++  java
  • 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
    ", student.Name, student.isStudent, student.Age, student.score)
    (
    *student).isStudent = false fmt.Printf("struct声明:name=%s,isStudent=%t,age=%d,score=%d ", (*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
    ", &student.Name)   //Name: 0xc00000c0 60
        fmt.Printf("Age:%p
    ", &student.Age)     //Age:  0xc00000c0 70
        fmt.Printf("Score:%p
    ", &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
    ", student.Name, student.Age, student.Score)
        fmt.Printf("初始化2:Name=%s,Age=%d,Score=%f
    ", student2.Name, student2.Age, student2.Score)
        fmt.Printf("初始化3:Name=%s,Age=%d,Score=%.1f
    ", 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是一个很复杂的数据结构,甚至里边包含指针的指针的变量,理解起来确实蛮抽象的。

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

    
    
    
    

  • 相关阅读:
    [转]C#里 泛型Where和 new()的使用
    测试
    C#中的static、readonly与const的比较
    将字符串格式化变为两位
    在VS中对字段进行包装
    安装mysql的心得
    关于mysql数据库的乱码问题
    timestamp的两个属性:CURRENT_TIMESTAMP 和ON UPDATE CURRENT_TIMESTAMP
    解决向数据库mysql插入double数据小数点不显示问题
    JDBOOK
  • 原文地址:https://www.cnblogs.com/two-bees/p/12552232.html
Copyright © 2011-2022 走看看