struct
golang语言中,struct有着至关重要的作用,struct用来定义一个抽象的数据结构。在程序设计中,首先对各种事物进行特征分析,提炼出各个事物最主要的特征信息,然后按照这些特征信息进行归类。每一个分类,有着自己的特征信息,且对于这个类而言,这些特征信息又不可或缺,正是有了这些零散的特征信息,才能更为形象的描述这个类。在程序设计中,基于类的设计,能够更好的保证数据完整性,也能更加准确的表达一个事物。golang中可以使用struct来定义包含诸多特征信息的抽象数据类型,也可以称之为类。定义结构体的语法格式是:
type name struct{
field1 dataType
field2 dataType
...
}
定义和使用结构体
定义结构体使用type和struct关键字组合完成。结构体中字段的作用域与字段的第一个字母有关,当结构体中字段第一个字母是大写时,能够在任何地方访问,如果结构体字段第一个字母不是大写,则只能在同一个包中被访问。下边这个示例,创建一个学生基本信息结构体,并对这个结构体进行初始化操作:
package main
import (
"fmt"
)
// 学生基础信息
type Student struct {
// 学号
Id int
// 学生姓名
Name string
// 学生年龄
Age int
// 手机号
Mobile int
// 专业
Major string
}
func main() {
// 创建Student指针类型变量
s1 := new(Student)
// 创建Student指针类型变量
s2 := &Student{}
// 创建Student类型变量
s3 := Student{}
// 创建Student指针类型变量, 并根据字段名附初始值
s4 := &Student{
Id: 20170901,
Name: "hzwy23",
Age: 18,
Mobile: 18107217021,
Major: "move brick",
}
// 创建Student类型变量,并根据字段在结构体中的顺序附初始值
s5 := Student{
20170901,
"hzwy23",
18,
18107217021,
"move brick",
}
fmt.Println("s1:", s1)
fmt.Println("s2:", s2)
fmt.Println("s3:", s3)
fmt.Println("s4:", s4)
fmt.Println("s5:", s5)
}
上边示例中可以看出,定义结构体变量通常有两种方式:第一种是使用new关键字创建指针类型变量,如上边示例代码中的变量s1;第二种是直接在结构体名称后边加上大括号({}),如上边示例代码中的变量s3。
通过在结构体后边加大括号({})的方式创建变量与使用new关键字创建对象不同之处在于new创建出来的是指针类型,而结构体名加大括号({})创建出来的不是指针类型。如果在使用结构体名加大括号({})创建变量时,在结构体名前边加上地址操作符(&),那么这样创建出来的也是指针类型变量,效果就与new关键字一样了,如上边示例代码中,s1与s2都是指针类型变量。
怎么操作结构体中的字段呢?
在程序中,可以通过结构体类型变量名加上点号(.),再加上字段名的方式访问结构体中的字段。如访问学生姓名的操作是:
s1.Name = "hi hzwy23"
s3.Name = "hello hzwy23"
fmt.Println(s3.Name)
fmt.Println(s1.Name)
不管变量是指针类型,还是非指针类型,操作结构体字段,都是一样的方法,如上边示例中s1是指针类型,s3是非指针类型,在访问字段名为Name的字段时,都是采用点号(.)加上字段名的方式访问。这一点与C/C++语言不同。
匿名字段
在定义struct中字段时,通常都是先写字段名,然后在后边跟上字段类型,这种方式定义的字段是常规字段。还有一种字段,叫做匿名字段,就是在结构体中,只有类型没有名字的字段。那么匿名字段的语法格式是什么呢?
type name struct{
dataType
dataType
...
}
哪些类型可以当做匿名字段使用呢?
golang中所有的类型都可以当做匿名字段使用。当struct出现了匿名字段后,可以理解成这个字段的名字就是类型名称,如定义一个int类型命名字段格式
type name struct{
int
}
匿名字段可以理解为下边的形式:
type name struct{
int int
}
相当于结构体中有一个int类型的字段,字段名称是int。
匿名字段没有名字,怎么访问呢?
下面通过一段示例代码讲解匿名字段访问方法:
package main
import "fmt"
type name struct {
// int类型匿名字段
int
}
func main() {
n := &name{
int: 8,
}
fmt.Println(n.int)
}
上边的示例代码中,首先定义结构体name类型的变量n,通过n.int,也就是变量名加上点号(.),再加上类型名的方式访问了匿名字段。
上述示例讲解了匿名字段是内置基本类型时的情况,那么当匿名字段是自定义结构体类型时,会是什么样的情况呢?
当匿名字段是结构体时,那么这个匿名字段的默认名字就是这个结构体的名字。与内置基本类型不同的是,当匿名字段是结构体时,这个匿名字段中所有的信息将会传递给父级结构体。请看下边一段示例:
package main
import (
"fmt"
)
// 学生基础信息
type Student struct {
// 学号
Id int
// 学生姓名
Name string
// 学生年龄
Age int
// 手机号
Mobile int
// 专业
Major string
int
}
type Demo struct {
Name string
Student
}
func main() {
// 创建Student类型变量,并根据字段在结构体中的顺序附初始值
s := Student{
20170901,
"hzwy23",
18,
18107217021,
"move brick",
18,
}
de := &Demo{
Name: "test demo",
Student: s,
}
fmt.Println("demo name is:", de.Name)
// 指定匿名字段类型访问匿名字段中的信息
fmt.Println("student name is:", de.Student.Name)
// 不指定匿名字段类型,直接访问匿名字段中的信息
fmt.Println("student id is:", de.Id)
fmt.Println("student int is:", de.int)
}
输出信息是:
demo name is: test demo
student name is: hzwy23
student id is: 20170901
student int is: 18
Student是Demo中的匿名字段,Demo类型的变量在访问Student中的字段时,如访问学生名字字段的方法是 de.Student.Name,访问学生信息中int类型匿名字段方法是:de.int。也就是说,外层结构体可以直接访问匿名字段内部的全部信息,当然如果匿名字段内的非导出字段或方法,只能在同一个包中被访问。
既然匿名字段的名字是类型名称,那么自然而然一个结构体重,不允许出现两个类型一样的匿名字段。如下边一个错误的示例:
// 错误示例:
type name struct {
int
int
}
匿名字段内部冲突问题
当结构体A和结构体B都拥有Name这个字段时,在结构体C中使用结构体A和B作为匿名字段,那么C中就会出现两个Name,这样会不会冲突呢?请看下边示例:
package main
import "fmt"
type A struct {
ID string
Name string
}
type B struct {
ID string
Name string
}
type C struct {
A
B
ID string
}
func main() {
a := A{
ID: "AAA",
Name: "struct A",
}
b := B{
ID: "BBB",
Name: "struct B",
}
c := C{
A: a,
B: b,
ID: "ccc",
}
fmt.Println(c.A.Name)
fmt.Println(c.B.Name)
fmt.Println(c.ID)
//ambiguous selector c.Name
//fmt.Println(c.Name)
}
输出信息:
struct A
struct B
ccc
结构体C中接收了A中的Name,也接收了B中的Name,如果使用结构体C的变量c直接来访问Name,如 c.Name,那么到底访问的是A中的字段Name,还是访问的B中的字段Name?在编译时会出现如下错误:
ambiguous selector c.Name
由于访问字段不明确,所以直接导致编译错误。那么对于这种情况,该怎么进行操作呢?将上边访问Name字段的语句换成下边情况:
fmt.Println(c.A.Name)
fmt.Println(c.B.Name)
输出信息是:
struct A
struct B
通过在变量c和字段Name之间加上匿名字段类型名,就可以解决上述字段访问模糊不清的情况。
另外3个结构体中都有名称为ID的字符串类型字段,golang会首先访问最外层的ID字段,也就是读取结构体C中的ID字段。如果外层没有ID字段,则情况就与Name字段相同了,必须显示的指定具体的匿名字段类型名,然后根据匿名字段类型名,访问下边的字段。