• GO结构体


    1. 结构体

    Go语言可以通过自定义的方式形成新的类型,结构体就是这些类型中的一种复合类型,结构体是由零个或多个任意类型的值聚合成的实体,每个值都可以称为结构体的成员。

    结构体成员也可以称为“字段”,这些字段有以下特性:

    • 字段拥有自己的类型和值;
    • 字段名必须唯一;
    • 字段的类型也可以是结构体,甚至是字段所在结构体的类型。

    使用关键字 type 可以将各种基本类型定义为自定义类型,基本类型包括整型、字符串、布尔等。结构体是一种复合的基本类型,通过 type 定义为自定义类型后,使结构体更便于使用。

    结构体的定义格式如下:

    1. type 类型名 struct {
    2. 字段1 字段1类型
    3. 字段2 字段2类型
    4. }

    • 类型名:标识自定义结构体的名称,在同一个包内不能重复。
    • struct{}:表示结构体类型,type 类型名 struct{}可以理解为将 struct{} 结构体定义为类型名的类型。
    • 字段1、字段2……:表示结构体字段名,结构体中的字段名必须唯一。
    • 字段1类型、字段2类型……:表示结构体各个字段的类型。

    示例:

    1. type Point struct {
    2. X int
    3. Y int
    4. }

    颜色的红、绿、蓝 3 个分量可以使用 byte 类型:

    1. type Color struct {
    2. R, G, B byte
    3. }

    结构体的定义只是一种内存布局的描述,只有当结构体实例化时,才会真正地分配内存

    1.1 实例化

    实例化就是根据结构体定义的格式创建一份与格式一致的内存区域,结构体实例与实例间的内存是完全独立的。

    基本的实例化形式:

    结构体本身是一种类型,可以像整型、字符串等类型一样,以 var 的方式声明结构体即可完成实例化。

    var ins T

    1

    T 为结构体类型,ins 为结构体的实例。

    1. type Index struct {
    2. x int
    3. y int
    4. }
    5. func main() {
    6. var myIndex Index
    7. //使用.来访问结构体的成员变量,结构体成员变量的赋值方法与普通变量一致。
    8. myIndex.y = 10
    9. myIndex.x = 20
    10. fmt.Println(myIndex) //{20 10}
    11. }
    //如果不赋值 结构体中的变量会使用零值初始化
    1. type Point struct {
    2. X int
    3. Y int
    4. }
    5. func main() {
    6. //可以使用
    7. var p = Point{
    8. X: 1,
    9. Y: 2,
    10. }
    11. var p = Point{
    12. 1,
    13. 2,
    14. }
    15. fmt.Printf("%v,x=%d,y=%d",p,p.X,p.Y )
    16. }

    创建指针类型的结构体:

    Go语言中,还可以使用 new 关键字对类型(包括结构体、整型、浮点数、字符串等)进行实例化,结构体在实例化后会形成指针类型的结构体。

    ins := new(T)

    1

    • T 为类型,可以是结构体、整型、字符串等。
    • ins:T 类型被实例化后保存到 ins 变量中,ins 的类型为 *T,属于指针。

    下面的例子定义了一个玩家(Player)的结构,玩家拥有名字、生命值和魔法值:

    1. type Player struct {
    2. name string
    3. hp int
    4. power int
    5. }
    6. play := new(Player)
    7. play.power = 10
    8. play.name = "小米"
    9. play.hp = 150
    10. fmt.Println(*play) //{小米 150 10}

    取结构体的地址实例化:

    在Go语言中,对结构体进行&取地址操作时,视为对该类型进行一次 new 的实例化操作,取地址格式如下:

    ins := &T{}

    其中:

    • T 表示结构体类型。
    • ins 为结构体的实例,类型为 *T,是指针类型。
    1. type Commend struct {
    2. Name string
    3. Var *int
    4. Commend string
    5. }
    6. func newCommend(name string, Var *int, commend string) *Commend {
    7. return &Commend{
    8. name,
    9. Var,
    10. commend,
    11. }
    12. }
    13. var version = 1
    14. func main() {
    15. m := newCommend("lct", &version, "test")
    16. fmt.Println(*m) //{lct 0x5d4318 test}
    17. }

    1.2 匿名结构体

    匿名结构体没有类型名称,无须通过 type 关键字定义就可以直接使用。

    1. ins := struct {
    2. // 匿名结构体字段定义
    3. 字段1 字段类型1
    4. 字段2 字段类型2
    5. }{
    6. // 字段值初始化
    7. 初始化字段1: 字段1的值,
    8. 初始化字段2: 字段2的值,
    9. }

    • 字段1、字段2……:结构体定义的字段名。
    • 初始化字段1、初始化字段2……:结构体初始化时的字段名,可选择性地对字段初始化。
    • 字段类型1、字段类型2……:结构体定义字段的类型。
    • 字段1的值、字段2的值……:结构体初始化字段的初始值。

    1. // 打印消息类型, 传入匿名结构体
    2. func printMsg(msg *struct {
    3. id int
    4. data string
    5. }) {
    6. fmt.Printf("type :%T,msg:%v", msg, msg)
    7. }
    8. func main() {
    9. msg := &struct {
    10. id int
    11. data string
    12. }{
    13. 21,
    14. "hello",
    15. }
    16. printMsg(msg) //type :*struct { id int; data string },msg:&{21 hello}
    17. }

    2. 方法

    在Go语言中,结构体就像是类的一种简化形式,那么类的方法在哪里呢?

    在Go语言中有一个概念,它和方法有着同样的名字,并且大体上意思相同,Go 方法是作用在接收器(receiver)上的一个函数,接收器是某种类型的变量,因此方法是一种特殊类型的函数。

    接收器类型可以是(几乎)任何类型,不仅仅是结构体类型,任何类型都可以有方法,甚至可以是函数类型,可以是 int、bool、string 或数组的别名类型,但是接收器不能是一个接口类型,因为接口是一个抽象定义,而方法却是具体实现,如果这样做了就会引发一个编译错误invalid receiver type…

    接收器也不能是一个指针类型,但是它可以是任何其他允许类型的指针。

    一个类型加上它的方法等价于面向对象中的一个类

    在Go语言中,类型的代码和绑定在它上面的方法的代码可以不放置在一起,它们可以存在不同的源文件中,唯一的要求是它们必须是同一个包的。

    类型 T(或 T)上的所有方法的集合叫做类型 T(或 T)的方法集。

    在面向对象的语言中,类拥有的方法一般被理解为类可以做的事情。在Go语言中“方法”的概念与其他语言一致,只是Go语言建立的“接收器”强调方法的作用对象是接收器,也就是类实例,而函数没有作用对象。

    为结构体添加方法:

    需求:将物品放入背包

    面向对象的写法:

    将背包做为一个对象,将物品放入背包的过程作为“方法”

    1. type Bag struct {
    2. items []int
    3. }
    4. func (b *Bag) Insert(item int) {
    5. b.items = append(b.items, item)
    6. }
    7. func main() {
    8. var bag Bag
    9. bag.Insert(10)
    10. fmt.Println(bag.items) //[10]
    11. }

    (b*Bag) 表示接收器,即 Insert 作用的对象实例。每个方法只能有一个接收器

    2.1 接收器

    接收器的格式如下:

    1. func (接收器变量 接收器类型) 方法名(参数列表) (返回参数) {
    2. 函数体
    3. }
    • 接收器变量:接收器中的参数变量名在命名时,官方建议使用接收器类型名的第一个小写字母,而不是 self、this 之类的命名。例如,Socket 类型的接收器变量应该命名为 s,Connector 类型的接收器变量应该命名为 c 等。
    • 接收器类型:接收器类型和参数类似,可以是指针类型和非指针类型。
    • 方法名、参数列表、返回参数:格式与函数定义一致。

    接收器根据接收器的类型可以分为指针接收器、非指针接收器,两种接收器在使用时会产生不同的效果,根据效果的不同,两种接收器会被用于不同性能和功能要求的代码中。

    指针类型的接收器:

    指针类型的接收器由一个结构体的指针组成,更接近于面向对象中的 this 或者 self。

    由于指针的特性,调用方法时,修改接收器指针的任意成员变量,在方法结束后,修改都是有效的。

    示例:

    使用结构体定义一个属性(Property),为属性添加 SetValue() 方法以封装设置属性的过程,通过属性的 Value() 方法可以重新获得属性的数值,使用属性时,通过 SetValue() 方法的调用,可以达成修改属性值的效果:

    1. type Property struct {
    2. val int
    3. }
    4. // set val
    5. func (p *Property) setVal(val int) {
    6. p.val = val
    7. }
    8. //get val
    9. func (p *Property) getVal() int {
    10. return p.val
    11. }
    12. func main() {
    13. p := new(Property)
    14. p.setVal(20)
    15. fmt.Println(p.getVal()) //20
    16. }

    非指针类型的接收器:

    当方法作用于非指针接收器时,Go语言会在代码运行时将接收器的值复制一份,在非指针接收器的方法中可以获取接收器的成员值,但修改后无效。

    点(Point)使用结构体描述时,为点添加 Add() 方法,这个方法不能修改 Point 的成员 X、Y 变量,而是在计算后返回新的 Point 对象,Point 属于小内存对象,在函数返回值的复制过程中可以极大地提高代码运行效率:

    1. package main
    2. import (
    3. "fmt"
    4. )
    5. // 定义点结构
    6. type Point struct {
    7. X int
    8. Y int
    9. }
    10. // 非指针接收器的加方法
    11. func (p Point) Add(other Point) Point {
    12. // 成员值与参数相加后返回新的结构
    13. return Point{p.X + other.X, p.Y + other.Y}
    14. }
    15. func main() {
    16. // 初始化点
    17. p1 := Point{1, 1}
    18. p2 := Point{2, 2}
    19. // 与另外一个点相加
    20. result := p1.Add(p2)
    21. // 输出结果
    22. fmt.Println(result)
    23. }

    在计算机中,小对象由于值复制时的速度较快,所以适合使用非指针接收器,大对象因为复制性能较低,适合使用指针接收器,在接收器和参数间传递时不进行复制,只是传递指针

    4. 给任意类型添加方法

    Go语言可以对任何类型添加方法,给一种类型添加方法就像给结构体添加方法一样,因为结构体也是一种类型。

    为基本类型添加方法:

    在Go语言中,使用 type 关键字可以定义出新的自定义类型,之后就可以为自定义类型添加各种方法了。我们习惯于使用面向过程的方式判断一个值是否为 0,例如:

    1. if v == 0 {
    2. // v等于0
    3. }

    如果将 v 当做整型对象,那么判断 v 值就可以增加一个 IsZero() 方法,通过这个方法就可以判断 v 值是否为 0,例如:

    1. if v.IsZero() {
    2. // v等于0
    3. }

    为基本类型添加方法的详细实现流程如下:

    1. package main
    2. import (
    3. "fmt"
    4. )
    5. // 将int定义为MyInt类型
    6. type MyInt int
    7. // 为MyInt添加IsZero()方法
    8. func (m MyInt) IsZero() bool {
    9. return m == 0
    10. }
    11. // 为MyInt添加Add()方法
    12. func (m MyInt) Add(other int) int {
    13. return other + int(m)
    14. }
    15. func main() {
    16. var b MyInt
    17. fmt.Println(b.IsZero())
    18. b = 1
    19. fmt.Println(b.Add(2))
    20. }

    5. 匿名字段

    结构体可以包含一个或多个匿名(或内嵌)字段,即这些字段没有显式的名字,只有字段的类型是必须的,此时类型也就是字段的名字。

    匿名字段本身可以是一个结构体类型,即结构体可以包含内嵌结构体。

    Go语言中的继承是通过内嵌或组合来实现的,所以可以说,在Go语言中,相比较于继承,组合更受青睐。

    1. type User struct {
    2. id int
    3. name string
    4. }
    5. type Manager struct {
    6. User
    7. }
    8. func (u *User) ToString() string {
    9. return fmt.Sprintf("User:%p,%+v", u, u)
    10. }
    11. func main() {
    12. m := Manager{
    13. User{1, "lct"},
    14. }
    15. fmt.Printf("manger:%p\n", &m) //manger:0xc000094030
    16. fmt.Println(m.ToString()) //User:0xc000094030,&{id:1 name:lct}
    17. }

    有点类似于重写

    1. type User struct {
    2. id int
    3. name string
    4. }
    5. type Manager struct {
    6. User
    7. title string
    8. }
    9. func (u *User) ToString() string {
    10. return fmt.Sprintf("User:%p,%+v", u, u)
    11. }
    12. func (m *Manager) ToString() string {
    13. return fmt.Sprintf("Manger:%p,%+v", m, m)
    14. }
    15. func main() {
    16. m := Manager{User{
    17. 1,
    18. "lct",
    19. }, "hahah"}
    20. fmt.Println(m.ToString()) //Manger:0xc00001a0f0,&{User:{id:1 name:lct} title:hahah}
    21. fmt.Println(m.User.ToString()) //User:0xc00001a0f0,&{id:1 name:lct}
    22. }
  • 相关阅读:
    集成学习(一):简述集成学习
    python(11)例题重写
    关于 Rancher 与防火墙 firewalld 的一些注意事项
    消息队列-Kafka-消费方如何分区与分区重平衡
    Matlab绘图函数subplot、tiledlayout、plot和scatter
    RK3568开发笔记(一):瑞芯微RK3568芯片介绍,入手开发板的核心板介绍
    [运维|数据库] mysql的charset与PostgreSQL的encoding
    [uni-app] uni.showToast 一闪而过问题/设定时间无效/1秒即逝
    如何通过上传图片判断参数设置是否正确
    Meta Reality Labs:理想的VR头显还需克服这10项技术挑战
  • 原文地址:https://blog.csdn.net/weixin_51595939/article/details/136390455