结构体

Go语言中没有class的概念,只有结构体struct,但我们可以为结构体定义函数(在Java/C/C++中通常叫方法或是成员函数),因此也可以实现面向对象。

结构体的定义

Go语言中结构体写法也是倒过来的,而且要加个type关键字。

type Vector2 struct {
    X int
    Y int
}

实际上,Go中的type有点类似C语言中的typedef,用于实现自定义类型。上面定义我们可以这样理解:我们声明了一个struct { ... }结构体类型,并为其起了别名叫Vector2。此外,结构体成员字段名称的首字母大小写决定了该成员的可见性(访问范围):

首字母大写:公开字段,结构体成员对包外可见。

首字母小写:私有字段,结构体成员对包外不可见,但包内可以访问。

结构体初始化

使用如下写法初始化一个结构体,初始化后我们可以直接打印输出。

package main

import "fmt"

type Vector2 struct {
    X int
    Y int
}

func main() {
    var vec Vector2 = Vector2{3, 5}
    fmt.Println(vec)
}

输出结果如下。

{3 5}

初始化时,我们也可以指明字段的值。

var vec Vector2 = Vector2{2, 3}

我们也可以不指明任何值,结构体的成员变量会初始化为默认值。

var vec Vector2 = Vector2{}

实际上上面一行代码也可以简写为自动类型推导的var vec = Vector2{}或是vec := Vector2{},这里就不多介绍了。

初始化结构体并获得一个指针

Go语言提供了new关键字,用它初始化一个结构体可以返回一个指向空结构体的指针,这里所谓的空结构体就是其成员变量全部按照默认值进行初始化。

var vec *Vector2 = new(Vector2)

如果需要在初始化结构体时直接赋值,我们直接使用取地址符就可以。

var vec *Vector2 = &Vector2{2, 3}

这种写法也可以不指定任何值。

var vec *Vector2 = &Vector2{}

尽管Go语言有new关键字,但是Go编译器会自动选择为变量分配栈内存还是堆内存,我们无法对其控制,也就是说Vector2{3, 5}new(Vector2)分配的内存我们根本无法确定其在栈上还是在堆上,这显然会让大多数C/C++程序员感到无法理解。

实际上,以现在CPU的性能,我们的应用程序瓶颈根本不会卡在栈内存还是堆内存这种问题上,Go语言这么设计是更加为开发者不要花过多的精力在繁琐的语法细节上考虑,如果你觉得某个模块这样写不好,或不适合当前项目,大可以用C/C++去写。

结构体成员变量的访问

Go语言中使用点号.访问结构体对象的成员。

var vec Vector2 = Vector2{2, 3}
fmt.Println(vec.X, vec.Y)

注:即使是指针,也是直接用.访问。

定义结构体函数

如下代码为Vector2结构体定义了一个函数Abs(),函数声明中v其实就是具有该成员函数的对象。

type Vector2 struct {
    X float64
    Y float64
}

func (v *Vector2) Abs() float64 {
    return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

这里注意v是一个指针,我们建议结构体的成员函数都使用指针指向结构体,这样能够减小结构体内存拷贝的开销。在其它编程语言中,这个v其实就是this指针。

匿名结构体

对于一些临时数据结构,在其它强类型语言中我们可能习惯于使用Map结构,Go语言中我们还可以使用匿名结构体,下面是一个例子。

package main

import "fmt"

func main() {
    var vec struct {
        x int
        y int
    }

    vec.x = 1
    vec.y = 2
    fmt.Println(vec.x, vec.y)
}

根据Go语言的语法,以上代码也可以简写为如下形式。

package main

import "fmt"

func main() {
    vec := struct {
        x int
        y int
    }{1, 2}
    fmt.Println(vec.x, vec.y)
}

结构体嵌套

普通嵌套

Go语言中结构体可以嵌套,下面是一个简单的例子。

package main

import "fmt"

type Teacher struct {
    TeacherId string
    Name      string
}

type Classroom struct {
    RoomId  string
    Teacher Teacher
}

func main() {
    room := Classroom{RoomId: "1", Teacher: Teacher{TeacherId: "1001", Name: "Tom"}}
    fmt.Println(room.RoomId)
    fmt.Println(room.Teacher.TeacherId)
}

代码中,Teacher类型嵌套在Classroom内,访问嵌套的属性时,我们需要指定嵌套类型。

嵌套匿名结构体

下面例子中我们实现了结构体匿名嵌套。

package main

import "fmt"

type Dog struct {
    Color string
    Animal
}

type Animal struct {
    Name string
}

func main() {
    dog := Dog{Color: "black", Animal: Animal{Name: "dog"}}
    fmt.Println(dog.Animal.Name)
    fmt.Println(dog.Name)
    fmt.Println(dog.Color)
}

匿名嵌套中,我们可以用变量.内嵌结构体类型名.字段的形式访问内嵌结构体字段,也可以直接用变量.字段访问,这是匿名嵌套和普通嵌套的一个主要区别。当然,这种访问方式可能存在字段名冲突问题,这个时候为了避免歧义我们就必须指定具体的内嵌结构体的字段。尽管Go语言不支持继承,但使用这种匿名嵌套结构体写法,我们可以实现类似“继承”的功能。

结构体的Tag

Go语言中结构体支持Tag属性,Tag的内容是一组键值对,它类似注释但又支持通过代码读取。实际上,结构体的Tag可以通过反射读取,下面是一个例子。

package main

import (
    "fmt"
    "reflect"
)

type Student struct {
    Name string `json:"name"`
    Age  int    `json:"age"`
}

func main() {
    student := Student{"Tom", 18}
    t := reflect.TypeOf(student)
    for i := 0; i < t.NumField(); i++ {
        field := t.Field(i)
        jsonTag := field.Tag.Get("json")
        fmt.Println(jsonTag)
    }
}

注意,结构体的Tag必须严格遵循key:"value"的格式,Tag写错造成一些库的运行逻辑异常是比较难以定位的。

Go语言的很多内置库和第三方库都通过Tag来对结构体字段进行一些额外配置,比如Go语言的Json库就支持通过结构体的Tag配置字段名等信息,下面例子演示了Go语言标准库中json包的用法。

package main

import (
    "encoding/json"
    "fmt"
)

type Student struct {
    Name string `json:"name"`
    Age  int    `json:"age"`
}

func main() {
    str, _ := json.Marshal(Student{"Tom", 18})
    fmt.Println(string(str))
}
作者:Gacfox
版权声明:本网站为非盈利性质,文章如非特殊说明均为原创,版权遵循知识共享协议CC BY-NC-ND 4.0进行授权,转载必须署名,禁止用于商业目的或演绎修改后转载。
Copyright © 2017-2024 Gacfox All Rights Reserved.
Build with NextJS | Sitemap