• golang(5):struct & 链表 & 二叉树 & 接口


     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) 其中 23 返回的都是指向结构体的指针,访问形式如下:
        // 标准形式:
        (*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
    ",&stu.Name)    // 打印地址
        fmt.Printf("Age:%p
    ",&stu.Age)
        fmt.Printf("score:%p
    ",&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
    ",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
    ",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
  • 相关阅读:
    linux Linux下USB无线网卡WL167G、TLWN321G驱动安装过程详解
    linux CW_EPPC_8.8在linux下的安装和卸载
    linux Linux diff与patch的深入分析
    linux 嵌入式2.6.37wifivnt6656移植驱动
    linux 华为ET128 &中兴ZTE MU351移动TDSCDMA G3上网卡 .
    linux CW8.8 编译 提示缺少libstdc++.so.5的error
    linux codewarrior 8.8 for powerpc 在win7经常崩溃的解决方法
    linux ubuntu 安装smb共享文件夹
    [zt]c++builder调用VC的dll以及VC调用c++builder的dll
    电子认证服务管理办法
  • 原文地址:https://www.cnblogs.com/neozheng/p/11247866.html
Copyright © 2020-2023  润新知