结构体简介
Go 通过类型别名(alias types)和结构体的形式支持用户自定义类型,或者叫定制类型。一个带属性的结构体试图表示一个现实世界中的实体。结构体是复合类型(composite types),当需要定义一个类型,它由一系列属性组成,每个属性都有自己的类型和值的时候,就应该使用结构体,它把数据聚集在一起。然后可以访问这些数据,就好像它是一个独立实体的一部分。结构体也是值类型,因此可以通过 new 函数来创建。
组成结构体类型的那些数据称为 字段(fields)。每个字段都有一个类型和一个名字;在一个结构体中,字段名字必须是唯一的。
Go中没有类的概念
结构体定义
- 如果字段在代码中从来不会用到,那么就可以把它命名为_(单独使用下划线,在Go中表示忽略)
- 结构体的字段可以事任何类型,也可以是函数或是接口
1 type identifier struct{ 2 field1 type1 3 field2 type2 4 ... 5 } 6 7 // 声明 8 var s identifier 9 identifier.field1 = value 10 identifier.field2 = value
结构体声明
1 // 下面三种相同,t为结构体指针 2 var t *T // T是结构体类型 3 t = new(T) // 结构体指针变量t 4 5 var t *T = new(T) 6 7 t := new(T) // 指针变量t
不使用new的声明方式:var t T 也会给t分配内存,并零化内存,t为类型T而不是指针类型*T
结构体赋值和使用
使用点符号给字段赋值或者获取结构体字段的值,变量不管是结构体类型还是结构体指针类型,都使用点符号来引用结构体的字段
注意当变量类型是指针结构体时,既可以通过指针给结构体字段赋值,也可以通过使用解指针的方式给结构体字段赋值,注意下面的例子
1 import "fmt" 2 type struct1 { 3 i1 int 4 f1 float32 5 s1 string 6 } 7 func main(){ 8 ms := new(struct1) // ms是*struct1类型 9 ms.i1 = 10 // 这是通过指针给结构体字段赋值,也可以通过解指针的方式给结构体赋值 这样子(*ms).i1 = 10 10 ms.f1 = 1.2 11 ms.s1 = "hello,word" 12 fmt.Println(ms.i1) 13 fmt.Println(ms.f1) 14 fmt.Println(ms.s1) 15 }
结构体初始化
1 // 方式一 2 ms:=&struct1{10,1.1,"gao"} // ms的类型是*struct1 3 4 // 方式二 5 var ms struct1 6 ms = struct1{10,1.1,"hu"} // ms的类型是ms 7 // 举例 8 type Interval struct{ 9 start int 10 end int 11 } 12 // 以下三种初始化方式 13 intr := Interval{0,3} 14 intr := Interval{start:3,end:3} 15 intt := Interval{end:4}
结构体的内存分布
Go语言中,结构体和它所包含的数据在内存中是以连续块的形式存在的,即使结构体中嵌套有其他的结构体,这在性能上带来了很大的优势。
1 type Rect1 struct {Min, Max Point}
2 type Rect2 struct {Min, Max *Point}
【结构体转换】
Go中的类型转换遵循严格的规则。当为结构体定义了一个alias类型时,此结构体类型和它的alias类型都有相同的底层类型。
1 package main 2 3 type number struct{ 4 a int 5 } 6 7 type aliasNumber number // 为结构体number定义了一个alias类型 aliasNumber
Go中实现构造方法
1- 原本不存在构造方法
1 type File struct { 2 fd int // 文件描述符 3 name string // 文件名 4 } 5 6 fun newFile(fd int, name,string)*File{ 7 if fd<0{ 8 return nil 9 } 10 return &File{fd,name} 11 }
2- 强制用户使用工厂方法:前面提到过,变量小写就会变成私有的,这里的实现也是这个原理。将结构体定义为私有的,在开放一个共有的方法来返回结构的指针。
1 type matrix struct{ 2 // 3 } 4 func NewMatrix(params)*matrix{ 5 m := new(matrix) 6 return m 7 } 8 9 // 在其他包中引入使用 10 package main 11 import "matrix" 12 wrong := new(matrix.matrix) // wrong way 13 right := matrix.NewMatrix(...) // right way
带标签的结构体
结构体的字段除了有名字和类型外,还可以有一个可选的标签:它是附属于字段的字符串,可以是文档或其他的重要标记。标签的内容不可以在一般的编程中使用,只有反射包reflect能够获取它。
示例如下
1 package main 2 3 import "fmt" 4 import "reflect" 5 6 type TagType struct{ 7 f1 bool "an important answer" 8 f2 string "the name of the thing" 9 f3 int "how much there are" 10 } 11 12 fun main(){ 13 tt := TagType{true,"kobe",82} 14 for i:=0;i<3,i++{ 15 refTag(tt,i) 16 } 17 } 18 func refTag(tt TagType,ix int){ 19 ttType := reflect.Typeof(tt) 20 ixField := ttType.Field(ix) 21 fmt.Printf("%v ",ixField.Tag) 22 }
结构体中的匿名字段
结构体中可以包含一个或多个匿名字段(字段没有名字,只有类型,这个类型可以包括结构体类型),但是一个结构体中只能包含一种类型的匿名字段。比如不能同时包含两个int int匿名字段(结构体类型也是同样的道理)。
使用带有匿名字段的结构体时,类型名称就是字段名,这就是为什么一个类型只能出现一次了。
package main import "fmt" type struct1 struct{ f1 int f2 bool f3 string float32 struct2 } type struct2 struct{ s1 string } func main(){ s1 := new(struct1) s1.f1 = 1 s1.f2 = true s1.f3 = "boom" s1.float32 = 2.3 s1.s1 = "childern" fmt.Println(s1) }
结构体中出现命名冲突
- 外层名字会覆盖内层名字(但是两者的内存空间都保留),这提供了一种重载字段或方法的方式
- 如果相同的名字在同一级别出现了两次,如果这个名字被程序使用了,将会引发错误。
如果想要使用内层的就通过层层去调用
1 package main 2 3 import ( 4 "fmt" 5 ) 6 7 type A struct { 8 a int 9 } 10 type B struct { 11 a int 12 b int 13 } 14 15 type C struct { 16 A 17 B 18 } 19 20 21 func main() { 22 var c_struct C 23 c_struct.A.a = 10 24 c_struct.b = 20 25 c_struct.B.a =30 26 27 fmt.Println(c_struct) 28 29 }