复杂数据类型
也叫派生数据类型、聚合数据类型
数组
数组是一个由固定长度的特定类型元素组成的序列,一个数组可以由零个或多个元素组成
数组的长度是数组类型的一个组成部分,因此 [3]int 和 [4]int 是两种不同的数组类型。数组的长度必须是常量表达式,因为数组的长度需要在编译阶段确定
在 Go 语言中数组是值类型而不是引用类型,因此参数传递时是对数组进行拷贝
var a [3]int
var a [3]int = [3]int{1, 2, 3}
a := [...]int{1, 2, 3} // 省略号表示数组的长度是根据初始化值的个数来计算
slice
切片(slice)代表变长的序列,序列中每个元素都有相同的类型。一个切片类型一般写作 []T,其中 T 代表切片中元素的类型
一个 slice 由三个部分构成:指针、长度和容量
多个 slice 之间可以共享底层的数据,并且引用的数组部分区间可能重叠
a := make([]int, 12)
a := []int{} // 空切片
a := []int // nil切片,指针为空
数组和切片的区别?
- 数组是值类型,切片是引用类型
- 数组的长度是固定的,而切片的长度是动态变化的
- 切片比数组多了一个属性:容量(cap)
- 切片的底层是数组
指针
Go 语言保留了 C 语言中的指针,指针用来指向变量的位置。Go 指针不支持算术运算,不能随意更改类型
a := 1
p1 := &a
p2 := new(int64)
var p3 *[5]int // 数组指针
var p4 [5]*int // 指针数组
map
哈希表是一种巧妙并且实用的数据结构。它是一个无序的 key/value 对的集合,其中所有的 key 都是不同的,然后通过给定的 key 可以在常数时间复杂度内检索、更新或删除对应的 value
在 Go 语言中,一个 map 就是一个哈希表的引用,map 类型可以写为 map[K]V,其中 K 和 V 分别对应 key 和 value。map 中所有的 key 都有相同的类型,所有的 value 也有着相同的类型,但是 key 和 value 之间可以是不同的数据类型。其中 K 对应的 key 必须是支持 == 比较运算符的数据类型(map、slice 和 func 不支持),所以 map 可以通过测试 key 是否相等来判断是否已经存在
ages := make(map[string]int) // mapping from strings to ints
ages := map[string]int{}
chan
Go 语言中的通道(channel)是一种特殊的类型。在任何时候,同时只能有一个 goroutine 访问通道进行发送和获取数据。goroutine 间可以通过通道进行通信。通道像一个传送带或者队列,总是遵循先入先出(First In First Out)的规则,保证收发数据的顺序。当通道为空或者满时,对通道的获取或发送操作将会被阻塞
var ch1 chan int
ch2 := make(chan int) // 无缓冲通道
ch3 := make(chan int, 2) // 有缓冲通道
ch3 <- 0 // 将0放入通道中
ch4 := make(chan<- int) // 单向通道,只能发送
ch5 := make(<-chan int) // 单向通道,只能接收
结构体
结构体是一种聚合的数据类型,是由零个或多个任意类型的值聚合成的实体。每个值称为结构体的成员
type Student struct {
Name string
Age int
}
空结构
struct{}
嵌入字段
Go 语言规范规定,如果一个字段的声明中只有字段的类型名而没有字段的名称,那么它就是一个嵌入字段,也可以被称为匿名字段
若嵌入字段的字段与结构体中的字段发生同名,则会产生 屏蔽 现象,屏蔽 现象会以嵌入的层级为依据,下一嵌入层级的字段会被当前层级的同名字段所 屏蔽
type Class struct {
Name string
}
type Student struct {
Name string
Age int
Class // 嵌入字段
}
JSON
JSON 是对 JavaScript 中各种类型的值——字符串、数字、布尔值和对象——Unicode 本文编码。它可以用有效可读的方式表示基础数据类型和数组、slice、结构体和 map 等聚合数据类型
type Movie struct {
Title string
Year int `json:"released"`
Color bool `json:"color,omitempty"`
Actors []string
}
接口
接口类型具体描述了一系列方法的集合,一个实现了这些方法的具体类型是这个接口类型的实例。接口类型不能被实例化
type ReadWriter interface {
Reader
Writer
}
Go 语言对接口实现的规定如下:
- 实现接口的方法时,如果接收者都是T类型,称作 T 类型实现了该接口
- 实现接口的方法是,如果接收者都是
*T类型,称作*T类型实现了该接口 - 如果方法的接收者为 T 类型,可以使用任意
*T类型的接收者来调用,因此在 Go 语言中规定,如果通过 T 类型实现了一个接口,那么*T类型也自动实现了该接口 - 如果方法的接收者为
*T类型,仅能通过可取地址的 T 类型接收者来调用,不能用不可取地址的 T 类型接收者(比如字面量)来调用,因此在 Go 语言中规定,如果通过*T类型实现了一个接口,那么 T 类型并没有实现该接口
接口变量
当使用接口类型声明一个变量,这个变量称作接口变量。给接口变量赋值需要满足如下条件:仅当一个表达式的值为 nil 或实现了该接口时,这个表达式才可以赋值给该接口变量
- 如果把 T 类型的实例赋值给接口变量,那么将拷贝该实例的数据结构到接口变量中
- 如果把
*T类型的实例指针赋值给接口变量,那么仅拷贝指针值到接口变量中 - 如果将一个接口变量赋值给另一个接口变量,两个接口变量将会引用同一个实例
interface{}
interface{} 不包含任何方法,正因为如此,所有的类型都实现了 interface{}。interface{} 可用来存储任意类型的值。它有点类似 C 语言的 void * 类型
接口值
接口值由两个部分组成,一个是具体的类型,另一个是该类型所对应的值。它们被称为接口的动态类型和动态值
在 Go 语言中,变量默认初始化为其零值。接口的零值就是把它的动态类型和动态值都设置为 nil
一个接口值是否为 nil 取决于它的动态类型,所以接口的零值是一个 nil 接口值,因为它的动态类型是 nil
如果两个接口值都是 nil 或者二者的动态类型完全一致且动态值都相等,那么两个接口值相等