Overview of Go Type System
-
Go由哪几种类型组成?
-
Built-in Basic Types
- 包括:string, bool, float32, float64, complex64, complex128, uint8(byte), uint16, uint32, uint64, int8, int16, int32(rune), int64, int。共计
17
种类型。
- 包括:string, bool, float32, float64, complex64, complex128, uint8(byte), uint16, uint32, uint64, int8, int16, int32(rune), int64, int。共计
-
Composite Types
。- 包括:pointer, function, interface, struct, map, slice, array, channel。共计8种类型。
-
Unsafe Pointer Types
(由标准库中unsafe
包提供)。 - 综上,Go类型系统由(17 + 8 + 1) == 26种类型构成。
-
-
什么是
Type Literal
?- 它是表示(denote)复合类型的一种形式。比如
map[int]int
,*int
,[]int
等。 - 它是
Undefined Type
,因为type T map[int]int
,T本身就可以表示一种特定类型。
- 它是表示(denote)复合类型的一种形式。比如
-
什么是
Type Declaration
?- 它也被称为
Type Definition
,Type Definition Declaration
。格式为type NewTypeName SourceType
。 - 不同的
Type Declaration
定义的多个类型一定是不同的类型。 -
NewTypeName
和SourceType
是两种不同的类型。 -
NewTypeName
和SourceType
拥有相同的Underlying Type
。两者之间可以进行显式类型转换。 -
Type Declaration
可以在函数内部使用。
- 它也被称为
-
什么是
Type Alias Declaration
?- 格式:
type table = map[string]int
- 它没有定义新的类型,仅仅给
map[string]int
起了一个别名。 -
Type Alias Declaration
也可以在函数内部使用
- 格式:
-
什么是
Defined Types
?什么是Undefined Types
?- 所有的
Built-in Basic Types
都是Defined Types
- 通过
Type Declaration
定义的类型是Defined Types
- 其它类型为
Undefined Types
。 -
Defined Types
等价于Named Types
,Undefined Types
等价于Unnamed Types
。
- 所有的
-
什么是
Underlying Types
?-
Built-in Basic Types
的Underlying Type
是它们本身 -
Unsafe Pointer Types
的Underlying Type
是它们本身 -
Undefined Types
的Underlying Type
是它们本身 -
type NewTypeName SourceType
NewTypeName和SourceType拥有相同的Underlying Type
。
-
-
什么是
Value
?- Value有多种表示方式,例如
Value Literal
(也被称为unnamed constants
)、Named Constants
、Variables
、Expressions
。 - 每种类型都会有一个
Zero Value
,比如function
,pointer
,interface
,map
,slice
,channel
的Zero Value
是nil
,数字类型的Zero Value
是0。 - Value又可分为
Untyped Value
和Typed Value
。var i int8 = 10
之所以是合法的,就是因为10
就是Untyped Value。反证法:如果10是Typed Value(假如是int),那么这就违反了不同类型之间转换的规则。Untyped Values
一般会有一个默认类型,例如""的默认类型是string, 10的默认类型是int,但是nil没有默认类型。const X int = 10
,该Named Constant
是Typed Value
;const Y = 10
,该Named Constant
是Untyped Value
。
- Value有多种表示方式,例如
-
什么是
Value Part
- 一个Value可能会由多个连续的内存块组成,它们被分为
Direct Part
和Indirect Parts
。 - 多个Value之间可能会共享
Indirect Part
,但是Direct Part
一定是不共享的。所以Value Size
的大小指的就是Direct Part
-
unsafe.Sizeof
返回Value Size
的大小
- 一个Value可能会由多个连续的内存块组成,它们被分为
-
哪些类型不支持比较
- map, slice, functions
- struct 的 field 类型属于以上三种类型
- array 的 element 类型属于以上三种类型
指针(Pointers)
-
word
的大小?- 在32位的操作系统下,word大小为4字节;在64位的操作系统下,word大小为8字节。
-
什么是
Base Type
?- 例如:
**int // A non-defined pointer type whose base type is *int.
。例如:type PP *Ptr // PP is a defined pointer type whose base type is Ptr.
- 例如:
-
指针的类型转换规则?
- 如果这两个类型的底层类型相同(忽略struct tag),则这两个类型之间可以进行显式类型转换。
- 如果这两个类型中的某一个是
Undefined Type
(考虑struct tag),那么它们之间可以进行隐式类型转换。 - 如果两个类型都是
Undefined Type
,如果它们的Base Type
相同,显然它们是相同的类型;否则,如果Base Type
的Underlying Type
相同,即可进行显式类型转换。
-
哪些值是可以取地址的(
addressable
,可写的)?- 变量;array的元素;slice的元素;struct的字段
-
其它值为什么不可以取地址(只读的)?
-
bytes in string
: 因为字符串是不可修改的。 -
map element
:Go编译器为了采用更高效的算法,将会改变map中元素的地址。 -
dynamic value of interface values
、constant values
、literal values
、package level functions
、methods
、intermediate values
等。这些Value是不可被修改的,只能作为赋值表达式的右值。
-
Structs
-
什么构成了Struct的标识?
-
field name
,field type
,field order
、field tag
- 如果两个
Struct Type Literal
的这些元素都相同,则这两个Struct Type
是相同的。
-
-
Struct类型转换规则。
- 如果这两个类型的底层类型相同(忽略struct tag),则这两个类型之间可以进行显式类型转换。
- 如果这两个类型中的某一个是
Undefined Type
(考虑struct tag),那么它们之间可以进行隐式类型转换。
-
如何计算Struct的大小?
- 某类型的起始地址一定是它的
Alignment
的整数倍。如果某类型的大小,占四个字节及以下,它的Alignment
大小等于Type Size
;array
的Alignment
取决于the type alignment of element
;struct
的Alignment
取决于the max type alignment of fields
;其它类型的alignment
值是word
。 - 某类型的大小(
Type Size
)一定是Type Alignment
的整数倍。Type Size
的大小等于Direct Part
的大小。
- 某类型的起始地址一定是它的
Value Parts
// map types
type _map *hashtableImpl
// channel types
type _channel *channelImpl
// function types
type _function *functionImpl
type _slice struct {
// referencing underlying elements
elements unsafe.Pointer
// number of elements and capacity
len, cap int
}
type _string struct {
elements *byte // referencing underlying bytes
len int // number of bytes
}
type _interface struct {
dynamicType *_type // the dynamic type
dynamicValue unsafe.Pointer // the dynamic value
}
type _interface struct {
dynamicTypeInfo *struct {
dynamicType *_type // the dynamic type
methods []*_function // method table
}
dynamicValue unsafe.Pointer // the dynamic value
}
Arrays, Slices and Maps in Go
-
对
key
和element
类型是否有要求?- 对于array和slice来说,key必须是int类型;对于map来说key必须是
Comparable
(== 和 !=),所以slice, map, function不能作为key。 - element可以是任意类型
- 对于array和slice来说,key必须是int类型;对于map来说key必须是
-
blank value VS zero value
- a blank slice:
i = []int{}
,zero value:i = nil
或i = ([]int)(nil)
- a blank interface:
i := map[int]int(nil)
; a zero-value interface:i = nil
- a blank slice:
-
Nested Composite Literals Can Be Simplified?
- 它被限制在使用
Value Literal
的情况下; - 可以不指定element的类型;也可以不指定key的类型。
- 它被限制在使用
-
v[k]的注意事项?
- 如果v是slice类型
- k值太大或v==nil,都会导致panic
- 支持v[x:y:z]操作(reslice),要求
0 <= x <= y <= z <= cap(v)
,其中z
不是必须的。
- 如果v是map类型
- 如果v == nil。如果v[k]作为赋值表达式的左值,会导致panic;如果作为右值,v[k]的值是element的
Zero Value
- 如果k是Interface类型,且dynamic type不支持比较,则产生panic。
- 如果v == nil。如果v[k]作为赋值表达式的左值,会导致panic;如果作为右值,v[k]的值是element的
- 如果v是slice类型
-
如何拷贝一个slice?
-
append
是否会为新的slice构建新的Indirect Parts
,完全取决于是cap和元素数量的比较。 - 我们知道
y=x
或y=x[:]
操作都会产生一个新的slice。但由于它们共享Indirect Part
,x或y的增删改都会影响到对方。 - 通过
y = append(x[:0:0], x)
进行一次深度拷贝,使得x和y不再共享Indirect Part
。
-
-
Zero Value的进一步解读
m := *new(map[string]int) // <=> var m map[string]int fmt.Println(m == nil) // true s := *new([]int) // <=> var s []int fmt.Println(s == nil) // true a := *new([5]bool) // <=> var a [5]bool fmt.Println(a == [5]bool{}) // true
所以
m==nil
,表示m的Direct Part
等于0。 -
修改map元素的注意事项?
- 你可以通过
m[k] = v
的方式修改元素的值; - 你只能对
Direct Part
进行整体修改,不能只针对某部分修改,如果元素的类型是struct,m[k].x = 1
是不合法的。同理元素类型是array时,也不能对array element进行修改。
- 你可以通过
-
for...range操作的特殊性?
- 可以对nil map或nil slice进行遍历
- 如果删除了未被遍历的元素,则该元素肯定不会被遍历
- 如果创建了一个新的元素,则该元素可能会被遍历。
-
删除map中的所有元素?
for key := range m { delete(m, key) }
-
删除map中的某个元素?
for key := range m { if key.expired() { delete(m, key) } }
Strings
-
字符串是不可修改的?
- 字符串是不可被修改的,所以
s[i] = 'k'
是不合法的。 - 这带来的好处是字符串的拷贝是高效的,
a=b
操作只涉及Direct Part
部分的修改。注意,a=string(b)操作将会导致字符串的深度拷贝。 - 如果我们尽量使用
a=b
的模式进行字符串拷贝,字符串之间的比较操作将会很高效。Go只需要检查字符串的长度是否一致,以及Indirect Part
对应的内存块的指针是否一致。
- 字符串是不可被修改的,所以
-
字符串和
[]byte
之间的关系- 通过[]byte(str) 和 []rune(str)将字符串类型转换为slice;通过string(xxx)将slice转换成字符串。两者之间的拷贝是深度拷贝。
- 在内置函数
append
和copy
中,如果第一个参数是[]byte
,第二个参数可以是string。 - 在以下四种情况下,Go编译器会进行优化,防止深度拷贝。
- a conversion (from string to byte slice) which follows the range keyword in a for-range loop.
- a conversion (from byte slice to string) which is used as a map key in map element indexing syntax.
- a conversion (from byte slice to string) which is used in a comparison
- a conversion (from byte slice to string) which is used in a string concatenation, and at least one of concatenated string values is a non-blank string constant.
-
字符串的连接操作有哪些?
-
fmt.Sprintf
,bytes.Buffer
,strings.Builder
,strings.Join
,+
。
-
-
关于Unicode和UTF-8之间的关系?
- Unicode是一个字符集,它为世界上所有的文字进行了编码。但是Unicode的单位是
code point
,大多数情况下,一个字符对应了一个code point
,但是也存在一个字符对应多个code point
的情况。 -
Code Point
在Go语言中对应了rune
类型。UTF-8或者UTF-16是一种对Code Point
进行编码的手段。目前,UTF-8最为流行。 - Go语言要求Go文件是UTF-8编码,string中存储的是UTF-8编码的内容。
- If a slice rune element value is outside the range of valid Unicode code points, then it will be viewed as 0xFFFD, the code point for the Unicode replacement character. 0xFFFD will be UTF-8 encoded as three bytes (0xef 0xbf 0xbd).
- Bad UTF-8 encoding representations will be converted to a rune value 0xFFFD. If erroneous UTF-8 is encountered, the character is set to U+FFFD and the index advances by one byte.
- Unicode是一个字符集,它为世界上所有的文字进行了编码。但是Unicode的单位是