Tags
Categories
基础
-
Go 语言的特征:静态类型、编译型、并发型、垃圾回收、快速编译、跨平台。
-
Go 语言的基本类型:布尔型、数字类型、字符串类型、派生类型。
数组
-
数组与切片的区别:
数组 切片 长度固定 长度可变 值类型 引用类型 引申:
[3]int和[4]int是不同的类型,但[]int是一个类型。 -
切片的数据结构
type slice struct { array unsafe.Pointer // 指向底层数组的指针 len int // 切片的长度 cap int // 切片的容量 } -
修改切片中的值:
func main() { s := []int{1, 2, 3} modifySlice(s) fmt.Println(s) // [1, 2, 3] } func modifySlice(s []int) { s[0] = 100 }传递切片时,传递的是切片的引用,所以在函数内部修改切片的值,会影响到原切片。
-
切片的扩容策略:
- 如果新申请的容量小于 256,则新容量为原容量的 2 倍。
- 若新申请的容量大于等于 256,则
newcap = oldcap+(oldcap+3*256)/4。 - 使用
growslice函数进内存对齐。
func func1() { s := []int{5} s = append(s, 7) s = append(s, 9) x := append(s, 11) y := append(s, 12) fmt.Println(s, x, y) // [5 7 9] [5 7 9 12] [5 7 9 12] } func func2() { s := []int{1,2} s = append(s,4,5,6) fmt.Printf("len=%d, cap=%d",len(s),cap(s)) // len=5, cap=6 } -
append函数:append函数返回值是一个新的切片,且必须作为返回值接收。append(slice, elem1, elem2)append(slice, slice2...)
-
切片作为函数参数时:
- 如果直接传递切片,实参 slice 并不会被函数操作改变。
- 如果传递切片的指针,实参 slice 会被函数操作改变。
- 但是通过指针传递切片,会导致切片的底层数组被修改,所以不推荐。
func main() { s := []int{1, 2, 3} modifySlice(s) fmt.Println(s) // [1, 2, 3] modifySlice2(&s) fmt.Println(s) // [100, 2, 3] } func modifySlice(s []int) { s[0] = 100 } func modifySlice2(s *[]int) { (*s)[0] = 100 }
Map
-
Map 的数据结构:
type hmap struct { count int flags uint8 B uint8 noverflow uint16 hash0 uint32 buckets unsafe.Pointer oldbuckets unsafe.Pointer nevacuate uintptr extra *mapextra } -
go 的 map 是使用哈希表实现的,并采用链表法解决哈希冲突。
-
map 的操作
make(map[keyType]valueType, cap):创建 map。map[key] = value:添加元素。delete(map, key):删除元素。value, ok := map[key]orvalue = map[key]:获取元素。len(map):获取元素个数。
-
map 的遍历
func main() { m := map[string]int{ "a": 1, "b": 2, "c": 3, } for k, v := range m { fmt.Println(k, v) } } -
map 的 key
- map 的 key 必须支持
==和!=操作。 - map 的 key 不能是函数类型、map 类型和切片类型。
- struct 类型不包含上述字段,也可以作为 key。
- 无序性:map 的遍历顺序与添加顺序无关。
- map 的 key 必须支持
-
无法对 map 进行取址操作,因为 map 可能会进行扩容,导致地址发生变化。
-
比较两个 map 是否相等:
- map 只能与 nil 比较。
- map 不能使用
==比较,只能遍历比较。
func main() { m1 := map[string]int{ "a": 1, "b": 2, "c": 3, } m2 := map[string]int{ "a": 1, "b": 2, "c": 3, } fmt.Println(m1 == m2) // invalid operation: m1 == m2 (map can only be compared to nil) fmt.Println(m1 == nil) // false fmt.Println(m1Equal(m1, m2)) // true } func m1Equal(m1, m2 map[string]int) bool { if len(m1) != len(m2) { return false } for k, v := range m1 { if v2, ok := m2[k]; !ok || v != v2 { return false } } return true } -
map 不是线程安全的,如果需要并发读写,需要加锁。
-
map 的扩容条件:
- 装载因子超过阈值(6.5)
- overflow 的 bucket 数量过多
- 采用渐进式搬迁扩容策略。
接口
-
值类型接收者会隐含地创建指针型接收者的方法。参考:值接收者和指针接收者
type greeter interface { greet() farewell() } type english struct { } func (e english) greet() { fmt.Println("Hello") } func (e *english) farewell() { fmt.Println("Goodbye") } func main() { var e greeter = &english{} e.greet() e.farewell() }这里的
e是一个指针,但是e.greet()是可以调用的,因为编译器会隐式地将e.greet()转换为(*e).greet(), 即由值类型的接收者为指针类型接收者创建了一个方法。相反如果e是一个值类型,那么e.farewell()就会报错,因为编译器不会隐式地将e.farewell()转换为(&e).farewell()。 -
接口的实现:
- 接口的实现是隐式的,只要实现了接口的方法,就实现了该接口。
- 接口的实现是
非侵入式的,不需要在实现类中显式地声明实现了哪些接口。
-
iface vs eface
- iface:定义了接口的类型,包含两个指针,一个指向类型的方法表,一个指向实际的数据。只有当接口存储的类型和对象都为 nil 时,接口才为 nil。
- eface:空接口,包含两个指针,一个指向类型的类型信息,一个指向实际的数据。
-
检查 T 类型是否实现了某个接口:
var _ interface{} = (*T)(nil) var _ interface{} = T{} -
断言
var i interface{} = "hello" if s, ok := i.(string); ok { fmt.Println(s) } -
使用接口实现多态
type animal interface { call() grow() } type cat struct { age int } func (c cat) call() { println("miao") } func (c *cat) grow() { c.age++ } type dog struct { age int } func (d dog) call() { println("wang") } func (d *dog) grow() { d.age += 2 } func howl(a animal) { a.call() } func grow(a animal) { a.grow() } func main() { c := cat{age: 1} howl(&c) grow(&c) println(c.age) d := dog{age: 2} howl(&d) grow(&d) println(d.age) }
内置函数
-
new和make的区别:new用于值类型和用户定义的类型,如自定义结构体。make用于内置引用类型,如map、slice、channel。
-
常用的内置函数:
len:返回长度。cap:返回容量。append:追加元素。copy:复制切片。close:关闭通道。delete:删除 map 中的元素。new:分配内存,返回指针。make:分配内存,返回引用类型。panic:停止常规的 goroutine。recover:允许程序恢复 goroutine。reflect.TypeOf:返回变量的实际类型。reflect.ValueOf:返回变量的实际值。unsafe.Sizeof:返回变量的字节大小。