目录
1. 说明
Go 通过类型别名和结构体的形式支持用户自定义类型,或者叫定制类型。
Go 语言可以为自定义类型定义方法, 来完成类似其他语言类的定义.
2. 结构体(类)
2.1 定义
type identifier struct {
field1 type1
field2 type2
...
}
结构体成员一般都有名字,如果字段在代码中从来也不会被用到,那么可以命名它为 _。
结构体成员类型可以是任意类型, 可以是结构体/函数或者接口.
注意:当成员类型是结构体自身的时候(如:链表/树), 必须使用指针类型.
类型 struct1 在定义它的包 pack1 中必须是唯一的,它的完全类型名是:pack1.struct1。
2.2 创建
使用 new 函数, 会给结构体变量分配内存, 并返回该类型的指针
var t *T
t = new(T)
//t := new(T)
// 等价于 t = &T{}
直接声明变量, 也会分配内存, 并初始化为零值
var t T
2.3 初始化
type struct1 struct {
age int
high float
name string
}
ms := struct1{10, 12.3, "blue"} //必须按照定义中的顺序来初始化数据
ms2 := struct1{high:14.5, name:"red"} //对指定字段进行初始化, 未初始化的字段为零值
2.4 使用
var st struct1
//使用 . 来获取结构体成员, 结构体变量和结构体指针都用 .
//不用像 c/c++ 那样用 . 和 ->
st.name = "red"
fmt.Println(st.name)
2.5 结构体转换
当为结构体定义了一个 alias 类型时,此结构体类型和它的 alias 类型都有相同的底层类型, 我们可以进行类型转换
type number struct {
a int
}
type num number //alias type
func main() {
a := number{23}
b := num{32}
// 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)
}
3. 结构体进阶
3.1 带标签的结构体
结构体中的字段除了名字和类型外, 还有一个可选的标签 (tag): 它是一个附属于字段的字符串, 可以是文档或其他标记.
标签内容只有包 reflect 能获取到它. 举例如下:
type TagType struct { // tags
res bool "An important answer"
name string "The name of the thing"
num int "How much there are"
}
func refTag(tt TagType, ix int) {
ttType := reflect.TypeOf(tt)
ixField := ttType.Field(ix)
fmt.Printf("%v\n", ixField.Tag)
}
func main() {
tt := TagType{true, "red", 1}
for i := 0; i < 3; i++ {
refTag(tt, i)
}
}
3.2 匿名字段和内嵌结构体(继承)
结构体可以包含一个或多个匿名/内嵌字段,
匿名字段: 这些字段没有显式的名字只有类型, 此时类型就是字段名字.
内嵌结构体: 匿名字段本身是结构体类型
Go 中通过内嵌或组合来实现类似于继承的特性.
type size struct {
long int
wide int
}
type AA struct {
name string
int
size
}
func main() {
test.Test()
f := test.NewStudent(10, "red")
f.Show()
aa := new(AA)
aa.name = "red"
aa.int = 12
//可以把内嵌结构体作为结构体使用
//aa.size = size{10, 10}
//也可以使用内嵌结构体内部的字段
aa.long = 10
aa.wide = 10
fmt.Printf("%v\n", aa)
}
命名冲突:
结构体内两个字段拥有相同的名字(可能是继承来的名字)时:
1. 外层名字会覆盖内层名字(但是两者的内存空间都保留),这提供了一种重载字段或方法的方式;
2. 如果相同的名字在同一级别出现了两次,如果这个名字被程序使用了,将会引发一个错误(不使用没关系)。没有办法来解决这种问题引起的二义性,必须由程序员自己修正。
type (
A struct {a int}
B struct {a, b int}
C struct {A; B}
D struct {B; b float32}
)
func main() {
var c C
//使用 c.a 是错误的, 存在二义性
//c.a = 12
fmt.Println(c)
var d D
//d.b 为外层的float32
fmt.Println(reflect.TypeOf(d.b))
}
3.3 工厂模式
Go 不支持面向对象编程语言中那样的构造方法, 但是我们可以自己实现. 例如
type Student struct {
age int
name string
}
func NewStudent(age int, name string) *Student {
if age < 0 {
return nil
}
return &Student{age, name}
}
func main() {
f := NewStudent(10, "red")
}
我们可以使用可见性规则来让类型私有化, 强制用户使用工厂类方法, 例如
package test
import (
"fmt"
)
//外部不可以使用 new 函数来创建 student 实例, 也不可以直接使用 student 结构体
type student struct {
age int
name string
}
//外部可以用实例使用可见方法
func (st *student) Show() {
fmt.Println("i'm", st.name, ",i am", st.age, "years old!")
}
//外部使用工厂方法得到结构体实例
func NewStudent(age int, name string) *student {
if age < 0 {
return nil
}
return &student{age, name}
}