• 命名返回值:没有参数的 return 语句返回已命名的返回值
  • println 的执行顺序。会先把函数计算结束之后,再按照顺序输出
  • 没有条件的 switch 同 switch true 一样。
  • defer函数调用会被压入一个栈中,所以后defer的值会先输出
  • 结构体指针的使用:修改结构体指针的值时,不需要带*号。
  • 数组定义的几种方式

    关于切片

  • 切片就像数组的引用,改变切片会改变底层的值

  • 切片的默认行为,可以不写完全部的上下限,因为切片默认有上下限
  • 只是截取切片是不会改变切片大小的

  • 切片的长度就是它所包含的元素个数。

  • 切片的容量是从它的第一个元素开始数,到其底层数组元素末尾的个数
  • 切片的零值是 nil

  • 函数的闭包

  • 接收者的类型定义和方法声明必须在同一包内;不能为内建类型声明方法,可以定义别名,**type **MyFloat float64

    关于方法和接口

  • 指针参数的函数必须接受一个指针,而以指针为接收者的方法被调用时,接收者既能为值又能为指针

  • 跟C++中一样,使用指针接收者可以修改接收者指向的值;另外,可以避免每次在调用方法时复制该值。

1.函数执行顺序是否是后序遍历的方式,可以再嵌套一层方法试试? 画个调用树出来看看?
我在网上没有找到相关的解释,就自己查看了fmt.Println()函数的源码,会先调用Fprintln函数,而Fprintln会先执行传入的函数,存在p的buf中,之后转换成string再打印。

1
2
3
4
5
6
7
8
9
10
func Println(a ...interface{}) (n int, err error) {
return Fprintln(os.Stdout, a...)
}
func Sprintln(a ...interface{}) string {
p := newPrinter()
p.doPrintln(a)
s := string(p.buf)
p.free()
return s
}

同时也重新把打印语句加复杂了一下,结果与预期相符。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package main

import "fmt"

func add(x,y int) int{
sum:=x+y
fmt.Println(sum)
return sum
}

func main(){
fmt.Println(add(1,2),add(add(1,1),add(3,7)),add(4,5))
}
运行结果为:
3
2
10
12
9
3 12 9
  1. 切片的增长为什么是20,是否在大于1024或某个值时,有其他的增长方法? 比如 增加1.2倍? 可以贴源码作为论据
    查看了一下slice中的growslice函数,可以看到当容量小于1024时,是按照2倍进行增长的,当超过1024时,按照1.25倍进行增长,应该是从节省空间的角度考虑,毕竟对一个很长的数组,再插入同样长度的数的概率很小。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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
}
}
}

通过代码测试上述结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
func main(){
var a []int
for i:=0;i<1500;i++{
a=append(a,i)
if i%100==0{
fmt.Printf("len: %d , cap; %d\n",len(a),cap(a))
}
}
}

输出结果为:
len: 1 , cap; 1
len: 101 , cap; 128
len: 201 , cap; 256
len: 301 , cap; 512
len: 401 , cap; 512
len: 501 , cap; 512
len: 601 , cap; 1024
len: 701 , cap; 1024
len: 801 , cap; 1024
len: 901 , cap; 1024
len: 1001 , cap; 1024
len: 1101 , cap; 1280
len: 1201 , cap; 1280
len: 1301 , cap; 1696
len: 1401 , cap; 1696

如上符合源码结果,所以之前的分析有些问题,但是append多个值的时候,与之前的结果相同。再次查看源码,发现实际上还有内存对齐的考虑:

1
capmem = roundupsize(uintptr(newcap) * sys.PtrSize)

其中capmem会根据roundupsize函数进行内存对齐。其中uintptr(newcap)表示新容量的大小,sys.PtrSize表示一个元素的大小,int时为8。roundupsize函数的实现如下

1
2
3
4
5
6
7
8
9
10
11
12
13
func roundupsize(size uintptr) uintptr {
if size < _MaxSmallSize {
if size <= smallSizeMax-8 {
return uintptr(class_to_size[size_to_class8[(size+smallSizeDiv-1)/smallSizeDiv]])
} else {
return uintptr(class_to_size[size_to_class128[(size-smallSizeMax+largeSizeDiv-1)/largeSizeDiv]])
}
}
if size+_PageSize < size {
return size
}
return round(size, _PageSize)
}

其中:_MaxSmallSize=32768,2的15次方,是32K。当需要分配的size大于32K时,需要mchche向mcentral申请;当size小于32K时,计算应该分配的sizeclass,直接去mchche申请。(关于具体的内存申请方面,在后面的开发需要的时候再仔细研究)
当size小于smallSizeMax-8=1024-8=1016时,采用size_to_class8的分配方式,大于1016时采用size_to_class128的分配方式。

1
2
3
4
size_to_class8 = [smallSizeMax/smallSizeDiv + 1]uint8{0, 1, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, 14, 14, 15, 15, 16, 16, 17, 17, 18, 18, 18, 18, 19, 19, 19, 19, 20, 20, 20, 20, 21, 21, 21, 21, 22, 22, 22, 22, 23, 23, 23, 23, 24, 24, 24, 24, 25, 25, 25, 25, 26, 26, 26, 26, 26, 26, 26, 26, 27, 27, 27, 27, 27, 27, 27, 27, 28, 28, 28, 28, 28, 28, 28, 28, 29, 29, 29, 29, 29, 29, 29, 29, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31}
size_to_class128 = [(_MaxSmallSize-smallSizeMax)/largeSizeDiv + 1]uint8{31, 32, 33, 34, 35, 36, 36, 37, 37, 38, 38, 39, 39, 39, 40, 40, 40, 41, 42, 42, 43, 43, 43, 43, 43, 44, 44, 44, 44, 44, 44, 45, 45, 45, 45, 46, 46, 46, 46, 46, 46, 47, 47, 47, 48, 48, 49, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 52, 52, 53, 53, 53, 53, 54, 54, 54, 54, 54, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 57, 57, 57, 57, 57, 57, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 60, 60, 60, 60, 60, 61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66}

class_to_size = [_NumSizeClasses]uint16{0, 8, 16, 32, 48, 64, 80, 96, 112, 128, 144, 160, 176, 192, 208, 224, 240, 256, 288, 320, 352, 384, 416, 448, 480, 512, 576, 640, 704, 768, 896, 1024, 1152, 1280, 1408, 1536, 1792, 2048, 2304, 2688, 3072, 3200, 3456, 4096, 4864, 5376, 6144, 6528, 6784, 6912, 8192, 9472, 9728, 10240, 10880, 12288, 13568, 14336, 16384, 18432, 19072, 20480, 21760, 24576, 27264, 28672, 32768}

可以看到两个数组值的不同,并且间隔的粒度也不同,当size较小时,间隔为8,当size较大时,间隔为128.
而为了内存优化考虑,class_to_size会控制分配大小,间隔从8-4096
以int为例

个数123456789
字节数81624324048566472
所占空间81632324848646480
size_to_class8123344556