type
关键字自定义类型。The new type is called a defined type. It is different from any other type, including the type it is created from.
语法
TypeDecl = "type" ( TypeSpec | "(" { TypeSpec ";" } ")" ) .
TypeSpec = AliasDecl | TypeDef .
AliasDecl = identifier "=" Type .
type newInt int
func main() {
var a newInt
var b int
fmt.Printf("%T %s %#v\n", a, reflect.TypeOf(a), a)
fmt.Printf("%T %s %#v\n", b, reflect.TypeOf(b), b)
//类型不同无法判等
//fmt.Println(b == a)//Invalid operation: b == a (mismatched types int and newInt)
}
/**
output:
main.newInt main.newInt 0
int int 0
*/
以上自定义一个类型为newInt,newInt的类型看上去和int没啥区别,但确确实实newInt是一个自定义的新类型,虽然和int具备相同的内存存储结构。newInt表示的时候main包下面的newInt 。int是内置 类型int。
type
关键对已有类型取别名使用关键字type定义自定义类型,包括基于基础类型、结构体类型、函数类型、数组等类型进行定义。
语法
AliasDecl = identifier "=" Type .
类型别名,意味着对于某一个类型,再取一个名字,只是名字的区别,最基本的类型还是最初始的类型。type classAliasName type
来个示例看看。
type aliasForInt = int
func main() {
var a aliasForInt
var b int
fmt.Printf("%T %s %#v\n", a, reflect.TypeOf(a), a)
fmt.Printf("%T %s %#v\n", b, reflect.TypeOf(b), b)
fmt.Println(a == b)
}
/**
int int 0
int int 0
true
*/
类型别名实际上和原类型是相同类型。经常使用的类型别名有:rune 和 byte。
// byte is an alias for uint8 and is equivalent to uint8 in all ways. It is
// used, by convention, to distinguish byte values from 8-bit unsigned
// integer values.
type byte = uint8
// rune is an alias for int32 and is equivalent to int32 in all ways. It is
// used, by convention, to distinguish character values from integer values.
type rune = int32
1. 自定义类型是新的类型,类型别名只是一个类型的另一个名字
2. 自定义类型和原类型不支持判等,类型别名支持判等。
go语言中没有使用type和struct关键字定义命名结构体,用来把多个类型的字段安先后顺序组合成一个符合类型,和C语言的struct比较类似。
结构体由一组元素组成,这些元素可以称为字段,每个字段都有一个名称和一个类型(以及一个tag)。字段名可以显式指定(IdentifierList)或隐式指定(EmbeddedField)。在结构体中,非空白字段名必须是惟一的。
A struct is a sequence of named elements, called fields, each of which has a name and a type. Field names may be specified explicitly (IdentifierList) or implicitly (EmbeddedField). Within a struct, non-blank field names must be unique.
语法
//官方语法
StructType = "struct" "{" { FieldDecl ";" } "}" .
FieldDecl = (IdentifierList Type | EmbeddedField) [ Tag ] .
EmbeddedField = [ "*" ] TypeName .
Tag = string_lit .
//简单表示
struct {
fieldName TYPE1
fieldName2 TYPE2
...
fieldNameN TYPEN
_ TYPE //匿名类型
}
func main() {
var b struct {
name string
age int
}
fmt.Printf("%#v %s\n", b, reflect.TypeOf(b))
}
/**
output:
struct { name string; age int }{name:"", age:0} struct { name string; age int }
*/
经常把struct {…}称为未命名的结构体类型,或者是匿名结构体。
使用关键字key定义一个类型animal。该animal是一个结构体,该结构体第一个域是name string类型,第二个域是age unint类型。
type animal struct {
name string
age uint
}
func main() {
a := animal{
name: "a",
age: 1,
}
fmt.Printf("%#v %s\n", a, reflect.TypeOf(a))
}
/**
output:
main.animal{name:"a", age:0x1} main.animal
*/
这样我们就对结构体有了最基本的认识。
结构体类型,属于自定义类型,也是一种类型。我们也可以使用var关键字声明结构体类型。
type animal2 struct {
name string
age uint
}
func main() {
var a animal2
a.name = "test"
fmt.Printf("%#v %s\n", a, reflect.TypeOf(a))
}
/**
output:
main.animal2{name:"test", age:0x0} main.animal2
*/
type animal3 struct {
name string
age uint
sex bool
}
func main() {
var a = animal3{
"wo", 15, true,
}
fmt.Println(a)
b := animal3{
"wo", 15, true,
}
fmt.Println(b)
// too few values in animal3 literal
//var c = animal3{
// "wo",
//}
}
/**
output:
{wo 15 true}
{wo 15 true}
*/
1. 必须显示初始化所有域字段
2. 初始化顺序同定义必须一致,不然类型可能匹配不上,或者复制错误。
type animal4 struct {
name string
age uint
sex bool
}
func main() {
var a = animal4{
name: "name1",
age: 0,
sex: false,
}
fmt.Println(a)
var b = animal4{
age: 10,
}
fmt.Println(b)
var c = animal4{
}
fmt.Println(c)
}
/**
output:
{name1 0 false}
{ 10 false}
{ 0 false}
*/
访问结构体中的一个变量名, 用 “.” 来连接,(*T).field 的表达和T.field一致
/*
结构体的使用
*/
type animal5 struct {
name string
age uint
sex bool
}
func main() {
a := animal5{
name: "boyyyy",
age: 30,
sex: false,
}
b := animal5{
name: "girl",
age: 1,
sex: true,
}
//访问结构体域值
fmt.Printf("a.name=[%s] a.age=[%d] a.sex=[%v]\n", a.name, a.age, a.sex)
//c是b的地址因此c指向b
c := &b
fmt.Printf("a = [ %p |%#v ]\t\t\t\tb = [ %p | %#v] \n", &a, a, &b, b)
a.age = 10
b.name = "girrrrrl"
//d是通过b构建的新内存块
d := b
fmt.Printf("a = [ %p |%#v ]\t\t\t\tb = [ %p | %#v] \n", &a, a, &b, b)
fmt.Printf("b = [ %p |%#v ]\t\t\t\td = [ %p | %#v] \n", &b, b, &d, d)
c.name = "girllll"
fmt.Printf("b = [ %p |%#v ]\t\t\t\tc = [ %p | %#v] \n", &b, b, c, c)
//(*T).field == T.field --->语法糖
(*c).name = "ggggirl"
fmt.Printf("b = [ %p |%#v ]\t\t\t\tc = [ %p | %#v] \n", &b, b, c, c)
}
/**
备注:地址值在不同电脑执行可能不一样
output:
a.name=[boyyyy] a.age=[30] a.sex=[false]
a = [ 0xc0000044a0 |main.animal5{name:"boyyyy", age:0x1e, sex:false} ] b = [ 0xc0000044c0 | main.animal5{name:"girl", age:0x1, sex:true}]
a = [ 0xc0000044a0 |main.animal5{name:"boyyyy", age:0xa, sex:false} ] b = [ 0xc0000044c0 | main.animal5{name:"girrrrrl", age:0x1, sex:true}]
b = [ 0xc0000044c0 |main.animal5{name:"girrrrrl", age:0x1, sex:true} ] d = [ 0xc000004540 | main.animal5{name:"girrrrrl", age:0x1, sex:true}]
b = [ 0xc0000044c0 |main.animal5{name:"girllll", age:0x1, sex:true} ] c = [ 0xc0000044c0 | &main.animal5{name:"girllll", age:0x1, sex:true}]
b = [ 0xc0000044c0 |main.animal5{name:"ggggirl", age:0x1, sex:true} ] c = [ 0xc0000044c0 | &main.animal5{name:"ggggirl", age:0x1, sex:true}]
*/
- 结构体类型赋值是值传递,因为这会重新分配新的内存,以上例子(d := b)中b和d的地址分别是0xc0000044c0 和 0xc000004540 。因此会用性能损耗。
- 可以通过定义指针变量,来避免值传递,但是当对c修改时会影响b,反之修改b也是影响c指向的值,以上例子中c的值是0xc0000044c0,实际上是b的地址
- 可以使用(*T).field 的或者T.field来获取值,或者修改值。类型+’.’+field
结构体定义字段域时,只有类型名没有字段名的字段叫做匿名字段。匿名字段只有类型没有字段名,意味着同一个类型的匿名字段只能有一个,不能重复。
匿名字段可以模拟面向对象中的继承,但是并不是真正的继承。
/**
匿名字段
*/
type Person struct {
name string
agt int
id string
addr string
}
type Employee struct {
Person
salary int
int
//Duplicate field 'int'
//int
//int* //Duplicate field 'int'
addr string
}
func main() {
e := Employee{
Person: Person{
name: "name",
agt: 10,
id: "xxxx001",
addr: "person addr",
},
salary: 49,
int: 333,
addr: "employee",
}
fmt.Printf("%#v \n", e)
}
/**
output:
main.Employee{Person:main.Person{name:"name", agt:10, id:"xxxx001", addr:"person addr"}, salary:49, int:333, addr:"employee"}
*/
*匿名字段可以是结构体,也可以是普通的内置类型。就想结构体类型Employee 第三个字段int,只定义了类型int,没有指定变量,因此int类型的匿名字段只能存在一个。包括int ,也不行。负责就报【Duplicate field ‘int’】的错误
不能存在多个同一类型和其类型的指针的匿名变量。
实际上来说初始化的时候可以使用 int:xxx进行初始化。也就是【类型名:值】的方式进行初始化。
除接口、指针、多记指针意外的任何命名类型都可以作为匿名字段
type myInt int
type myIntP *myInt
type d struct {
myInt
//myIntP//Embedded type cannot be a pointer
//*myInt//Duplicate field 'myInt'
}
因未命名类型没有名字标识,无法作为匿名字段。????—还是没有搞明白。。。TODO
相同字段采用最外层优先访问,类重载类似,这样也可以模拟出继承的特性,子类可以直接使用父类的成员变量。但是一旦命名重复,那么需要显示使用字段名访问。
type file struct {
name string
}
type data struct {
file
name string
log
}
func main() {
d := data{
file: file{
name: "filename",
},
name: "dataname",
}
fmt.Printf("%#v\n", d)
//最外层的name
d.name = "dataname2"
//使用匿名字段的log.name1
d.name1 = "logname1"
//当字段名重复以后必须显示的进行使用d.匿名字段类型.字段值
d.file.name = "filename"
fmt.Printf("%#v\n", d)
}
/**
output:
main.data{file:main.file{name:"filename"}, name:"dataname", log:main.log{name1:""}}
main.data{file:main.file{name:"filename"}, name:"dataname2", log:main.log{name1:"logname1"}}
*/
使用匿名字段就意味着存在命名重复的情况,默认情况下,编译优先查找显示的命名字段,然后如果匿名字段不存在,就开始在匿名字段成员中查找字段,当匿名字段的子项和命名字段同名,就需要手工使用显示字段名。就像上面例子中:d.file.name = "filename"是显示的使用。因为data类型中游name字段。
Tag字段标签是go中新增加的特性,是对字段进行描述的元数据。可以再运行的时候通过反射的机制读取。Tag在结构体字段的后面定义,通过反引号标记。
A field declaration may be followed by an optional string literal tag, which becomes an attribute for all the fields in the corresponding field declaration. An empty tag string is equivalent to an absent tag.
tag语法
struct {
field fileType `key1:"value" key:"value2"`
}
type user struct {
name string `昵称`
sex byte `性别`
}
func main() {
u := user{
name: "u_name",
sex: 0,
}
v := reflect.ValueOf(u)
t := v.Type()
for i, n := 0, t.NumField(); i < n; i++ {
//打印每个字段的tag和对应的值
fmt.Printf("%s: %v\n", t.Field(i).Tag, v.Field(i))
}
}
/**
output:
昵称: u_name
性别: 0
*/
type User struct {
Name string
Age int
weight uint
}
type User2 struct {
Name string `json:"姓名"`
Age int `json:"age_"`
weight uint `json:"WEI"`
}
func main() {
u := &User{
Name: "jack ma",
Age: 99,
weight:10,
}
fmt.Println(u)
bytes, _ := json.Marshal(u)
//{"Name":"jack ma","Age":99} 没有weight,因为是小写代码开头,
//go语言中小写开头的字段为private因此如果需要序列化就得大写。并且对外暴露
//可以通过tag实现json的序列化。
fmt.Printf("%v\n", string(bytes))
u2 := &User2{
Name: "jack ma",
Age: 99,
weight:10,
}
fmt.Println(u)
//使用tag和json库配合来进行灵活的序列化转换
bytes2, _ := json.Marshal(u2)
fmt.Printf("%v\n", string(bytes2))
}
/**
&{jack ma 99 10}
{"Name":"jack ma","Age":99}
&{jack ma 99 10}
{"姓名":"jack ma","age_":99}
*/
type User3 struct {
Name string `json:"姓名" pack:"name"`
Age int `json:"age_"`
weight uint `json:"WEI"`
}
func main() {
u := User3{
Name: "ttt",
Age: 0,
weight: 0,
}
v := reflect.ValueOf(u)
t := v.Type()
for i, n := 0, t.NumField(); i < n; i++ {
//打印每个字段的tag和对应的值
fmt.Printf("%s i [%v]|[%v] \n", t.Field(i).Tag, t.Field(i).Tag.Get("json"), t.Field(i).Tag.Get("pack"))
}
}
/**
output:
json:"姓名" pack:"name" [姓名]|[name]
json:"age_" [age_]|[]
json:"WEI" [WEI]|[]
*/
Tag最常用的场景就是json序列化和反序列化。
4. Tag是类型的一部分,Tag不同意味着类型也不同
空接口-struct{},没有字段域,struct{}的变量或者struct{}为元素的数组的长度均为0.
/*
空结构
*/
func main() {
var a struct{}
var b [100]struct{}
println(unsafe.Sizeof(a))
println(unsafe.Sizeof(b))
}
/**
output:
0
0
*/
struct{}元素的数组,不会分配类型,但是可以进行切片操作。
func main() {
var b [100]struct{}
s := b[:]
s2 := b[10:]
fmt.Println(cap(s), len(s), s[8])
fmt.Println(cap(s2), len(s), s2[8])
fmt.Printf("%p %p %p\n", &b[0], &b[50], &b[99])
}
/**
output:
100 100 {}
90 100 {}
0x596c18 0x596c18 0x596c18
*/
以上可以看出来struct{}类型的元素组成的数组,元素的地址都是同一个,实际上0x596c18 是运行时的runtime.zerobase变量。
func main() {
exitChan := make(chan struct{})
go func() {
println("go rountine running")
exitChan <- struct{}{}
}()
<-exitChan
println("sub go runtine exit")
}
/**
output:
go rountine running
sub go runtine exit
*/
对齐基本和c语言的方式一致。按照4的倍数不足进行填充。
示例后续补充—TODO