Golang 学习笔记 2-2 复合数据类型数组和结构体
Table of Contents
1 数组
golang 原生支持多维数组。
1.1 一维数组
数组的初始化及遍历方式:
var a [3]int // 三个整数的数组
var a [3]int = [3]int{1, 2, 3}
var a [3]int = [3]int{1, 2} // 第三个元素默认零值
q := [...]int{1, 2, 3} // 数组元素个数由初始化数组元素的个数决定
r := [...]int{99: -1} // 定义了有 100 个元素的数组, 最后一个元素是 -1
fmt.Println(a[len(a) - 1]) // 输出数组最后一个元素
for i, v := range a {
fmt.Println("%d %d\n") // 输出索引 i 和元素 v
}
数组很少直接使用,大多数情况下使用长度可变的 slice。
数组长度是数组类型的一部分,所以 [3]int
和 [4]int
是两种不同的数据类型。数组的长度必须是常量表达式,即,表达式的值在程序编译时就要确定。
q := [3]int{1, 2, 3}
q = [4]int{1, 2, 3, 4} // 编译错误,[4]int 不能赋值给 [3]int
Go 中的数组是值传递,而其他语言是隐式的引用传递。
1.2 多维数组
多维数组的声明方式: var variable_name [SIZE1][SIZE2]...[SIZEN] variable_type
数组的元素是数组:
a = [3][4]int{
{0, 1, 2, 3}, // 第一行索引为 0
{4, 5, 6, 7}, // 第二行索引为 1
{8, 9, 10, 11}, // 第三行索引为 2
}
fmt.Println(a) // "[[0 1 2 3] [4 5 6 7] [8 9 10 11]]"
2 结构体
2.1 结构体创建和初始化
结构体是将零个或多个任意类型的命名变量组合在一起的聚合数据类型。结构体成员通过 .
被访问。结构体零值由成员零值组成。
type Employee struct{
ID int
Name string
Address string
DoB time.Time
Position string
Salary int
ManagerID int
}
var dilbert Employee
position := &delbert.Position
*position = "Senior " + *position
var employeeOfTheMonth *Employee = &dilbert
employeeOfTheMonth.Position += " (proactice team leader)"
// 上面那条等价于
(*employeeOfTheMonth).Position += " (proactice team leader)"
结构体的成员变量通常一行只写一个,变量的名称在类型的前面,但是相同类型的连续成员变量可以写在一行上:
type Employee struct{
ID int
Name, Address string
DoB time.Time
Position string
Salary int
ManagerID int
}
结构体内变量顺序改变 -> 另一个结构体:
type Employee struct{
ID int
Name, Address string
DoB time.Time
Position string
Salary int
ManagerID int
}
// 上下两个结构体不同
type Employee struct{
Name, Address string
ID int
DoB time.Time
Position string
Salary int
ManagerID int
}
如果一个结构体成员变量名称大写,那么它是可导出的。结构体 T
的成员变量不能包含结构体 T
,但是可以包含指向 T
的指针;但是数组的元素可以是数组。 比如下面是一个二叉树的节点的实现:
type tree struct{
valut int
left, right *tree
}
有两种方式初始化结构体:
type Point struct { X, Y int}
p := Point{1, 2} // X=1, Y=2 必须记住成员变量的顺序
p := Point{Y: 2, X: 1} // X=1, Y=2 显式初始化
初始化不可导出的变量会报错:
package p
type T struct{ a, b int}
pakcage q
import "p"
var _ = p.T{a: 1, b: 2} // 编译错误,无法引用 a, b
var _ = p.T{1, 2} // 编译错误,无法引用 a, b
大型结构体通常使用结构体指针的方式直接传递给函数或者从函数中返回,这样节省内存分配产生的开销。在函数需要修改结构体内容的情况下,也要用结构体指针。
pp := &Point(1, 2)
// 上面的写法等同于
pp := new(Point)
*pp = Point{1, 2}
注: new
函数的作用是返回一个指向类型的 指针 *T
, make
的作用是为 slice, map 或 channel 初始化,并且返回 引用 T
。
2.2 结构体比较
两个同类结构体在比较时, ==
操作符按照成员变量顺序来进行比较。如果所有成员变量相同,则两个结构体相同。
type Point struct{ X, Y int}
p := Point{1, 2}
q := Point{2, 1}
fmt.Println(p == q) // `false`
fmt.Println(p.X == q.X || p.Y == q.Y) // `false`
和其他可比较类型一样,结构体也可以作为 map 的键。
type address struct{
hostname string
port int
}
hits := make(map[address]int)
hits[address{"golang.org", 443}]++
2.3 结构体嵌套和匿名成员
可以简单理解成 结构体的继承 。 Circle
定义了圆心坐标 X, Y
以及圆的半径; Wheel
拥有 Circle
的所有属性,还定义了 Spokes
:
type Circle struct{
X, Y, Radius int
}
type Wheel struct{
X, Y, Radius, Spokes int
}
很显然,上面的情况可以写成:
type Circle struct{
X, Y, Radius int
}
type Wheel struct{
Circle Circle
Spokes int
}
这样看上去更清晰了但是访问变量变得更复杂:
var w Wheel
w.Circle.X = 8
w.Circle.Y = 8
w.Circle.Radius = 5
w.Spokes = 20
Go 允许定义不带名称的结构体成员,只需要指定成员的类型即可,这种结构体成员叫 匿名成员 。匿名成员的类型必须是一个命名类型或者指向命名类型的指针。所以 Circle
和 Wheel
的定义可以简化成:
type Circle struct{
X, Y, Radius int
}
type Wheel struct{
Circle
Spokes int
}
有了这种结构体嵌套的功能,就可以直接访问我们需要的变量而不用指定一大串中间变量:
var w Wheel
w.X = 8 // 等价于 w.Circle.X = 8
w.Y = 8 // 等价于 w.Circle.Y = 8
w.Radius = 5 // 等价于 w.Circle.Radius = 5
w.Spokes = 20
但结构体字面量无法初始化有嵌套结构的结构体:
w = Wheel{8, 8, 5, 20} // 编译错误, 未知的成员变量
w = Wheel{X: 8, Y: 8, Radius: 5, Spokes: 20} // 编译错误, 未知的成员变量
// 下面的写法才会初始化含有嵌套结构的结构体
w = Wheel{
Circle{8, 8, 5},
20,
}
w = Wheel{
Circle{X: 8, Y: 8, Radius: 5},
Spokes: 20,
}
注意: 结构体嵌套时,如果父结构体发生了重合,那么,在访问时,需要注意层级问题。
type Point struct { X, Y int}
type Circle struct{
Point
Radius int
}
type Wheel struct{
Circle // 父结构体 Point
Point
Spokes int
}
w := Wheel{
Circle: Circle{
Point: Point{1, 1},
Radius: 0,
},
Point: Point{2, 2},
Spokes: 10,
}
fmt.Println(w.X) // `2`
fmt.Println(w.Point.X) // `2`
fmt.Println(w.Circle.X) // `1`
fmt.Println(w) // `{{{1 1} 0} {2 2} 10}`