Go 语言入门指南:基础语法和常用特性解析 | 青训营

2023年 8月 13日 66.5k 0

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

相关文章

JavaScript2024新功能:Object.groupBy、正则表达式v标志
PHP trim 函数对多字节字符的使用和限制
新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
为React 19做准备:WordPress 6.6用户指南
如何删除WordPress中的所有评论

发布评论