bool | 布尔类型 |
numeric | 数值类型 |
string | 字符串类型 |
array | 数组 |
slice | 切片 |
struct | 结构体 |
pointer | 指针 |
function | 函数 |
interface | 接口 |
map | 集合 |
channel | 管道 |
numeric
uint8 | the set of all unsigned 8-bit integers (0 to 255) |
uint16 | the set of all unsigned 16-bit integers (0 to 65535) |
uint32 | the set of all unsigned 32-bit integers (0 to 4294967295) |
uint64 | the set of all unsigned 64-bit integers (0 to 18446744073709551615) |
int8 | the set of all signed 8-bit integers (-128 to 127) |
int16 | the set of all signed 16-bit integers (-32768 to 32767) |
int32 | the set of all signed 32-bit integers (-2147483648 to 2147483647) |
int64 | the set of all signed 64-bit integers (-9223372036854775808 to 9223372036854775807) |
float32 | the set of all IEEE-754 32-bit floating-point numbers |
float64 | the set of all IEEE-754 64-bit floating-point numbers |
complex64 | the set of all complex numbers with float32 real and imaginary parts |
complex128 | the set of all complex numbers with float64 real and imaginary parts |
byte | alias for uint8 |
rune | alias for int32 |
uint | either 32 or 64 bits |
int | same size as uint |
uintptr | an unsigned integer large enough to store the uninterpreted bits of a pointer value |
可以看到,uint、int的大小是与体系架构有关系的,32位或者64位
string
string的元素不能取地址,s[i] 代表第i个元素,但是&s[i]是违法的。
array
ArrayType = "[" ArrayLength "]" ElementType . ArrayLength = Expression . ElementType = Type .
- 长度是类型一部分,[2]int 和 [4]int 是两个不同的类型
- 长度必须是由一个常量表达式计算出来,最终能够计算为一个 非负的、int 类型 的值
- 数组类型的定义不能定义为 值类型的自包含,如下展示了非法、合法的定义
// invalid array types
type (
T1 [10]T1 // element type of T1 is T1
T2 [10]struct{ f T2 } // T2 contains T2 as component of a struct
T3 [10]T4 // T3 contains T3 as component of a struct in T4
T4 struct{ f T3 } // T4 contains T4 as component of array T3 in a struct
)
// valid array types
type (
T5 [10]*T5 // T5 contains T5 as component of a pointer
T6 [10]func() T6 // T6 contains T6 as component of a function type
T7 [10]struct{ f []T7 } // T7 contains T7 as component of a slice in a struct
)
slice
可以使用make来定义一个切片:make([]T, length, capacity),此外也可以使用new。两种定义切片的方式:
make([]int, 50, 100) new([100]int)[0:50]
struct
关注下匿名变量(或者嵌入变量)的定义。一个声明了类型但没有明确字段名的字段被称为嵌入字段。一个内嵌字段必须被指定为一个类型名T或一个指向非接口类型名*T的指针,T本身不能是一个指针类型。未限定的类型名作为字段名。
// A struct with four embedded fields of types T1, *T2, P.T3 and *P.T4
struct {
T1 // field name is T1
*T2 // field name is T2
P.T3 // field name is T3
*P.T4 // field name is T4
x, y int // field names are x and y
}
下面的则是一些错误示例:
struct {
T // conflicts with embedded field *T and *P.T
*T // conflicts with embedded field T and *P.T
*P.T // conflicts with embedded field T and *T
}
同样,struct也不能定义为值类型的自包含,如下:
// invalid struct types
type (
T1 struct{ T1 } // T1 contains a field of T1
T2 struct{ f [10]T2 } // T2 contains T2 as component of an array
T3 struct{ T4 } // T3 contains T3 as component of an array in struct T4
T4 struct{ f [10]T3 } // T4 contains T4 as component of struct T3 in an array
)
// valid struct types
type (
T5 struct{ f *T5 } // T5 contains T5 as component of a pointer
T6 struct{ f func() T6 } // T6 contains T6 as component of a function type
T7 struct{ f [10][]T7 } // T7 contains T7 as component of a slice in an array
)
再者,在最一般的形式中,也存在一些更为通用的定义,参见 General Interfaces 。
In their most general form, an interface element may also be an arbitrary type term T, or a term of the form ~T specifying the underlying type T, or a union of terms t1|t2|…|tn. Together with method specifications, these elements enable the precise definition of an interface's type set as follows:
- The type set of the empty interface is the set of all non-interface types.
- The type set of a non-empty interface is the intersection of the type sets of its interface elements.
- The type set of a method specification is the set of all non-interface types whose method sets include that method.
- The type set of a non-interface type term is the set consisting of just that type.
- The type set of a term of the form ~T is the set of all types whose underlying type is T.
- The type set of a union of terms t1|t2|…|tn is the union of the type sets of the terms.
// 1. By construction, an interface's type set never contains an interface type.
// An interface representing only the type int.
interface {
int
}
// An interface representing all types with underlying type int.
interface {
~int
}
// An interface representing all types with underlying type int that implement the String method.
interface {
~int
String() string
}
// An interface representing an empty type set: there is no type that is both an int and a string.
interface {
int
string
}
// 2. In a term of the form ~T, the underlying type of T must be itself, and T cannot be an interface.
type MyInt int
interface {
~[]byte // the underlying type of []byte is itself
~MyInt // illegal: the underlying type of MyInt is not MyInt
~error // illegal: error is an interface
}
// 3. Union elements denote unions of type sets:
// The Float interface represents all floating-point types
// (including any named types whose underlying types are
// either float32 or float64).
type Float interface {
~float32 | ~float64
}
// 4. The type T in a term of the form T or ~T cannot be a type parameter, and the type sets of all non-interface terms must be pairwise disjoint (the pairwise intersection of the type sets must be empty). Given a type parameter P:
interface {
P // illegal: P is a type parameter
int | ~P // illegal: P is a type parameter
~int | MyInt // illegal: the type sets for ~int and MyInt are not disjoint (~int includes MyInt)
float32 | Float // overlapping type sets but Float is an interface
}
// 5. Interfaces that are not basic may only be used as type constraints, or as elements of other interfaces used as constraints. They cannot be the types of values or variables, or components of other, non-interface types.
var x Float // illegal: Float is not a basic interface
var x interface{} = Float(nil) // illegal
type Floatish struct {
f Float // illegal
}
// 6. An interface type T may not embed a type element that is, contains, or embeds T, directly or indirectly.
// illegal: Bad may not embed itself
type Bad interface {
Bad
}
// illegal: Bad1 may not embed itself using Bad2
type Bad1 interface {
Bad2
}
type Bad2 interface {
Bad1
}
// illegal: Bad3 may not embed a union containing Bad3
type Bad3 interface {
~int | ~string | Bad3
}
// illegal: Bad4 may not embed an array containing Bad4 as element type
type Bad4 interface {
[10]Bad4
}
map
MapType = "map" "[" KeyType "]" ElementType . KeyType = Type .
比较运算符 == 和 != 必须为键类型的操作数完全定义;因此键类型不能是函数、映射或片断。如果键类型是一个接口类型,这些比较运算符必须为动态键值定义;失败将导致运行时的panic。
// 定义
map[string]int
map[*T]struct{ x, y float64 }
map[string]interface{}
// 初始化
make(map[string]int)
make(map[string]int, 100)
chan
ChannelType = ( "chan" | "chan" "<-" | "<-" "chan" ) ElementType . chan T // can be used to send and receive values of type T chan<- float64 // can only be used to send float64s <-chan int // can only be used to receive ints // <- 总是跟随最左边的chan标识关联 chan<- chan int // same as chan<- (chan int) chan<- <-chan int // same as chan<- (<-chan int) <-chan <-chan int // same as <-chan (<-chan int) chan (<-chan int)
预置标识符(predeclared identifiers)
Types: any bool byte comparable complex64 complex128 error float32 float64 int int8 int16 int32 int64 rune string uint uint8 uint16 uint32 uint64 uintptr Constants: true false iota Zero value: nil Functions: append cap close complex copy delete imag len make new panic print println real recover
iota
是一个常量标识符,标识的是在常量定义中该常量声明对应的索引,从0开始。
const ( c0 = iota // c0 == 0 c1 = iota // c1 == 1 c2 = iota // c2 == 2 ) const ( a = 1 << iota // a == 1 (iota == 0) b = 1 << iota // b == 2 (iota == 1) c = 3 // c == 3 (iota == 2, unused) d = 1 << iota // d == 8 (iota == 3) ) const ( u = iota * 42 // u == 0 (untyped integer constant) v float64 = iota * 42 // v == 42.0 (float64 constant) w = iota * 42 // w == 84 (untyped integer constant) ) const x = iota // x == 0 const y = iota // y == 0 const ( bit0, mask0 = 1 << iota, 1<<iota - 1 // bit0 == 1, mask0 == 0 (iota == 0) bit1, mask1 // bit1 == 2, mask1 == 1 (iota == 1) _, _ // (iota == 2, unused) bit3, mask3 // bit3 == 8, mask3 == 7 (iota == 3) )
2.1 数组
arr1 := [3]int{1, 2, 3}
arr2 := [...]int{1, 2, 3} // 这里编译器会自动推导出来长度
在代码中没有找到直观的代码,能够证明数据的实现,从已有的网络资源和自己测试我们大致可以还原数组的基本原理。
创建:
- 会在编译期间完成连续空间的直接申请,这个可能是存储在堆、也可能是存储在栈,取决于变量逃逸分析
- 因为数组的长度是固定的,所以没有类似slice、map之类的显示的字段来存储len、cap,都是在编译期间直接生效
访问:
- 首先因为空间连续、长度固定,那么访问的操作,直接基于连续空间进行偏移进行寻址,完成访问
- 其次,在访问数据之前会进行边界检查
var a = [...]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 0}
a[i] = 10 // i>len(a)的话,在运行期间,会被边界检查函数panic掉
a[100] = 10 // 这一句则会在编译期间就失败
删除:
数组没有删除,因为数组是固定长度的容器,删除了后那个"空洞"里面放什么?
2.2 切片
type slice struct {
array unsafe.Pointer
len int
cap int
}
makeslice
申请出来slice,完成初始化
growslice
扩容切片,当执行append的时候,如果新的容量大于老的cap,那么会触发扩容,调用growslice完成。
扩容策略:
先尝试二倍扩容,不满足需求,则使用新的容量
如果满足,则尽量降低需要的数量,做一个最大满足即可。
此处根据阈值(const threshold = 256)判断,老的容量小于threshold,则使用二倍扩容,大于threshold,则按照原始的1/4增长,直到刚好满足新的容量
类似拥塞控制,先二倍二倍的快速扩张到threshold,之后1/4的扩张。
删除
切片没有删除。为啥?仁者见仁智者见智了。个人理解,因为没必要,切片的本质是一个迭代器,迭代器从业务特性角度,不应当调整其内容。不像map,map是一个可变容量的容器,所以可以增删改查。那么,如果非要删除,对于迭代器而言,倾向于构造一个新的迭代器,来承载删除后的迭代任务,即类似:
// 删除第二个
// 第一种方式:保序删除
s := []int{1, 2, 3, 4, 5, 6}
sNew := append(s[:1], s[2:]...)
fmt.Println(sNew) // [1 3 4 5 6]
// 第二种方式:不保序删除
s = []int{1, 2, 3, 4, 5, 6}
s[1] = s[len(s)-1]
sNew = s[:len(s)-1]
fmt.Println(sNew) // [1 6 3 4 5]
copy
底层在runtime的时候,调动的是slicecopy,可以看到不会修改dst的大小,只是把内部的array内容直接memmove
append
底层在runtime的时候,如果需要扩容,会调用growslice,进行扩容,之后返回新的;如果不需要扩容,则会使用同一份内容内容,然后返回
for-range
package main
import (
"fmt"
)
type Hero struct {
Name string
}
func main() {
data := []Hero{{Name: "flynn"}}
// 错误写法
for index, d := range data {
d.Name = "vivi" // 这个d是个临时变量,不能影响data中真实的值
fmt.Println(data[index].Name) // flynn
}
// 正确写法
for index, d := range data {
d.Name = "vivi" // 这个d是个临时变量,不能影响data中真实的值
data[index] = d
fmt.Println(data[index].Name) // flynn
}
}
2.3 nil与零值
零值我们一般可以认为是类型的默认值,在Go语言中,布尔类型的零值(初始值)为 false,数值类型的零值为 0,字符串类型的零值为空字符串"",而指针、切片、映射、通道、函数和接口的零值则是 nil。
nil 是Go语言中一个预定义好的标识符,与其他语言的NULL还是存在一些差异的。
- nil不能比较,即fmt.Println(nil==nil) 是不合法的
- nil 不是关键字或保留字,即我们可以定义一个名称为nil的变量,例如:var nil = errors.New("my god")
- nil 没有默认类型,即 fmt.Printf("%T", nil) 是不合法的
- 不同类型 nil 的指针是一样的,都是 0x0,但是却不能比较
var m map[int]string var ptr *int fmt.Printf(m == ptr) // invalid operation: arr == ptr (mismatched types []int and *int)
- map、slice 和 function 类型的 nil 值不能比较
var s1 []int
var s2 []int
fmt.Printf(s1 == s2) // invalid operation: s1 == s2 (slice can only be compared to nil)
- nil 是 map、slice、pointer、channel、func、interface 的零值
- 不同类型的 nil 值占用的内存大小可能是不一样的。一个类型的所有的值的内存布局都是一样的,nil 也不例外,nil 的大小与同类型中的非 nil 类型的大小是一样的。但是不同类型的 nil 值的大小可能不同,具体的大小取决于编译器和架构。
var p *struct{}
fmt.Println( unsafe.Sizeof( p ) ) // 8
var s []int
fmt.Println( unsafe.Sizeof( s ) ) // 24
var m map[int]bool
fmt.Println( unsafe.Sizeof( m ) ) // 8
var c chan string
fmt.Println( unsafe.Sizeof( c ) ) // 8
var f func()
fmt.Println( unsafe.Sizeof( f ) ) // 8
var i interface{}
fmt.Println( unsafe.Sizeof( i ) ) // 16
2.4 类型转换
分为四种:断言、强制、显式、隐式
断言:
s := x.(T) or s, ok := x.(T)
如果断言成功,s即T类型,否则第一种写法hipanic,第二种写法ok为false
switch-type断言:
switch x.(type) {
case nil:
case int:
default:
}
强制:
通过unsafe包进行强类型转换,类似c/c++的强转
var f float64 bits = *(*uint64)(unsafe.Pointer(&f)) type ptr unsafe.Pointer bits = *(*uint64)(ptr(&f)) var p ptr = nil
这种强制的转换,有一种典型用法,用以做编译期间接口类型检测
var _ Context = (*ContextBase)(nil)
nil 的类型是 nil 地址值为 0,利用强制类型转换成了 * ContextBase,返回的变量就是类型为 * ContextBase 地址值为 0,然后 Context=xx 赋值如果 xx 实现了 Context 接口就没事,如果没有实现在编译时期就会报错,实现编译期间检测接口是否实现。
显式:
写法很简单:T(x),在一下任何一种情况下,变量x都可以成功转换,规则可以参考 reflect.Value.Convert :
- x 可以分配成 T 类型。
- 忽略 struct 标签 x 的类型和 T 具有相同的基础类型。
- 忽略 struct 标记 x 的类型和 T 是未定义类型的指针类型,并且它们的指针基类型具有相同的基础类型。
- x 的类型和 T 都是整数或浮点类型。
- x 的类型和 T 都是复数类型。
- x 的类型是整数或 [] byte 或 [] rune,并且 T 是字符串类型。
- x 的类型是字符串,T 类型是 [] byte 或 [] rune。
隐式:
在Go中,变量之间没有隐式类型转换。但是,编译器可以进行变量和常量之间的隐式类型转换。
- 例如:如下示例中,我们将常量123的整数类型隐式转换为int类型的值。由于常量的形式不使用小数点或指数,因此常量采用整数类型。只要不需要截断,就可以将整数类型的常量隐式转换为有符号和无符号整数变量。
- 如果常量使用与整数类型兼容的形式,则也可以将浮点类型的常量隐式转换为整数变量:
- 但是下面的转换却是不可行的
隐式类型转换主要包含了整数、浮点数、算术计算以及自定义类型,这里展开下自定义类型
type Numbers int8 const One Numbers = 1 const Two = 2 * One
我们声明了一个新类型,称为Numbers,其基本类型为int8。然后,我们以Numbers类型声明常量One,并分配整数类型的常量1。接下来,我们声明常量2,该常量通过将常量2和Numbers类型的常量One相乘而提升为Numbers类型。类似time.Duration的设计。
切片的copy是值还是引用?值拷贝。
package main
import (
"fmt"
)
type Student struct {
Name string
}
func main() {
var data [10]Student
var datas = make([]Student, 5)
copy(datas, data[0:len(data)])
fmt.Printf("%p, %p\n", &data[0], &datas[0])
var stus [10]int
var stuss = make([]int, 5)
copy(stuss, stus[0:len(stus)])
fmt.Printf("%p, %p\n", &stus[0], &stuss[0])
}
0xc0000000a0, 0xc000060050 0xc00001e2d0, 0xc000024120
从数组直接构造一个切片,是值拷贝还是引用?引用
a := [12]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 0}
b := a[5:6]
fmt.Println(cap(b), len(b)) // 7, 1,因为 a[5:6] <==> a[5:6:len(a)]
fmt.Println(&a[5], &b[0]) // fmt.Println(a[5], b[0])
fmt.Println(a[5], b[0]) // 6 7
b[0] = 1000
fmt.Println(a[5], b[0]) // 1000 1000
返回值的类型为string,能否返回nil
package main
import (
"fmt"
)
func GetValue(m map[int]string, id int) (string, bool) {
if _, exist := m[id]; exist {
return "存在数据", true
}
return nil, false
}
func main() {
intmap:=map[int]string{
1:"a",
2:"bb",
3:"ccc",
}
v,err:=GetValue(intmap,3)
fmt.Println(v,err)
}
编译失败,nil 可以用作 interface、function、pointer、map、slice 和 channel 的“空值”。但是如果不特别指定的话,Go 语言不能识别类型,所以会报错。通常编译的时候不会报错,但是运行是时候会报:cannot use nil as type string in return argument.
所以将GetValue函数改成如下形式就可以了
func GetValue(m map[int]string, id int) (string, bool) {
if _, exist := m[id]; exist {
return "存在数据", true
}
return "不存在数据", false
}
- https://juejin.cn/post/7095489639024164894
- https://zhuanlan.zhihu.com/p/118316486