结构体struct
结构体是一种聚合的数据类型, 是由零个或多个任意类型的值聚合成的实体。每个值称为结构体的成员。
下面声明了一个叫Employee的结构体类型,结构体变量的成员可以通过点操作符访问。
type Employee struct {
ID int
Name string
Address string
DoB time.Time
Position string
Salary int
ManagerID int
}
var zhexiao Employee
func main() {
fmt.Println(zhexiao.ID)
}
通过指针访问:
func main() {
var zhexiaoPtr *Employee = &zhexiao
zhexiaoPtr.Address = "Wuhan"
(*zhexiaoPtr).ManagerID = 123
name := &zhexiao.Name
*name = "ZHE"
fmt.Println(zhexiao.Name)
fmt.Println(zhexiao.Address)
fmt.Println(zhexiao.ManagerID)
}
注意:
下面的EmployeeByID函数返回对应的结构体的指针,如果将EmployeeByID函数的返回值从*Employee指针类型改为Employee值类型将会编译出错。这是因为在赋值语句的左边不能确定返回值是否是一个变量(函数如果返回的是值而不是指针)。
func EmployeeByID(id int) *Employee { /* ... */ }
id := zhexiao.ID
EmployeeByID(id).Salary = 0
如果成员类型相同,则可以被合并到一行。
type Employee struct {
ID int
Name, Address string
...
}
结构体成员的输入顺序也有重要的意义,顺序不同则代表定义了不同的结构体类型。
type EmployeeA struct {
ID int
Name string
}
type EmployeeB struct {
Name string
ID int
}
一个S的结构体类型将不能再包含S类型的成员:因为一个聚合的值不能包含它自身。但是可以包含*S指针类型的成员,这可以让我们创建递归的数据结构, 比如链表和树结构等。 下面是一个二叉树实现:
type tree struct {
val int
left, right *tree
}
func add(t *tree, val int) *tree {
if t == nil {
t = new(tree)
t.val = val
return t
}
//如果插入值小于节点值,则插入左子树
if val < t.val {
t.left = add(t.left, val)
} else {
t.right = add(t.right, val)
}
return t
}
func read(values []int, t *tree) []int {
if t != nil{
values = read(values, t.left)
values = append(values, t.val)
values = read(values, t.right)
}
return values
}
func main() {
var myTree *tree
var output []int
listData := []int{10, 1, 5, 2, 17, 19, 6, 20}
for _, v := range listData{
myTree = add(myTree, v)
}
output = read(output, myTree)
fmt.Println(output)
}
结构体字面值
结构体字面值可以指定每个成员的值,下面这种写法需要记住结构体成员的类型和顺序,一般在包内部使用。
type Point struct{ X, Y int }
p := Point{1, 2}
下面的这种写法更加常用,如果成员被忽略的话将使用默认零值。
type Employee struct {
ID int
Name string
Age int
}
func main(){
zx := Employee{ID:1, Name:"zhexiao"}
fmt.Println(zx) //{1 zhexiao 0}
}
结构体可以作为函数的参数和返回值,如果考虑效率的话,较大的结构体通常会用指针的方式传入和返回。
func age(e *Employee) int {
return e.Age + 1
}
如果要在函数内部修改结构体成员的话,用指针传入是必须的;因为在Go语言中,所有的函数参数都是值拷贝传入的,函数参数将不再是函数调用时的原始变量。
func age(e *Employee) {
e.Age = e.Age + 1
}
结构体通常用指针处理,可用下面的写法来创建结构体指针并初始化成员。
func main(){
zx := &Employee{Name:"zhexiao"}
fmt.Println(zx.Name)
// 与上面等价
zz := new(Employee)
*zz = Employee{Name:"123"}
fmt.Println(zz.Name)
}
结构体比较
如果结构体的全部成员都是可以比较的, 那么结构体也是可以比较的。
type Point struct{ X, Y int }
p := Point{1, 2}
q := Point{2, 1}
fmt.Println(p == q) // "false
可比较的结构体类型和其他可比较的类型一样, 可以用于map的key类型。
func main() {
zx := Employee{Name: "zhexiao"}
hints := make(map[Employee]int)
hints[zx] = 1
fmt.Println(hints)
}
结构体嵌入和匿名成员
结构体嵌入机制让一个命名的结构体包含另一个结构体类型的匿名成员。
匿名成员:Go语言有一个特性让我们只声明一个成员对应的数据类型而不指名成员的名字。匿名成员的数据类型必须是命名的类型或指向一个命名的类型的指针。
得意于匿名嵌入的特性, 我们可以直接访问叶子属性而不需要给出完整的路径。
type Point struct {
X, Y int
}
type Circle struct {
Point
Radius int
}
type Wheel struct {
Circle
Spokes int
}
func main() {
var w Wheel
w.X = 8 // 等同 w.Circle.Point.X = 8
w.Y = 8 // 等同 w.Circle.Point.Y = 8
w.Radius = 5 // 等同 w.Circle.Radius = 5
w.Spokes = 20
fmt.Println(w)
}
结构体字面值必须遵循类型声明时的结构,下面2种语法:
w = Wheel{Circle{Point{8, 8}, 5}, 20}
w = Wheel{
Circle: Circle{
Point: Point{X: 8, Y: 8},
Radius: 5,
},
Spokes: 20,
}
fmt.Printf("%#v\n", w)
注意:Printf函数中包含的#副词,表示语法打印值。
在 Go 中,当变量或函数的首字母大写的时候,会被从包中导出(在包外部可见,或者说公有的)。如果上面的例子Circle变为了circle,在包内部没问题,依然可以访问。但是在包外部, 因为circle没有导出不能访问它们的成员, 因此简短的匿名成员访问语法也是禁止的。