• go语言中的结构体和组合思想入门示例


    参考《go语言从入门到进阶实战》和韩顺平《go语言核心编程》

    关于go中的面向对象

    1. Golang 也支持面向对象编程(OOP),但是和传统的面向对象编程有区别,并不是纯粹的面向对象语言。所以我们说Golang 支持面向对象编程特性是比较准确的。
    2. Golang 没有类(class),Go 语言的结构体(struct)和其它编程语言的类(class)有同等的地位,你可以理解Golang 是基于struct 来实现OOP 特性的。
    3. Golang 面向对象编程非常简洁,去掉了传统OOP 语言的继承、方法重载、构造函数和析构函数、隐藏的this 指针等等
    4. Golang 仍然有面向对象编程的继承,封装和多态的特性,只是实现的方式和其它OOP 语言不一样,比如继承:Golang 没有extends 关键字,继承是通过匿名字段来实现。
    5. Golang 面向对象(OOP)很优雅,OOP 本身就是语言类型系统(type system)的一部分,通过接口(interface)关联,耦合性低,也非常灵活。
      go中的一个结构体:
      在这里插入图片描述
    type 结构体名称struct {
    field1 type
    field2 type
    }
    
    • 1
    • 2
    • 3
    • 4

    这里要注意:
      如果结构体的字段类型是:指针,slice,和map的零值都是nil,即还没有分配空间/如果需要使用这样的字段,需要先make,才能使用.
      例如:

    type Person struct{
      Name string
      Age int
      Scores [5]float64
      ptr *int/指针
      slice []int//切片
      map1 map[string]string //map 
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    创建实例:

    var p1 Person
    p1.slice = make([]int,10)
    p1.slice[10]=100
    p1.map1 = make(map[string]string)
    p1.map1["k1"]="acb"
    
    • 1
    • 2
    • 3
    • 4
    • 5

    不同结构体变量的字段是独立,互不影响,一个结构体变量字段的更改,不影响另外一个,结构体是值类型,例子:

    var monster1 Monster
    monster1.Name="牛魔王"
    monster1.Age= 500
    monster2:=monster1//结构体是值类型,默认为值拷贝monster2.Name="青牛精"
    fmt.Print1n("monster:1=",monster1)//monster1={牛魔王500]
    fmt.Println(monster2=",monster2)//monster2={青牛精500}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    创建结构体变量和访问结构体字段

    有4方式:
    1.方式1-直接声明
    案例演示: var person Person
    2.方式2-{}
    案例演示: var person Person = Person{}
    3.方式3-&
    案例: var person *Person = new (Person)
    在这里插入图片描述
    4.方式4-{}
    案例: var person *Person = &Person{}
    同样是指针类型,赋值方式同3
    说明:

    1. 第3 种和第4 种方式返回的是结构体指针。
    2. 结构体指针访问字段的标准方式应该是:(*结构体指针).字段名,比如(*person).Name = “tom”
    3. 但go 做了一个简化,也支持结构体指针.字段名, 比如person.Name = “tom”。更加符合程序员使用的习惯,go 编译器底层对person.Name 做了转化(*person).Name。

    其中有一种最简单的方式,不用声明var,直接一个变量名,go会自动识别类型:

    abc := new (Person)
    
    • 1

    struct类型的内存分配机制

    在这里插入图片描述
    输出的结果是: p2.Name = tom p1.Name = 小明
    内存分配图:
    在这里插入图片描述
    对于如下代码:
    在这里插入图片描述
    输出:、在这里插入图片描述
    内存分配:
    在这里插入图片描述
    注意:

    1. 结构体的所有字段在内存中是连续的
    2. 结构体是用户单独定义的类型,和其它类型进行转换时需要有完全相同的字段(名字、个数和类型)
      在这里插入图片描述

    3)3) 结构体进行type 重新定义(相当于取别名),Golang 认为是新的数据类型,但是相互间可以强转
    在这里插入图片描述
    4) struct 的每个字段上,可以写上一个tag, 该tag 可以通过反射机制获取,常见的使用场景就是序列化和反序列化。
    举例:
    在这里插入图片描述

     //1.创建一个Monster变量
     monster:=Monster{"牛魔王"500,"芭蕉扇~"}
      //2.将monster变量序列化为json格式字串
     //json.Marshal函数中使用反射,
     jsonstr,err= json.Marshal(monster)
      if err !=nil
      fmt.Println("json处理错误",err)
      fmt.Println("jsonstr",string(jsonstr))
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    使用组合思想描述对象特性

    在面向对象思想中,实现对象关系需要使用“继承”特性。例如,人类不能飞行,鸟类可以飞行。人类和鸟类都可以继承自可行走类,但只有鸟类继承自飞行类。 面向对象的设计原则中也建议对象最好不要使用多重继承,有些面向对象语言从语言层面就禁止了多重继承,如C#和Java语言。鸟类同时继承自可行走类和飞行类,这显然是存在问题的。在面向对象思想中要正确地实现对象的多重特性,只能使用一些精巧的设计来补救。 Go语言的结构体内嵌特性就是一种组合特性,使用组合特性可以快速构建对象的不同特性

    例子:人和鸟的特性

    
    // 可飞行的
    type Flying struct{}
    
    func (f *Flying) Fly() {
    	fmt.Println("can fly")
    }
    
    // 可行走的
    type Walkable struct{}
    
    func (f *Walkable) Walk() {
    	fmt.Println("can calk")
    }
    
    // 人类
    type Human struct {
    	Walkable // 人类能行走
    }
    
    // 鸟类
    type Bird struct {
    	Walkable // 鸟类能行走
    	Flying   // 鸟类能飞行
    }
    
    func main() {
    
    	// 实例化鸟类
    	b := new(Bird)
    	fmt.Println("Bird: ")
    	b.Fly()
    	b.Walk()
    
    	// 实例化人类
    	h := new(Human)
    	fmt.Println("Human: ")
    	h.Walk()
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    在这里插入图片描述

    初始化结构体内嵌

    结构体内嵌初始化时,将结构体内嵌的类型作为字段名像普通结构体一样进行初始化,详细实现过程:

    package main
    import "fmt"
    // 车轮
    type Wheel struct {
    	Size int
    }
    // 引擎
    type Engine struct {
    	Power int    // 功率
    	Type  string // 类型
    }
    // 车
    type Car struct {
    	Wheel
    	Engine
    }
    func main() {
    	c := Car{
    		// 初始化轮子
    		Wheel: Wheel{
    			Size: 18,
    		},
    		// 初始化引擎
    		Engine: Engine{
    			Type:  "1.4T",
    			Power: 143,
    		},
    	}
    	fmt.Printf("%+v\n", c)
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    输出:
    {Wheel:{Size:18} Engine:{Power:143 Type:1.4T}}

    初始化匿名结构体:
    有时考虑编写代码的便利性,会将结构体直接定义在嵌入的结构体中。也就是说,结构体的定义不会被外部引用到。在初始化这个被嵌入的结构体时,就需要再次声明结构才能赋予数据。

    package main
    
    import "fmt"
    
    // 车轮
    type Wheel struct {
    	Size int
    }
    // 车
    type Car struct {
    	Wheel
    	// 引擎
    	Engine struct {
    		Power int    // 功率
    		Type  string // 类型
    	}
    }
    
    func main() {
    	c := Car{
    		// 初始化轮子
    		Wheel: Wheel{
    			Size: 18,
    		},
    		// 初始化引擎
    		Engine: struct {
    			Power int
    			Type  string
    		}{
    			Type:  "1.4T",
    			Power: 143,
    		},
    	}
    	fmt.Printf("%+v\n", c)
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    成员名字冲突

    嵌入结构体内部可能拥有相同的成员名,成员重名时会发生什么?下面通过例子来讲解。

    01        package main
    02        
    03        import (
    04                        "fmt"
    05        )
    06        
    07        type A struct {
    08                        a int
    09        }
    10        
    11        type B struct {
    12                        a int
    13        }
    14        
    15        type C struct {
    16                        A
    17                        B
    18        }
    19        
    20        func main() {
    21                        c := &C{}
    22                        c.A.a = 1
    23                        fmt.Println(c)
    24        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    代码说明如下: ·第7行和第11行分别定义了两个拥有a int字段的结构体。 ·第15行的结构体嵌入了A和B的结构体。 ·第21行实例化C结构体。 ·第22行按常规的方法,访问嵌入结构体A中的a字段,并赋值1。 ·第23行可以正常输出实例化C结构体。 接着,将第22行修改为如下代码:

      func main() {
            c := &C{}
            c.a = 1
            fmt.Println(c)
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    此时再编译运行,编译器报错:编译器告知C的选择器a引起歧义,也就是说,编译器无法决定将1赋给C中的A还是B里的字段a。

  • 相关阅读:
    数组题目总结 ---- 田忌赛马
    C练习题_13
    实验笔记之——DPVO(Deep Patch Visual Odometry)
    为什么加上<log4j2.version>配置就可以更新log4j2的版本?
    项目管理之5W2H项目定义法
    cmake 之add_definitions使用误区
    D. Radio Towers(斐波那契+逆元)
    全国测绘法宣传日 | 强化国土意识,保障数据安全
    c 语言基础题目:L1-029 是不是太胖了
    Autosar架构介绍:总目录
  • 原文地址:https://blog.csdn.net/qq_41358574/article/details/125914917