• golang之结构体和方法


    结构体的定义

    结构体是将零个或者多个任意类型的命令变量组合在一起的聚合数据类型。
    每个变量都叫做结构体的成员。

    其实简单理解,Go语言的结构体struct和其他语言的类class有相等的地位,但是GO语言放弃了包括继承在内的大量面向对象的特性,只保留了组合这个基础的特性。
    所有的Go语言类型除了指针类型外,都可以有自己的方法。

    先通过一个下的例子理解struct。

    package main
    
    import "fmt"
    
    type Student struct {
        Name  string
        Age   int
        Sex   string
        Score int
    }
    
    func main() {
        var stu Student
        stu.Name = "zhangsan"
        stu.Age = 12
        stu.Sex = "male"
        stu.Score = 90
        fmt.Printf("name:%s
     age:%d
     sex:%s
     score:%d
    ", stu.Name, stu.Age, stu.Sex, stu.Score)
    
        fmt.Printf("%v
    ", stu)
        fmt.Printf("%+v
    ", stu)
        fmt.Printf("%#v
    ", stu)
    }

    关于Go中的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
    }

    struct中字段的访问,和其他语言一样使用“.”点这个符号
    var stu Student
    stu.Name = "tom"
    stu.Age = 18

    赋值的时候我们是通过stu.Name同样的我们访问的时候也是通过stu.Name

    struct定义的三种形式

    type Student struct {
    Name string
    age int
    }
    对于上面这个结构体,我们定义的三种方式有:

    1. var stu student
    2. var stu *Student = new(Student)
    3. var stu *Student = &Student

    上面三种方法中,方法2和方法3的效果是一样的,返回的都是指向结构体的指针,访问的方式如下:
    stu.Name,stu.Age
    (*stu).Name,(*stu).Age而这种方法中可以换成上面的方法直接通过stu.Name访问
    这里是go替我们做了转换了,当我们通过stu.Name访问访问的时候,go会先判断stu是值类型还是指针类型如果是指针类型,会替我们改成(*stu).Name

    struct中所有字段的内存是连续的

    Go 中的struct没有构造函数,一般通过工厂模式来解决,通过下面例子理解:

    package main
    
    import "fmt"
    
    type Student struct {
        Name string
        Age  int
    }
    
    func newStudent(name string, age int) *Student {
        return &Student{
            Name: name, //后面需加上逗号,不然会报错
            Age:  age,  //后面需要加上逗号,不然会报错
        }
    }
    
    func main() {
        stu := newStudent("tom", 22)
        fmt.Println(stu)
        fmt.Println(stu.Name)
    }

    struct中的tag

    我们可以为struct中的每个字段,写上一个tag。这个tag可以通过反射的机制获取到,最常用的场景就是json序列化和反序列化

    下面先写一个正常我们序列化的例子:

    package main
    
    import (
        "encoding/json"
        "fmt"
    )
    
    type Student struct {
        Name string
        Age  int
    }
    
    func main() {
        var stu Student
        stu.Name = "tom"
        stu.Age = 22
    
        data, err := json.Marshal(stu)
        if err != nil {
            fmt.Printf("json marshal fail,fail message is %v
    ", err)
            return
        }
        fmt.Printf("json marshal result is %s
    ", data)
    }

    运行结果如下:

    json marshal result is {"Name":"tom","Age":22}

    注意:这里有个问题是我们在定义struct中的字段的时候如:Name,Age都是首字母大写的,这样你json序列化的时候才能访问到,如果是小写的,json包则无法访问到,所以就像上述的结果一样,序列化的结果也是首字母大写的,但是我就是想要小写怎么办?这里就用到了tag,将上述的代码更改为如下,序列化的结果就是小写的了:

    package main
    
    import (
        "encoding/json"
        "fmt"
    )
    
    type Student struct {
        Name string `json:"name"`
        Age  int    `json:"age"`
    }
    
    func main() {
        var stu Student
        stu.Name = "tom"
        stu.Age = 22
    
        data, err := json.Marshal(stu)
        if err != nil {
            fmt.Printf("json marshal fail is %v
    ", err)
            return
        }
        fmt.Printf("json marshal result %s
    ", data)
    
        //反序列化到struct
        var stu2 Student
        err = json.Unmarshal(data, &stu2)
        if err != nil {
            fmt.Printf("unmarshal fail is %v
    ", err)
            return
        }
        fmt.Printf("unmarshal result is %+v
    ", stu2)
    }

    可以看到将json反序列化成结构的方式是

    json.Unmarsha

    结构体的比较

    如果结构体的全部成员都是可以比较的,那么结构体也是可以比较的,那样的话,两个结构体将可以使用==或!=运算符进行比较。相等比较运算符将比较两个机构体的每个成员
    如下面例子:

    package main
    
    import "fmt"
    
    type Pointer struct {
        x int
        y int
    }
    
    func main() {
        p1 := Pointer{1, 2}
        p2 := Pointer{2, 3}
        p3 := Pointer{1, 2}
        fmt.Printf("p1==p2:%t
    ", p1 == p2)
        fmt.Printf("p1==p3:%t
    ", p1 == p3)
    }

    匿名字段

    结构体中字段可以没有名字
    下面是一个简单的例子:

    package main
    
    import "fmt"
    
    type Student struct {
        Name string
        Age  int
        int
    }
    
    func main() {
        var stu Student
        stu.Name = "tom"
        stu.Age = 22
        stu.int = 111
        fmt.Printf("%+v
    ", stu)
    }

    可能上面的这里例子看了之后感觉貌似也没啥用,其实,匿名字段的用处可能更多就是另外一个功能(其他语言叫继承),例子如下:

    package main
    
    import "fmt"
    
    type People struct {
        Name string
        Age  int
    }
    type Student struct {
        People
        Score int
    }
    
    func main() {
        var stu Student
        // stu.People.Name = "tom"//是下面的简写
        // stu.People.Age = 22
        stu.Name = "tom"
        stu.Age = 22
        stu.Score = 100
        fmt.Printf("%+v
    ", stu)
    }

    注意:关于字段冲突的问题,我们在People中定义了一个Name字段,在Student中再次定义Name,这个时候,我们通过s.Name获取的就是Student定义的Name字段

    如下:

    package main
    
    import "fmt"
    
    type People struct {
        Name string
        Age  int
        Sex  string
    }
    type Student struct {
        //字段冲突,重新定义一个Sex覆盖就行
        Sex int //放在People的上边或下边都行
        People
    }
    
    func main() {
        var stu Student
        stu.Name = "tom"
        stu.Age = 22
        stu.Sex = 1
        fmt.Printf("%+v
    ", stu)
    }

    方法

    首先强调一下:go中任何自定义类型都可以有方法,不仅仅是struct
    注意除了:指针和interface

    通过下面简单例子理解:

    package main
    
    import (
        "fmt"
    )
    
    //这里是我们普通定义的一个函数add
    func add(a,b int) int {
        return a+b
    }
    
    type Int int
    
    //这里是对Int这个自定义类型定义了一个方法add
    func (i Int) add(a,b int) int{
        return a+b
    }
    //如果想要把计算的结果赋值给i
    func(j *Int) add2(a,b int){
        *j = Int(a+b)
        return
    }
    
    func main(){
        c := add(100,200)
        fmt.Println(c)
        var b Int
        res := b.add(10,100)
        fmt.Println(res)
    
        var sum Int
        sum.add2(20,20)
        fmt.Println(sum)
    
    }

    方法的定义:

    func(receiver type)methodName(参数列表)(返回值列表){

    }

    下面是给一个结构体struct定义一个方法

    package main
    
    import (
        "fmt"
    )
    
    
    type Student struct{
        Name string
        Age int
    }
    
    func (stu *Student)Set(name string,age int){
        stu.Name = name
        stu.Age = age
    }
    
    func main(){
        var s Student
        s.Set("tome",23)
        fmt.Println(s)
    }

    注意:方法的访问控制也是通过大小写控制的

    在上面这个例子中需要注意一个地方func (stu *Student)Set(name string,age int)这里使用的是(stu *Student)而不是(stu Student)这里其实是基于指针对象的方法

    基于指针对象的方法

    当调用一个函数时,会对其每个参数值进行拷贝,如果一个函数需要更新一个变量,或者函数的其中一个参数是在太大,我们希望能够避免进行这种默认的拷贝,这种情况下我们就需要用到指针了,所以在上一个代码例子中那样我们需要func (stu *Student)Set(name string,age int)来声明一个方法

    这里有一个代码例子:

    package main
    
    import "fmt"
    
    type Pointer struct {
        X float64
        Y float64
    }
    
    func (p *Pointer) ScaleBy(f float64) {
        p.X *= f
        p.Y *= f
    }
    
    func main() {
        //method 1
        p1 := &Pointer{2, 3}
        p1.ScaleBy(3)
        fmt.Printf("p1:%f
    ", *p1)
    
        //method 2
        p2 := Pointer{2, 3}
        p2r2 := &p2
        p2r2.ScaleBy(3)
        fmt.Printf("p2:%f
    ", p2)
    
        //method 3
        p3 := Pointer{2, 3}
        (&p3).ScaleBy(3)
        fmt.Printf("p3:%f
    ", p3)
    
        //method 4
        //相对来说方法2和方法3有点笨拙
        //方法4,go语言这里会自己判断p是一个Point类型的变量,
        //并且其方法需要一个Point指针作为指针接收器,直接可以用下面简单的方法
        p4 := Pointer{2, 3}
        p4.ScaleBy(3)
        fmt.Printf("p4:%f
    ", p4)
    }

    上面例子中最后一种方法,编译器会隐式的帮我们用&p的方法去调用ScaleBy这个方法
    当然这种简写方法只适用于变量,包括struct里面的字段,如:p.X

    转自https://www.cnblogs.com/zhaof/p/8244542.html

  • 相关阅读:
    设计模式(六)Prototype Pattern 原型模式
    设计模式(五)Builder Pattern建造者模式
    Linux安装软件
    日志技术及JUL入门
    IDEA推出新字体,极度舒适
    HDFS的API操作
    Apollo的灰度发布
    Apollo整合SpringBoot开发
    Apollo配置发布原理
    Apollo应用配置
  • 原文地址:https://www.cnblogs.com/justdoyou/p/10033675.html
Copyright © 2020-2023  润新知