- Basic Types
- Numbers
- intergers
- floating-point numbers
- complex numbers
- Strings
- Booleans
- Aggregate Types
- Arrays
- Structs
- Reference Types
- Pointers
- Slices
- Maps
- Functions
- Channels
- Interfaces
- Basic interfaces
- Embedded interfaces
- General interfaces
- 类型比较
- Range
- 创建变量
- 占位符
- JSON
- https://github.com/bytedance/mockey 中的 mock 是如何实现的
Basic Types
推荐用 new
来创建指针, int
类型还有对应的 untype 类型,作为对应的编译器常量,精度在 256 bits 以上。
Numbers
只有 intergers 有跟随编译器变化位数的 int
类型,其他都指定了位数,例如只有 float64
类型没有 float
类型。
intergers
- (signed, unsigned) * (four sizes: 8, 16, 32, 64) = 8 types
- int, uint, depend on particular platform
- rune: int32
- byte: uint8
- uintptr: width is not specified but is sufficient to hold all the bits of a pointer value.
一般和外界对接要指明符号和大小,但程序内更推荐使用int。
floating-point numbers
complex numbers
Strings
string
都是 immutable 的,所以直接拼接 string
很浪费内存,类似 java 中的 StringBuilder
,Go 中有 bytes.Buffer
来临时存放字符,最后再转化成 string。
所有 string
采用 UTF-8 编码,因此 string
的 len 不是其字符数量(UTF-8 编码有可能超过一个 byte
),可以用 utf8.RuneCountInString
来获得其中 rune
字符的长度。
rune
代表一个 UTF-8 字符,格式为:
- 0xxxxxx runes 0-127 (ASCII)
- 11xxxxx 10xxxxxx 128-2047 (Values < 128 unused)
- 110xxxx 10xxxxxx 10xxxxxx 2048-65535 (Values < 2048 unused)
- 1110xxx 10xxxxxx 10xxxxxx 65536-0x10ffff (Other value unused)
UTF-8 有如下特性:
- 为了找到一个字符的开头,不会往前回溯超过3个 bytes。
- 解码顺序从左到右,不需要 bookahead。
- 没有一个 UTF-8 编码是另一个编码的子串,这让查找很方便。
- UTF-8 字符的字典序等同于其编码顺序。
- 一个我不知道的知识点:没有 NUL(zero)bytes,方便编程语言设置其 NUL 符号。
Booleans
Aggregate Types
Arrays
array
可以在初始化的时候指定每一位的元素值,如下面例子
type Currency int
const (
USE Currency = iota
EUR
GBP
RMB
)
symbol := […]string{USD: “$”, EUR: “€”, GBP:”£”, RMB: “¥”}
fmt.Println(RMB, symbol[RMB]) // “3 ¥”
Structs
Reference Types
Pointers
For a
of pointer to array type: a[x]
is shorthand for (*a)[x]
If a
is a pointer to an array, a[low : high]
is shorthand for (*a)[low : high]
.
If x
is addressable and &x
's method set contains m
, x.m()
is shorthand for (&x).m()
statement *p++
is the same as (*p)++
.
Slices
slice
之间不能用 ==
比较符号。
唯一被允许的比较是 slice == nil
,为 slice
内置 ==
比较符号会有一些deep comparison的问题:
- 和
array
不一样,slice
的底层数据结构是 indirect 的,甚至可能出现一个slice
中的元素引用这个slice
的情况。 slice
的底层array
是有可能被改变的,移除内置==
让slice
无法作为map
的key
。
Maps
Spec
map
中的 value 无法被取地址,这是为了防止 rehash 导致地址变化带来问题。因此你无法对 map
中的 value 唤起 pointer method,因为没有地址可传入。
map
和 slice
一样,没有内置的 ==
操作
数据结构
type hmap struct { ... }
,其中 B uint8
代表当前哈希表持有的 buckets 数量的 2 次幂,即 len(buckets)
= 。
每个 bucket 对应的是 runtime.bmap
,一个 bucket 会存 8 个键值对,一旦桶溢出,多余的键值对则会转移到溢出桶。
- 当桶的数量少于 24 时,由于数据较少、使用溢出桶的可能性较小,因此会省略创建过程以减少额外开销。
- 当桶的数量多于 24 时,会额外创建 个溢出桶。
寻址
用 key hash 的后面几位决定目标桶,用 key hash 的前 8 位比较 key 是否相等。
for loop 时 runtime 会 random 出一个起点,如果中途修改 map 中的 kv,被修改的 kv 能否被遍历到取决于修改时 iterator 的位置是否在被修改 kv 的前面。
扩容
- 装载因子超过 6.5(元素数量 / 桶数量)-> 翻倍扩容,将桶的数量翻倍。
- 溢出数量桶超过某个阈值(未知,GPT 说因运行平台和版本而异)-> 等量扩容,桶的数量和原先相同,例如一个
map
被插入了太多数据又被删除,就会导致溢出桶过多而装载因子较小。
扩容不会修改 hash 因子,在扩容期间向旧桶写入、删除数据时会引发旧桶数据向新桶分流。
Functions
注意 receiver 定义为指针类型的情况,调用时就自动转化 value 为指针的默认行为是为了方便指针方法修改 receiver 内部状态,如果 receiver 是 value 其内部状态就不会被修改。这一点和 C 的 const 有点类似。
Channels
从已经关闭的 channel
接收消息会立即得到零值,可以用 _, ok := <-ch
的方法判断 channel
是否已经关闭。
close 已关闭的 channel
或者 nil
channel
会导致 panic。
从 nil
channel
接收消息会 block forever。
channel
的计算效率目前和直接使用锁相比并不高。
Interfaces
一个 interface
里包含了两个值,代表其指向元素的 type
和 value
,因此 val i interface{} := new(SomeType)
中的 i
并不等于 nil
,因为它还包含了一个 type
值。
Basic interfaces
Embedded interfaces
- 一个
interface
里可以加上其他interface
的名字,代表其拥有这些子interface
的所有方法(这个就是接口的组合吧)。- 区分点:此时这个
interface
的method set变大了(有了其子interface
的所有method),但是其 type set 变小了(子interface
对应type sets的交集)。 - 应该不允许出现同名但参数类型不同的方法
- 区分点:此时这个
General interfaces
~type
,代表这个interfece
的 underlying type 是某type
~T
中的T
必须是 underlying type 自身,且T
不可以是interface
t1|t2|…|tn
代表这些interface
所有 type set 的并集- 其中不能混有类型参数,且非
interface
的类型不能有相同的类型,即不可以写t1|t1
- 其中不能混有类型参数,且非
类型比较
- A named type is always different from any other type;
- Otherwise, two types are identical if their underlying type literals are structurally equivalent.
whether two values may be compared with == and != is related to assign ability to: in any comparison, the first operand must be assignable to the type of the second operand or vice versa.
Arithmetic operators apply to numeric values and yield a result of the same type as the first operand.
Range
range
右边的表达式叫做 range expression
range expression 的 core type 只能是:
- (pointer to an)
array
slice
string
map
channel
permitting receive operations
range
关键词本质上是一个语法糖,其在编译后会被展开为一个 for,针对不同结构体,展开结果不同,大致形式为:
ha := a
hv1 := 0
hn := len(ha)
v1 := hv1
v2 := nil
for ; hv1 < hn; hv1++ {
tmp := ha[hv1]
v1, v2 = hv1, tmp
...
}
Core type
non-interface 的 core type 和其 underlying type 一样
interface在这两种情况下有core type
- 其 type set 中的所有 type 有相同的一个 underlying type
U
,那么其 core type 为U
。 - type set 只有 channel type,方向相同,返回的都是 type E,那么其 core type 为
E
。
创建变量
new
:创建一个zero value的 basic type,返回对应的指针,其他方法就比new
要啰嗦(new就一行。- composite literals。
make
:初始化 runtime 内置的 composite literals。
没有被给定指定值的变量都会被赋予zero value。
定义变量只是定义变量之间的DAG,真正的初始化从第一个没有依赖其他变量的变量开始。https://go.dev/ref/spec#Package_initialization
占位符
%d
十进制整数
%x
%o
%b
十六进制、八进制、二进制整数
%f
%g
%e
浮点数,e 表示 3.1415923e+00 的形式,g 指根据情况选择 f 或 e 输出(看哪个输出格式更紧凑)。All three verbs allow field width and numeric precision to be controlled.
%t
布尔值
%c
rune、unicode code point
%s
字符串、凡是实现了Stringer interface的类型都适用
%q
quoted string “abc” or rune ‘c'
%v
any value in a natural format
%T
type of any value
%%
literal percent sign (no operand)
%w
error values
%[1]d
代表把 format 的第一个参数用十进制整数的形式打印出来,这样多个占位符可以用上相同的参数。举个例子:
fmt.Sprintf("%[2]d %[1]d\n", 11, 22) // "22 11"
%#d
代表把前面表示进制(0、0x)的前缀给打印出来
% x
,中间有个空格,代表把数字中的每个元素按照 %x
的格式打印出来,每个元素之间用空格分隔。
JSON
JSON marshal是依赖反射读取field tag以实现序列化
json:"name"
,表示一个field序列化后的key为namejson:"-"
,表示序列化忽略这个fieldjson:"-,"
,表示这个field序列化后的key为"-"
json:",omitempty"
,表示在序列化时,如果这个field是空值,忽略这个field- JSON cannot represent cyclic data structures and Marshal does not handle them.
- 如果想自定义编解码过程,就需要实现
encoding/json.Marshaler
和encoding/json.Unmarshaler
接口。
https://github.com/bytedance/mockey 中的 mock 是如何实现的
通过替换函数对应地址的字节码,将函数调用引导进 mock 函数。