结构体将多个不同类型字段组合成一个复合类型,字段名,排列顺序,字段标签属于类型组成部分。
方法
Go语言中,方法是与对象实例绑定的特殊函数
type Point struct { X, Y float64 }
// 这是包级别的函数
func Distance(p, q Point) float64 {
return math.Hypot(q.X - p.X, q.Y - p.Y)
}
// 这是类型point的方法
func (p Point) Distance(q Point) float64 {
return math.Hypot(q.X - p.X, q.Y - p.Y)
}
func main() {
p := Point{1, 2}
q := Point{4, 6}
fmt.Println(Distance(p, q))
fmt.Println(p.Distance(q))
}
第一个声明是包级别的函数,第二个声明式类型Point
的方法。
因为每一个类型有它自己的命名空间,所以我们能在其他不同类型中使用名字Distance
作为方法名。
type Path []Point
func (path Path) Distance() float64 {
sum := 0.0
for i := range path {
if i > 0 {
sum += path[i-1].Distance(path[i])
}
}
}
Path
是一个命名的slice类型,而非Point这样的结构体,但我们依旧能够给他定义方法。
Go和许多其他面向对象语言不同,可以将方法绑定到任何类型上,可以很方便的为简单的类型(如数字,字符串,slice,map,甚至函数)定义任何附加的行为。
- 指针接收者的方法
因为函数式传值调用,所以当我们希望避免复制整个实参,或者想要修改实参时,就可以使用指针来传递变量,这同样适合于接收者。
从上面输出可以看出来:当使用T类型作为接收者时,并没有改变变量的内部状态,但是使用*T类型时改变了变量的内部状态。type Point struct { X float64 Y float64 } func (p Point) test() { p.X = 2.2 p.Y = 3.2 } func (p *Point) test2() { p.X = 2.2 p.Y = 3.2 } func main() { p := Point{1.1, 2.1} fmt.Println(p.X, p.Y) // 1, 2 p.test() fmt.Println(p.X, p.Y) // 1, 2 (&p).test2() fmt.Println(p.X, p.Y) // 2.3 2.34 }
所以如何选择方法的receiver类型?
- 要修改实例状态,使用*T,无需修改状态的小对象或者固定值,使用T
- 大对象建议使用*T,以减少复制成本
- 若包含Mutex等同步字段,用*T,避免因复制造成锁操作无效
- 引用类型,字符串,函数等指针包装对象,直接用T
- 其他无法确定的情况,都用*T
方法集
空结构体
空结构类型指没有字段的结构类型,比较特殊,无论其自身,还是作为数组元素类型,其长度都为0,但作为数组类型时,对应的数组仍然可以操作元素,对应的切片 len,cap 操作也正常。
func main() {
var a struct{}
var b [100]struct{}
fmt.Println(unsafe.Sizeof(a), unsafe.Sizeof(b)) // 0 0
fmt.Println(b[3], len(b), cap(b)) // {} 100 100
}
空结构可作为channel元素类型,用于事件通知。
匿名字段
结构体嵌套
内存对齐
struct 的比较性
-
go 语言中哪些是可以比较的:
- 可比较: Integer,Floating-point,String,Boolean,Complex(复数型),Pointer,Channel,Interface,Array
- 不可比较:Slice,Map,Function
-
同一 struct 的实例可以比较吗?
type S struct { Name string Age int Address *int } func main() { a := S{Name: "aa", Age: 1, Address: new(int)} b := S{Name: "aa", Age: 1, Address: new(int)} fmt.Println(a == b) // 输出false }
如果给结构体S增加一个Slice类型的成员变量后又是什么情况呢?
type S struct { Name string Age int Address *int Nums []int } func main() { a := S{Name: "aa", Age: 1, Address: new(int), Nums: []int{1, 2, 3, 4}} b := S{Name: "aa", Age: 1, Address: new(int), Nums: []int{1, 2, 3, 4}} fmt.Println(a == b) // invalid operation: a == b (struct containing []int cannot be compared) }
所以结论:同一个 struct 的两个实例可比较也不可比较,当结构不包含不可直接比较成员变量时可直接比较,否则不可直接比较。
-
reflect.DeepEqual
当需要对含有不可直接比较的数据类型的结构体实例进行比较时,我们可以借助 reflect.DeepEqual 函数 来对两个变量进行比较:type S struct { Name string Age int Address *int Nums []int } func main() { a := S{Name: "aa", Age: 1, Address: new(int), Nums: []int{1, 2, 3, 4}} b := S{Name: "aa", Age: 1, Address: new(int), Nums: []int{1, 2, 3, 4}} fmt.Println(reflect.DeepEqual(a, b)) // 输出为true }
- 不同类型的值永远不会深度相等
- 当两个数组的元素对应深度相等时,两个数组深度相等
- 当两个相同结构体的所有字段对应深度相等的时候,两个结构体深度相等
- 当两个函数都为nil时,两个函数深度相等,其他情况不相等(相同函数也不相等)
- 当两个interface的真实值深度相等时,两个interface深度相等
- map的比较需要同时满足以下几个:
- 两个map都为nil或者都不为nil,并且长度要相等
- 相同的map对象或者所有key要对应相同
- map对应的value也要深度相等
- 指针,满足以下其一即是深度相等
- 两个指针满足go的==操作符
- 两个指针指向的值是深度相等的
- 切片,需要同时满足以下几点才是深度相等
- 两个切片都为nil或者都不为nil,并且长度要相等
- 两个切片底层数据指向的第一个位置要相同或者底层的元素要深度相等,注意:空的切片跟nil切片是不深度相等的
- 其他类型的值(numbers, bools, strings, channels)如果满足go的==操作符,则是深度相等的。要注意不是所有的值都深度相等于自己,例如函数,以及嵌套包含这些值的结构体,数组等
struct 可以作为 map 的 key 吗?
struct
必须是可比较的,才能作为 key,否则编译时报错。