美文网首页Go
玩转Golang之Struct结构体

玩转Golang之Struct结构体

作者: 夏海社长 | 来源:发表于2018-08-11 23:31 被阅读430次

    先介绍一下go语言的类型系统

    Golang中的类型系统

    类型系统是指一个语言的类型体系结构。一个典型的类型系统通常包含如下基本内容:

     基础类型,如byte、int、bool、float等;
     复合类型,如数组、结构体、指针等;
     可以指向任意对象的类型(Any类型);
     值语义和引用语义;
     面向对象,即所有具备面向对象特征(比如成员方法)的类型;
     接口。

    Go语言中的大多数类型都是值语义,并且都可以包含对应的操作方法。在需要的时候,你可以给任何类型(包括内置类型)“增加”新方法。而在实现某个接口时,无需从 该接口继承(事实上,Go语言根本就不支持面向对象思想中的继承语法),只需要实现该接口 要求的所有方法即可。任何类型都可以被Any类型引用。Any类型就是空接口,即interface{}。

    什么是结构体

    结构体(struct)是用户自定义的类型,它代表若干字段的集合,可以用于描述一个实体对象,类似java中的class,是golang面向对象编程的基础类型。
    结构体的概念在软件工程上旧的术语叫 ADT(抽象数据类型:Abstract Data Type)。在 C++ 它也存在,并且名字也是 struct,在面向对象的编程语言中,跟一个无方法的轻量级类一样。因为 Go 语言中没有类的概念,所以在 Go 中结构体有着更为重要的地位。

    如何定义一个结构体

    type Coordinate struct {
        X, Y float32
    }
    

    语法:type<Name>struct{}
    上述代码定义个一个名为Coordinate的结构体,里面包括了两个float32的变量X,Y,该结构体可用于表示一个平面坐标。

    添加对象方法

    其他的经典面向对象语言,如java,C#,定义对象方法时,会包含在class的定义内,如

    public class Coordinate{
        public float X {get; set;}
        public float Y {get; set;}
        //打印坐标
        public void GetCoordinate(){
              Console.WriteLine("("+this.X+","+this.Y+")");
        }
    }
    

    在go语言中,对象方法在结构体定义的外部添加

    type Coordinate struct {
        X, Y float32
    }
    //打印坐标
    func (coo *Coordinate) GetCoordinate() {
        fmt.Printf("(%.2f,%.2f)\n", coo.X, coo.Y)
        return
    }
    

    其中,func关键字后面的(coo *Coordinate),表示该函数传入一个指向Coordinate的指针,可通过指针变量coo来操作结构体的值。

    几种结构体初始化

    一、按原始字段顺序通过创建结构体

    package main
    import (
        "fmt"
    )
    func main(){
        p0 := Coordinate{1, 2}
        p0.GetCoordinate()
    }
    

    输出:(1.00,2.00),其中X=1,Y=2

    二、按照自定义字段顺序进行初始化

    package main
    import (
        "fmt"
    )
    func main(){
        p0 := Coordinate{Y:1, X:2}
        p0.GetCoordinate()
    }
    

    输出:(2.00,1.00),其中X=2,Y=1

    三、通过new函数创建

    package main
    import (
        "fmt"
    )
    func main(){
        //给该结构体p2变量分配内存,它返回指向已分配内存的指针
        p0 := new(Coordinate)
        p0.X=1
        p0.Y=2
        p0.GetCoordinate()
    }
    

    输出:(1.00,2.00),其中X=1,Y=2
    其中p0 := new(Coordinate)等价于以下写法

    p3 := &Coordinate{X: 1, Y: 2}
    
    p3 := &Coordinate{1,2}
    

    比较三种创建方式

    其中,第一种与第二种,p0均为一个类型为Coordinate的实例,而第三种p0为一个指向Coordinate的指针,相当于var p0 *Coordinate = new(Coordinate)

    一般在进行例如type T struct {a, b int}的结构体定义之后
    习惯使用t := new(T)给该结构体变量分配内存,它返回指向已分配内存的指针。变量t是一个指向T的指针,此时结构体字段的值是它们所属类型的零值。
    声明 var t T 也会给 t 分配内存,并零值化内存,但是这个时候 t是类型T。在这两种方式中,t 通常被称做类型T的一个实例(instance)或对象(Object)var t *T = new(T)等价于t := new(T)

    通过代码分析以上结论

    package main
    import (
        "fmt"
    )
    func main(){
        p0 := Coordinate{1, 2}
        //给该结构体p2变量分配内存,它返回指向已分配内存的指针
        p2 := new(Coordinate)
        p2.X = 1
        p2.Y = 2
        p3 := &Coordinate{X: 1, Y: 2}
        p4 := &Coordinate{1, 2}
    
        fmt.Println("-------输出p0-------")
        fmt.Printf("%v\n%T\n", p0, p0)
        fmt.Println("-------输出p2-------")
        fmt.Printf("%v\n%T\n", p2, p2)
        fmt.Println("-------输出p3-------")
        fmt.Printf("%v\n%T\n", p3, p3)
        fmt.Println("-------输出p4-------")
        fmt.Printf("%v\n%T\n", p4, p4)
    }
    

    输出:

    -------输出p0-------
    {1 2}
    Coordinate
    -------输出p2-------
    &{1 2}
    *Coordinate
    -------输出p3-------
    &{1 2}
    *Coordinate
    -------输出p4-------
    &{1 2}
    *Coordinate
    

    可以看出来,p2,p3,p4均为一个指针变量

    添加值拷贝的对象方法

    刚才说到了,添加一个对象方法,可以通过func (t *T) functionname()来创建,其中t为一个指针变量。我们也可以通过值拷贝的方式,添加一个对象方法,语法为func(t T) functionname()

    package main
    import (
        "fmt"
    )
    type Coordinate struct {
        X, Y float32
    }
    
    func (coo *Coordinate) GetCoordinate() {
        fmt.Printf("(%.2f,%.2f)\n", coo.X, coo.Y)
        return
    }
    //值拷贝对象方法
    func (coo Coordinate) SetPosition01(a float32,b float32) {
        coo.X = a
        coo.Y = b
    }
    
    //指针变量对象方法
    func (coo *Coordinate) SetPosition02(a float32,b float32) {
        coo.X = a
        coo.Y = b
    }
    func main(){
        p0 := Coordinate{1, 2}
        fmt.Print("SetPosition01调用前:")
        p0.GetCoordinate()
        p0.SetPosition01(0, 0)
        fmt.Print("SetPosition01调用后:")
        p0.GetCoordinate()
    
        fmt.Print("SetPosition02调用前:")
        p0.GetCoordinate()
        p0.SetPosition02(0, 0)
        fmt.Print("SetPosition02调用后:")
        p0.GetCoordinate()
    }
    

    输出:

    SetPosition01调用前:(1.00,2.00)
    SetPosition01调用后:(1.00,2.00)
    SetPosition02调用前:(1.00,2.00)
    SetPosition02调用后:(0.00,0.00)
    

    从程序输出中可以看出,调用SetPosition01方法,发生了值拷贝,即使在方法内改变了coo的值,外部的p0的值没有被改变。而SetPosition02方法中,coo为指向p0地址的指针,由于是通过指针变量修改了X,Y的值,所以调用完毕后,外部p0的值会被修改为(0,0)

    匿名结构体

    package main
    import (
        "fmt"
    )
    func main(){
        p_3d := struct {
            X, Y, Z float32
        }{1, 2, 3}
        fmt.Println("-------输出p_3d-------")
        fmt.Printf("%v\n%T\n", p_3d, p_3d)
    }
    

    输出:

    -------输出p_3d-------
    {1 2 3}
    struct { X float32; Y float32; Z float32 }
    

    p_3d为一个包含X,Y,Z三个变量的匿名结构体

    golang构造函数?

    在Go语言中没有构造函数的概念,对象的创建通常交由一个全局的创建函数来完成,以 NewXXX来命名,表示“构造函数”:
    这一切非常自然,开发者也不需要分析在使用了new之后到底背后发生了多少事情。在Go语言中,一切要发生的事情都直接可以看到。
    —— 《Go语言编程》

    func NewRect(x, y, width, height float64) *Rect { 
        return &Rect{x, y, width, height}
    }
    

    变量、方法可见性

    Go语言对关键字的增加非常吝啬,其中没有privateprotectedpublic这样的关键 字。要使某个符号对其他包(package)可见(即可以访问),需要将该符号定义为以大写字母开头,如:

     type Rect struct { 
      X, Y float64
      Width, Height float64 
    }
    

    这样,Rect类型的成员变量就全部被导出了,可以被所有其他引用了Rect所在包的代码访问到。 成员方法的可访问性遵循同样的规则,例如:

    func (r *Rect) area() float64 { 
        return r.Width * r.Height
    } 
    

    这样,Rectarea()方法只能在该类型所在的包内使用。
    需要注意的一点是,Go语言中符号的可访问性是包一级的而不是类型一级的。在上面的例 子中,尽管area()Rect的内部方法,但同一个包中的其他类型也都可以访问到它。这样的可访问性控制很粗旷,很特别,但是非常实用。如果Go语言符号的可访问性是类型一级的,少不 了还要加上friend这样的关键字,以表示两个类是朋友关系,可以访问彼此的私有成员。

    相关文章

      网友评论

        本文标题:玩转Golang之Struct结构体

        本文链接:https://www.haomeiwen.com/subject/ccfjbftx.html