range
range是go中的一种循环,被广泛应用于slice,map,channel的遍历中,但是很多时候,它运行的结果似乎和我们预期不一样。
下面是一个例子:
v := []int{1, 2, 3}
for _, value := range v {
v = append(v, value)
}
fmt.Println(v)//[1 2 3 1 2 3]
你可能会觉得这个程序无法停下来,因为在遍历的过程中不断向其中添加元素,实际上是不会的,range的底层是一个for循环,for的次数在range执行的那一刻就被确定了,下面是go编译器的slice range源码中的注释
for_temp := range
len_temp := len(for_temp)
for index_temp = 0; index_temp < len_temp; index_temp++ {
value_temp = for_temp[index_temp]
index = index_temp
value = value_temp
original body
}
再来一个例子:
items := []Item{Item{1}, Item{2}, Item{3}}
var all []*Item
for _, item := range items {
all = append(all, &item)
}
fmt.Println(all)
for _, item := range all {
fmt.Println(item.value)
}
//Output:
//[0xc00001e088 0xc00001e088 0xc00001e088]
//3
//3
//3
你可能觉得all中应该是1,2,3,实际上不是的,因为range中使用的item实际上每次变量都是同一个,只不过这个item被多次赋值了而已,底层没有每次循环都开辟一个新的空间,因此获取的item的地址实际上是同一个item.
最后一个例子:
var prints []func()
for _, v := range []int{1, 2, 3} {
prints = append(prints, func() { fmt.Println(v) })
}
for _, print := range prints {
print()
}
//Output:
//3
//3
//3
你可能会奇怪为什么不是1,2,3,实际上这也涉及到一点闭包的知识,在闭包中如果直接使用闭包外的值,实际上闭包会捕获这个值,在闭包中对这个值的修改会直接影响到闭包外的变量,也就是说闭包获得了v的地址,而range中使用的v实际上都是同一个,自然最后都是输出3.
但以上问题都可以在使用中通过下面这样的方式解决:
for _,v:=range []int{1,2,3}{
v:=v
//Todo something on v
}
以上代码所在的工作就是声明了一个局部的v,并进行了重新赋值,本质是声明一个局部变量覆盖外层的v,外面的for那一行的v实际上是和for所在的区域属于同一个作用域,只声明一次,被反复赋值。
slice
切片区别于数组,数组的长度固定,不可改变,切片可以通过内存分配的方式改变其长度,切片可以简单理解成一个数组的指针。
其“/src/runtime/slice.go”中定义的底层结构是这样的:
type slice struct {
array unsafe.Pointer
len int
cap int
}
显然,array是一个指向了底层的数组的指针,len表示当前切片的长度,cap表示切片的容量。
切片创建
- 引用自数组
var array [10]int slice := array[1:8]
fmt.Println(cap(slice))//9
slice1 := array[5:6:7]
fmt.Println(cap(slice1))//2
array[start:end]方式获取切片slice的默认容量是从start开始直至array末尾 array[start:end:max],max表示切片的容量结束位置,start [0,10)
// Count(0)->[0,0)返回值恒为0,相当于不统计
// Count(5)->[0,5)
// Count(5,7)->[5,7)
// Count(-5) ->[10-5,10)
// Count(-1,-7)->[10-7,10-1]
// Count(4,12)->[4,10)
func (bm *Bitmap) Count(pos ...int) int {
count := 0
if len(pos) == 0 {
for _, value := range bm.data {
// 使用 Brian Kernighan's 算法统计一个整数中1的个数
//-1会使得从右往左数开始的最近一个1的位置原来的1变成0,该位置之后的值之前是0,减去1之后变成1,然后与之前的数进行与,
//由于除了右数最近的一个1前面的都不变,所以进行与运算不会改变,但是之后的都会变成0,所以就会造成一种现象,每次都取最右侧的一个1变为0,
//直到取完所有1.
for value != 0 {
value = value & (value - 1)
count++
}
}
return count
} else if len(pos) == 1 {
if pos[0] == 0 {
return 0
} else if pos[0] > 0 {
if pos[0] > bm.size {
pos[0] = bm.size
}
return bm.countBits(0, pos[0])
} else {
if -pos[0] = 0 && pos[1] > 0 && pos[1] > pos[0] {
return bm.countBits(pos[0], pos[1])
} else if pos[0] pos[1] {
return bm.countBits(bm.size+pos[1], bm.size+pos[0])
} else {
return bm.Count(pos[0])
}
}
}
func (bm *Bitmap) countBits(n, m int) int {
count := 0
for i := n; i < m; i++ {
if bm.GetBit(i) {
count++
}
}
return count
}
func (bm *Bitmap) Invert() {
for i := 0; i < len(bm.data); i++ {
bm.data[i] = ^bm.data[i]
}
}
func WithSliceByte(data []byte) Bitmap {
// 计算所需的位图大小
size := len(data) * 8
// 根据计算得到的位图大小创建位图对象
bitmap := Bitmap{
data: make([]uint32, (size+31)/32),
size: size,
}
// 将字节数据转换为位图数据
for i, b := range data {
offset := i * 8
for j := 0; j < 8; j++ {
if b&(1