Go结构体

本文仅是自己阅读笔记,不正确之处请多包涵和纠正。
原文The way to go

一、结构体定义

结构体定义的一般方式如下:

type identifier struct {
    field1 type1      //字段名1  数据类型
    field2 type2      //字段名2  数据类型
    ...
}

结构体的字段可以是任何类型,甚至是结构体本身,也可以是函数或者接口。

1、使用 new与使用var声明结构体的区别

使用 new 函数给一个新的结构体变量分配内存,它返回指向已分配内存的指针:var t *T = new(T),如果需要可以把这条语句放在不同的行(比如定义是包范围的,但是分配却没有必要在开始就做)。

var t *T
t = new(T)

写这条语句的惯用方法是:t := new(T)变量 t 是一个指向 T的指针,此时结构体字段的值是它们所属类型的零值。
注意:
声明 var t T 也会给 t 分配内存,并零值化内存,但是这个时候 t 是类型T(与new返回的t不一样,new返回的t是一个结构体类型指针,var声明的t是一个结构体类型变量)。在这两种方式中,t 通常被称做类型 T 的一个实例或对象。

2、初始化结构体

初始化一个结构体实例的简短和惯用方式如下:

type struct1 struct {
    i1  int
    f1  float32
    str string
}

//第一种
ms := &struct1{10, 15.5, "Chris"}  // 此时ms的类型是 *struct1

//第二种
ms2 := struct1{10, 15.5, "Chris"}  // 此时ms2的类型是 struct1
//第三种
var ms struct1                     // 此时ms3的类型是 struct1,  &ms3即为 *struct1
ms3 = struct1{10, 15.5, "Chris"}

&struct1{a, b, c} 是一种简写,底层仍然会调用 new (),这里值的顺序必须按照字段顺序来写。表达式 new(Type) 和 &Type{} 是等价的。
注:(*ms).str = "Chris"这种赋值写法是合法的

3、使用选择器(.)来引用结构体字段

例如:

type structname struct {
    fieldname1 type1      //字段名1  数据类型
    fieldname2 type2      //字段名2  数据类型
    ...
}

使用选择器给字段赋值:

structname.fieldname1 = value

使用选择器获取结构体字段的值

structname.fieldname1

无论变量是一个结构体类型还是一个结构体类型指针,都使用同样的 选择器符(selector-notation) 来引用结构体的字段:

type myStruct struct { i int }
var v myStruct    // v是结构体类型变量
var p *myStruct   // p是指向一个结构体类型变量的指针
v.i
p.i
4、结构体的内存布局

Go 语言中,结构体和它所包含的数据在内存中是以连续块的形式存在的,即使结构体中嵌套有其他的结构体,这在性能上带来了很大的优势。不像Java 中的引用类型,一个对象和它里面包含的对象可能会在不同的内存空间中。

5、结构体转换

Go 中的类型转换遵循严格的规则。当为结构体定义了一个 alias 类型时,此结构体类型和它的 alias 类型都有相同的底层类型

示例:

package main
import "fmt"

type number struct {
    f float32
}

type nr number   // alias type

func main() {
    a := number{5.0}
    b := nr{5.0}
    // var i float32 = b   // compile-error: cannot use b (type nr) as type float32 in assignment
    // var i = float32(b)  // compile-error: cannot convert b (type nr) to type float32
    // var c number = b    // compile-error: cannot use b (type nr) as type number in assignment
    // needs a conversion:
    var c = number(b)
    fmt.Println(a, b, c)
}

输出:

{5} {5} {5}

二、使用工厂的方法创建结构体实例

Go 语言不支持面向对象编程语言中那样构造对象的构造方法,但是在GO语言中很容易实现类似的“构造子工厂”方法。假设定义了如下的 File 结构体类型:

type File struct {
    fd      int     // 文件描述符
    name    string  // 文件名
}

下面是这个结构体类型对应的工厂方法,它返回一个指向结构体实例的指针:

func NewFile(fd int, name string) *File {
    if fd < 0 {
        return nil
    }

    return &File{fd, name}
}

然后这样调用它:

f := NewFile(10, "./test.txt")

在 Go 语言中常常像上面这样在工厂方法里使用初始化来简便的实现构造函数。可以说是工厂实例化了类型的一个对象,就像在基于类的OO语言中那样。
如果想知道结构体类型T的一个实例占用了多少内存,可以使用:size := unsafe.Sizeof(T{})。

1、如何强制使用工厂方法

通过应用可见性规则就可以禁止使用 new 函数,强制用户使用工厂方法,从而使类型变成私有的,就像在面向对象语言中那样。

type matrix struct {
    ...
}

func NewMatrix(params) *matrix {
    m := new(matrix) // 初始化 m
    return m
}

然后在其他包里使用工厂方法:

package main
import "matrix"
...
wrong := new(matrix.matrix)     // 编译失败(matrix 是私有的)
right := matrix.NewMatrix(...)  // 实例化 matrix 的唯一方式
2、map 和 struct 使用new()和make()的不同点

1、不能使用make()去构建一个结构体变量
2、使用new()一个map时,返回的是一个指向nil的指针,尚未被分配内存。所以都是使用make()构造map。
(而不同的是new()一个结构体时已分配内存,返回一个结构体指针)

示例:

package main

type Foo map[string]string
type Bar struct {
    thingOne string
    thingTwo int
}

func main() {
    // OK
    y := new(Bar)
    (*y).thingOne = "hello"
    (*y).thingTwo = 1

    // NOT OK
    z := make(Bar) // 编译错误:cannot make type Bar
    (*z).thingOne = "hello"
    (*z).thingTwo = 1

    // OK
    x := make(Foo)
    x["x"] = "goodbye"
    x["y"] = "world"

    // NOT OK
    u := new(Foo)
    (*u)["x"] = "goodbye" // 运行时错误!! panic: assignment to entry in nil map
    (*u)["y"] = "world"
}

三、使用其他自定义包里的结构体

使用其他包的结构体,其实也是遵循可见性规则

1、上面也有提到,当其他包的结构体没有导出时,但有提供导出的工厂方法,依然是可以创建结构体变量的。
2、当其他包的结构体导出时,只要在创建结构体变量时,在结构体名前加上包名即可。

示例:

package structPack

type ExpStruct struct {
    Mi1 int
    Mf1 float32
}
package main
import (
    "fmt"
    "./struct_pack/structPack"
)

func main() {
    struct1 := new(structPack.ExpStruct)
    struct1.Mi1 = 10
    struct1.Mf1 = 16.

    fmt.Printf("Mi1 = %d\n", struct1.Mi1)
    fmt.Printf("Mf1 = %f\n", struct1.Mf1)
}

输出:

Mi1 = 10
Mf1 = 16.000000

四、结构体中字段的标签

1、结构体中字段的标签

结构体中的字段除了有名字和类型外,还可以有一个可选的标签(tag):它是一个附属于字段的字符串,可以是文档或其他的重要标记。标签的内容不可以在一般的编程中使用,只有包reflect 能获取它。

2、通过reflect包获取结构体中字段的标签

reflect包可以在运行时自省类型、属性和方法,比如:在一个变量上调用 reflect.TypeOf() 可以获取变量的正确类型,如果变量是一个结构体类型,就可以通过 Field 来索引结构体的字段,然后就可以使用 Tag 属性。

package main

import (
	"fmt"
	"reflect"
)

type TagType struct { // tags
	field1 bool   "An important answer"
	field2 string "The name of the thing"
	field3 int    "How much there are"
}

func main() {
	tt := TagType{true, "Barak Obama", 1}
	for i := 0; i < 3; i++ {
		refTag(tt, i)
	}
}

func refTag(tt TagType, ix int) {
	ttType := reflect.TypeOf(tt)
	ixField := ttType.Field(ix)
	fmt.Printf("%v\n", ixField.Tag)
}

输出:

An important answer
The name of the thing
How much there are
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值