golang 基本数据类型
数组、字符串、切片
数组
数组的特点是长度固定,拥有多个相同数据类型的数据元素。
长度也是数组类型的一部分。因此不同长度的数组是不同的数组,不同长度的数组也不能互相赋值。
// 定义数组的方式
a1 := [3]int{1,2,3} // 以固定长度定义。
a2 := [...]int{1,2} // 根据后面初始化的元素个数来动态决定数组长度。
关于数组作为函数参数的问题:
func main() {
arr := [3]int{1, 2, 3}
charr(arr)
fmt.Println(arr) // 结果为1 2 3
charrp(&arr)
fmt.Println(arr) // 结果为-1 2 3
}
func charr(arr [3]int) {
arr[0] = -1
}
func charrp(arr *[3]int) {
arr[0] = -1
}
在go中,所有的函数参数传参都是传值的方式。https://golang.org/doc/faq#pass_by_value
因此对于第一个函数,修改的只是副本,因此原数组没有变化;对于第二个函数,传递的是指针,参数copy了一份指针,指向同一个底层数组,因此原数组被修改了。
字符串
字符串的特点是底层字节数组只读,不可修改。
type StringHeader struct {
Data uintptr
Len int
}
由于字符串只读的特点,因此对于字符串的拼接,实际上是创建了新的底层字节数组,重新指向。
func main() {
s := "first"
t := s
s += "-second"
fmt.Println(t) // first 底层仍然指向原来的first
fmt.Println(s) // first-second 分配新的字符串,s指向新的字符串
}
由于字符串只读的特点,因此字符串原串和子串,共享同一段底层字节序列,因此生成子串的开销较低。
切片
切片类似动态数组,长度不固定。
type SliceHeader struct {
Data uintptr
Len int
Cap int
}
SliceHeader表示一个切片,因此在切片的复制操作中,并不会真正复制底层数据,只是复制了一个SliceHeader指向同一个底层数组。因此将slice作为函数参数传递时,尽管是传值的设计,但由于指向了同一个底层数组,因此在函数内对slice的修改,在函数外是可以共享的。同理对于slice的分割操作,与字符串的子串类似。
切片的截取:可以有三个参数low,high,max需要满足low≤high≤max。low表示被截取切片的起始下标,high表示被截取下标的结束下标,low和high满足左闭右开。对于第三个参数max,新切片cap=max-low,表示能截取到原切片最大的下标,开区间;max需要满足≤原切片cap,否则运行错误;当max缺省时,max=原切片cap
arr := []int{1, 2, 3, 4, 5, 6}
fmt.Println(len(arr), cap(arr)) // 6 6
as1 := arr[0:3]
fmt.Println(len(as1), cap(as1)) // 3 6
as2 := arr[1:3:5]
fmt.Println(len(as2), cap(as2)) // 2 4
切片的扩容
扩容策略:
- 如果申请容量大于原容量的两倍,则扩容容量为申请容量
- 如果原容量小于1024,则每次扩容为原来的2倍
- 如果原容量大于1024,则每次增长原容量的1/4,直到满足申请容量或者溢出
- 如果溢出,则扩容容量为申请容量
newcap := old.cap
doublecap := newcap + newcap
if cap > doublecap {
newcap = cap
} else {
if old.len < 1024 {
newcap = doublecap
} else {
// Check 0 < newcap to detect overflow
// and prevent an infinite loop.
for 0 < newcap && newcap < cap {
newcap += newcap / 4
}
// Set newcap to the requested cap when
// the newcap calculation overflowed.
if newcap <= 0 {
newcap = cap
}
}
}
除此之外,在计算了新容量大小后,会在内存分配上,对新容量的大小做一个调整,保证内存对齐。
switch {
case et.size == 1:
lenmem = uintptr(old.len)
newlenmem = uintptr(cap)
capmem = roundupsize(uintptr(newcap))
overflow = uintptr(newcap) > maxAlloc
newcap = int(capmem)
case et.size == sys.PtrSize:
lenmem = uintptr(old.len) * sys.PtrSize
newlenmem = uintptr(cap) * sys.PtrSize
capmem = roundupsize(uintptr(newcap) * sys.PtrSize)
overflow = uintptr(newcap) > maxAlloc/sys.PtrSize
newcap = int(capmem / sys.PtrSize)
// Returns size of the memory block that mallocgc will allocate if you ask for the size.
func roundupsize(size uintptr) uintptr {
if size < _MaxSmallSize {
if size <= smallSizeMax-8 {
return uintptr(class_to_size[size_to_class8[divRoundUp(size, smallSizeDiv)]])
} else {
return uintptr(class_to_size[size_to_class128[divRoundUp(size-smallSizeMax, largeSizeDiv)]])
}
}
if size+_PageSize < size {
return size
}
return alignUp(size, _PageSize)
}
// divRoundUp returns ceil(n / a).
func divRoundUp(n, a uintptr) uintptr {
// a is generally a power of two. This will get inlined and
// the compiler will optimize the division.
return (n + a - 1) / a
}
底层会根据数据类型大小和请求的内存大小,在预定义的内存对齐表中查找到对应的值,才是最终扩容后新的容量