文章目录
- Struct
- 定义struct
- 构造struct实例
- struct的值和指针
- 在与函数共用时:
- 匿名字段和嵌套struct
- 嵌套struct的名称冲突问题
Struct
是一个值类型的
定义struct
type identifier struct {
field1 type1
field2 type2
…
}
// 或者
type T struct { a, b int }
理论上,每个字段都是有具有唯一性的名字的,但如果确定某个字段不会被使用,可以将其名称定义为空标识符_
来丢弃掉:
type T struct {
_ string
a int
}
每个字段都有类型,可以是任意类型,包括内置简单数据类型、其它自定义的struct类型、当前struct类型本身、接口、函数、channel等等。
如果某几个字段类型相同,可以缩写在同一行:
type mytype struct {
a,b int
c string
}
构造struct实例
定义了struct,就表示定义了一个数据结构,或者说数据类型,也或者说定义了一个类。总而言之,定义了struct,就具备了成员属性,就可以作为一个抽象的模板,可以根据这个抽象模板生成具体的实例,也就是所谓的"对象"。
例如:
type person struct{
name string
age int
}
// 初始化一个person实例
var p person
这里的p就是一个具体的person实例,它根据抽象的模板person构造而出,具有具体的属性name和age的值,虽然初始化时它的各个字段都是0值。换句话说,p是一个具体的人。
struct初始化时,会做默认的赋0初始化,会给它的每个字段根据它们的数据类型赋予对应的0值。例如int类型是数值0,string类型是"",引用类型是nil等。
因为p已经是初始化person之后的实例了,它已经具备了实实在在存在的属性(即字段),所以可以直接访问它的各个属性。这里通过访问属性的方式p.FIELD
为各个字段进行赋值。
// 为person实例的属性赋值,定义具体的person
p.name = "longshuai"
p.age = 23
获取某个属性的值:
fmt.Println(p.name) // 输出"longshuai"
也可以直接赋值定义struct的属性来生成struct的实例,它会根据值推断出p的类型。
var p = person{name:"longshuai",age:23}
p := person{name:"longshuai",age:23}
// 不给定名称赋值,必须按字段顺序
p := person{"longshuai",23}
p := person{age:23}
p.name = "longshuai"
如果struct的属性分行赋值,则必须不能省略每个字段后面的逗号",",否则就会报错。这为未来移除、添加属性都带来方便:
p := person{
name:"longshuai",
age:23, // 这个逗号不能省略
}
除此之外,还可以使用new()函数或&TYPE{}
的方式来构造struct实例,它会为struct分配内存,为各个字段做好默认的赋0初始化。它们是等价的,都返回数据对象的指针给变量,实际上&TYPE{}
的底层会调用new()。
p := new(person)
p := &person{}
// 生成对象后,为属性赋值
p.name = "longshuai"
p.age = 23
使用&TYPE{}
的方式也可以初始化赋值,但new()不行:
p := &person{
name:"longshuai",
age:23,
}
选择new()还是选择&TYPE{}
的方式构造实例?完全随意,它们是等价的。但如果想要初始化时就赋值,可以考虑使用&TYPE{}
的方式。
struct的值和指针
package main
import (
"fmt"
)
type person struct {
name string
age int
}
func main() {
p1 := person{}
p2 := &person{}
p3 := new(person)
fmt.Println(p1)
fmt.Println(p2)
fmt.Println(p3)
}
输出:
{ 0}
&{ 0}
&{ 0}
可见p1,p2,p3是不一样的
p1、p2、p3都是person struct的实例,但p2和p3是完全等价的,它们都指向实例的指针,指针中保存的是实例的地址,所以指针再指向实例,p1则是直接指向实例。这三个变量与person struct实例的指向关系如下:
变量名 指针 数据对象(实例)
-------------------------------
p1(addr) -------------> { 0}
p2 -----> ptr(addr) --> { 0}
p3 -----> ptr(addr) --> { 0}
p1和ptr(addr)保存的都是数据对象的地址,p2,p3则保存着ptr的地址。
不过要注意,p1.name和p2.name都可以访问实例的属性.
再看看
var p4 *person
这样的定义也是一个指针,他指向person的对象,初始化是nil,即没有明确的方向。
图示:
p4 -> ptr(nil)
既然p4是一个指针,那么可以将&person{}
或new(person)
赋值给p4。
var p4 *person
p4 = &person{
name:"longshuai",
age:23,
}
fmt.Println(p4)
上面的代码将输出:
&{longshuai 23}
在与函数共用时:
因为函数值传递时,是用的copy的方法,耗内存 所以可以直接传指针。
例如:
func add(p *person){...}
既然要传指针,那struct的指针何来?自然是通过&
符号来获取。分两种情况,创建成功和尚未创建的实例。
对于已经创建成功的struct实例p
,如果这个实例是一个值而非指针(即p->{person_fields}
),那么可以&p
来获取这个已存在的实例的指针,然后传递给函数,如add(&p)
。
匿名字段和嵌套struct
struct中的字段可以不用给名称,这时称为匿名字段。匿名字段的名称强制和类型相同。例如:
type animal struct {
name string
age int
}
type Horse struct{
int
animal //匿名字段
sound string
}
上面的Horse中有两个匿名字段int
和animal
,它的名称和类型都是int和animal。等价于:
type Horse struct{
int int
animal animal
sound string
}
显然,上面Horse中嵌套了其它的struct(如animal)。其中animal称为内部struct,Horse称为外部struct。
以下是一个嵌套struct的简单示例:
package main
import (
"fmt"
)
type inner struct {
in1 int
in2 int
}
type outer struct {
ou1 int
ou2 int
int
inner
}
func main() {
o := new(outer)
o.ou1 = 1
o.ou2 = 2
o.int = 3
o.in1 = 4
o.in2 = 5
fmt.Println(o.ou1) // 1
fmt.Println(o.ou2) // 2
fmt.Println(o.int) // 3
fmt.Println(o.in1) // 4
fmt.Println(o.in2) // 5
}
上面的o
是outer struct的实例,但o
除了具有自己的显式字段ou1和ou2,还具备int字段和inner字段,它们都是嵌套字段。一被嵌套,内部struct的属性也将被外部struct获取,所以o.int
、o.in1
、o.in2
都属于o
。也就是说,外部struct has a 内部struct
,或者称为struct has a field
。
上面的outer实例,也可以直接赋值构建:
o := outer{1,2,3,inner{4,5}}
在赋值inner中的in1和in2时不能少了inner{}
,否则会认为in1、in2是直接属于outer,而非嵌套属于outer。
嵌套struct的名称冲突问题
假如外部struct中的字段名和内部struct的字段名相同,会如何?
有以下两个名称冲突的规则:
- 外部struct覆盖内部struct的同名字段、同名方法
- 同级别的struct出现同名字段、方法将报错
package main
import "fmt"
type A struct {
a int
b int
}
type B struct {
b float32
c string
d string
}
type C struct {
A
B a string
c string
}
func main() {
var c C
c.a = "ca"
fmt.Println(c.a)
}
按照规则(1),直属于C的a和c会分别覆盖A.a和B.c。可以直接使用c.a、c.c分别访问直属于C中的a、c字段,使用c.d或c.B.d都访问属于嵌套的B.d字段。如果想要访问内部struct中被覆盖的属性,可以c.A.a的方式访问。
按照规则(2),A和B在C中是同级别的嵌套结构,所以A.b和B.b是冲突的,将会报错,因为当调用c.b的时候不知道调用的是c.A.b还是c.B.b。