Golang 入门基础(四) - 内置数据类型(下)
channel 类型
channel
用于进行多线程管道通信与同步。
定义 channel
1 | a := make(chan <type>, [<size>]) |
1 | a := make(chan int) // 无缓冲区的 int 通道 |
- 注意:
channel
也是引用数据类型,直接使用var a chan int
是错误的,其没有构造chan
结构。
channel 读写
-
channel
具有缓冲区大小,读写会对channel
的缓冲区进行操作。当缓冲区为空时,读操作阻塞,知道新的写操作完成;当缓冲区为满时,写操作阻塞。 -
特殊地,当缓冲区大小为
0
时,所有读写操作都会阻塞等待直到channel
的读写操作配对。 -
channel
写
1 | <channel> <- <data> |
1 | a <- 1 |
channel
读
1 | <data> := <-<channel> |
1 | x := <-a |
channel
非阻塞读
1 | // 详见 Golang 入门基础(五) - 控制语句 - 条件控制语句 - select-case 语句 |
channel 应用
map 类型
定义 map
1 | a := make(map[<keyType>]<valueType>) |
1 | a := make(map[string]int) |
- 注意:
map
也是引用数据类型,直接使用var a map[<keyType>]<valueType>
是错误的,其没有构造map
结构。
操作 map
- 插入或修改元素
1 | a[<key>] = <value> // 如果元素存在, 插入值, 否则修改值 |
- 访问元素
1 | x := a[<key>] // 如果元素存在, 获取元素值, 否则返回 0 值 |
- 删除元素
1 | delete(<mapName>, <key>) // 如果元素存在, 删除元素, 否则不进行操作 |
- 遍历元素
1 | for key, value := range <mapName> { |
1 | for key := range <mapName> { |
map 原理
map
底层为优化的拉链式哈希表,因此map
是无序的。关于拉链式哈希表不多作介绍。map
对拉链式哈希表作的优化包括:- 链表内存访问较慢,
map
使用数组链表加速访问。数组链表是指每次申请一定数量的连续内存,连续内存溢出时再由连续内存链接到下一个申请的连续内存,一块连续内存可以存储多个键值对,这样一块连续内存称为overflow
(不包括第一块连续内存)。 - 键比对可能较慢(如以字符串作为键),
map
使用二级哈希值进行比对,在链表中仅当二级指针相同时才进行键比对。
- 链表内存访问较慢,
map
查找工作流程:- 使用
key
类型对应的哈希函数计算足够大的哈希值hashCode
,取hashCode
低B
位作为一级哈希值firstlyHashCode
,取hashCode
的高 8 位作为二级哈希值secondaryHashCode
,其中B
的值取决于哈希表扩容后的大小A
,B=log2(A)
。 - 以
firstlyHashCode
作为索引,索引到一个数组链表。 - 以
secodaryHashCode
作为键依次于数组链表的第一块连续内存中的secodaryHashCode
比较,如果相同则继续比较key
,如果相同则认为找到,否则继续向后找。 - 如果当前连续内存中没有找到,那么前往当前内存的下一个
overflow
以相同方式查找,如果没有更多overflow
,认为不存在。
- 使用
map
扩容策略:- 如果满足以下条件之一,
map
将申请一块2
倍的连续内存,并将原表数据逐一转储到新表。 - 扩容后,负载因子折半,溢出桶数量均摊折半。
- 如果满足以下条件之一,
函数
具名函数与匿名函数
- 函数按分类可以分为具名函数与匿名函数,函数是一个对象即一种数据类型,具名函数是一种特殊的匿名函数。
- 具名函数
1 | func <functionName>([parameterList...]) [returnTypes...] { |
1 | func DivMod(x int, y int) (int, int) { |
- 匿名函数
1 | <functionName> = f([parameterList...]) [returnTypes...] { |
1 | DivMod := func(x int, y int) (int, int) { |
函数传参
-
每项参数使用
parameterName parameterType
的格式,见3.8.1
的例子。 -
Golang
中没有引用类型,避免参数拷贝可使用指针类型*parameterType
。 -
Golang
中args...
表示解包,...args
表示封包,对应Python
中的*args
,其实际构造一个切片。
1 | Sum := func(a ...int) int { |
Golang
可以进行批量参数类型定义。
1 | func demo(a, b int, s, ss string) {} |
Golang
中没有函数默认值、函数重载。
函数返回
- 如果未指定函数返回值,需用
return
返回相同的返回值列表。 - 如果指定函数返回值名,直接使用
return
返回即可。此时函数返回值已定义,无需在函数体内定义。
1 | Sum := func(a ...int) (s int) { |
方法
Golang
实际没有类与对象的概念,方法是Golang
的类面向对象实现,它实现了对象与函数的静态绑定。其调用方式与面向对象相同。
1 | // 函数实现 |
1 | // 方法实现 |
- 方法的对象必须是本包结构体。例如
func (builder strings.Builder) <functionName>([parameterList...]) [returnTypes...]
是错误的,func (x int) <functionName>([parameterList...]) [returnTypes...]
是错误的。 - 值对象与指针对象是不同的,但它们的调用方式是相同的。以值对象传递,将对对象的拷贝操作;以指针对象传递,将直接操作对象。
接口
- 接口也是
Golang
中类面向对象的实现,接口的存在类似于面向对象中的虚基类。接口是方法的集合,任何包含所有接口定义的方法的结构体都满足接口。接口可以被赋值为任何满足接口的结构体(隐式类型转换),但不能使用非接口定义的字段、方法。
定义接口
- 定义接口。任何满足接口所有方法的结构体都可以赋值到接口。
1 | type Human interface { |
- 定义空接口。任何结构体都可以赋值到空接口,空接口在底层原理上与一般接口不同。
1 | type Any interface { |
- 叠加接口
1 | type Writer interface { |
- 匿名接口。在第一个示例中,
interface{}
即为匿名接口。
1 | var i interface{} = interface{}("Hello") |
动态类型
- 接口可以接收一切符合接口的类型,因此可以用于动态类型。
1 | package main |
类型断言
Golang
中接口支持返回到原始类型,判断接口原始类型的方法称为类型断言。类型断言使Golang
中的动态类型更加灵活。
类型判断
value
为接收原始类型的变量,其类型为originalType
;ok
为判断interfaceVariable
的原始类型是否为originalType
,判断为否,value
将没有意义。
1 | <value>, <ok> := <interfaceVariable>.(<originalType>) |
1 | human := Human(Me("Jamhus")) |
1 | It's not Man. |
类型分支
1 | switch human.(type) { |
评论