html5个人网站源码,湖北响应式网页建设企业,网站建设保密协议,域名数和网站数1. 特点 slice并不是数组或数组指针。它通过内部指针和相关属性引用数组片段#xff0c;以实现变长方案。
切片#xff1a;切片是数组的一个引用#xff0c;因此切片是引用类型。但自身是结构体#xff0c;值拷贝传递。切片的长度可以改变#xff0c;因此#xff0c;切片…1. 特点 slice并不是数组或数组指针。它通过内部指针和相关属性引用数组片段以实现变长方案。
切片切片是数组的一个引用因此切片是引用类型。但自身是结构体值拷贝传递。切片的长度可以改变因此切片是一个可变数组。切片的遍历方式和数组一样可以用len()求长度。表示可用元素的数量读写操作不能超过该限制。cap可以求出slice最大扩张容量不能超出数组限制。0 len(slice) len(array)。其中array是slice引用的数组。切片的定义var 变量名 []类型比如var str []string var arr []int。如果slice等于nil那么lencap结果都等于0。
2. 切片源码 切片对应的数据结构定义位于runtime/slice.go文件中其定义如下:
type slice struct {array unsafe.Pointerlen intcap int
}
array指针指向引用数组对应位置。len可用元素个数。cap容量可放元素个数。 我们在进行切片赋值传参截断时其实是复制一个slice结构体只不过底层数组是同一个。这就导致了无论是在复制的切片中修改值还是修改形参切片值都会修改到原来的切片和引用的数组。 总的来说切边底层有一个数组(make创建的切片底层也会先创建一个数组)切片是数组的引用。
3. 创建切片 4. 切片初始化 package mainimport fmt//全局变量
var arr [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
var slice0 []int arr[2:8]
var slice1 []int arr[0:6] //可以简写为arr[:6]
var slice2 []int arr[5:10] //可以简写为arr[5:]
var slice3 []int arr[0:len(arr)] //可以简写为arr[:]
var slice4 []int arr[0 : len(arr)-1] //去掉最后一个元素func main() {fmt.Printf(全局变量arr %v\n, arr)fmt.Printf(全局变量slice0 %v\n, slice0)fmt.Printf(全局变量slice1 %v\n, slice1)fmt.Printf(全局变量slice2 %v\n, slice2)fmt.Printf(全局变量slice3 %v\n, slice3)fmt.Printf(全局变量slice4 %v\n, slice4)fmt.Println(--------------------------)arr2 : [...]int{9, 8, 7, 6, 5, 4, 3, 2, 1, 0}slice5 : arr2[2:8]slice6 : arr2[0:6] //可以简写为arr[:6]slice7 : arr2[5:10] //可以简写为arr[5:]slice8 : arr2[0:len(arr)] //可以简写为arr[:]slice9 : arr2[:len(arr)-1] //去掉最后一个元素fmt.Printf(局部变量arr %v\n, arr2)fmt.Printf(局部变量slice5 %v\n, slice5)fmt.Printf(局部变量slice6 %v\n, slice6)fmt.Printf(局部变量slice7 %v\n, slice7)fmt.Printf(局部变量slice8 %v\n, slice8)fmt.Printf(局部变量slice9 %v\n, slice9)
} 5. 通过make来创建切片
var slice []type make([]type, len)
var slice []type make([]type, len, cap)
slice : make([]type, len)
slice : make([]type, len, cap) 切片的内存布局 读写操作实际目标是底层数组只需要注意索引号的差别。
package mainimport fmtfunc main() {data : [...]int{0, 1, 2, 3, 4, 5}s : data[2:4]s[0] 100s[1] 200fmt.Println(s)fmt.Println(data)
} 可直接创建slice对象自动分配底层数组
package mainimport fmtfunc main() {var s1 []int []int{0, 1, 2, 3, 8: 100} //通过初始化表达式构造可使用索引号fmt.Println(s1, len(s1), cap(s1))s2 : make([]int, 6, 8) //使用make创建指定len和capfmt.Println(s2, len(s2), cap(s2))s3 : make([]int, 6) //使用make创建省略cap相当于caplenfmt.Println(s3, len(s3), cap(s3))
} 使用make动态创建slice避免了数组必须用常量的麻烦。还可以用指针直接访问底层数组退化成普通数组操作。
package mainimport fmtfunc main() {s : []int{1, 2, 3, 4}p : s[1] //获得底层数组元素指针*p 100fmt.Println(s)
} 至于[][]T是指元素类型为[]T
package mainimport fmtfunc main() {data : [][]int{[]int{1, 2, 3},[]int{10, 20, 30, 40},[]int{100, 200},}fmt.Println(data)
} 可以直接修改struct array/slice成员
package mainimport fmtfunc main() {data : [5]struct {x int}{}s : data[:]s[1].x 10s[2].x 20fmt.Println(data)fmt.Printf(%p, %p\n, data, data[0])
} 6. 用append内置函数操作切片(切片追加) append向slice尾部添加数据返回新的slice对象。
package mainimport (fmt
)func main() {var s []int //s是nil没有分配内存fmt.Println(s)s append(s, 1) //append会先为s分配内存再存放数据fmt.Println(s)var a []int []int{1, 2, 3}fmt.Printf(slice a: %v\n, a)b : []int{4, 5, 6}fmt.Printf(slice b: %v\n, b)//append向slice尾部添加数据c : append(a, b...)fmt.Printf(slice c: %v\n, c)d : append(a, 10)fmt.Printf(slice d: %v\n, d)e : append(a, 100, 200, 300)fmt.Printf(slice e: %v\n, e)//append返回的新的slice对象fmt.Printf(a:%p, c:%p, d:%p, e:%p, a, c, d, e)
} 8. slice扩容
超出元slice.cap限制就会重新分配底层数组即便原数组并未填满。 从下面现象可以看出 当数据没有超出slice的cap时如果引用存在的数组变量(不是make创建的)底层数组使用的是存在的数组变量。修改切片的值就会修改数组的值。当数据超出slice的cap时会重新分配底层数组修改切片的值不会修改存在数组变量的值而是修改切片底层数组的值。 package mainimport fmtfunc main() {var data [...]int{1, 2, 3, 4, 10: 0}s : data[:2:3]fmt.Printf(data:%p, data[0]:%p\n, data, data[0]) //data:0xc0000103f0, data[0]:0xc0000103f0//slice中数据保存的是数组对应下标的地址使用索引访问相当于对指针解引用在取地址实际取的是数组中的地址fmt.Printf(s:%p, s[0]:%p\n, s, s[0]) //s:0xc000008048, s[0]:0xc0000103f0//未扩容前修改值s[0] 11fmt.Println(s, data)s append(s, 100, 200)fmt.Println(s, data)fmt.Printf(s[0]:%p, data[0]:%p\n, s[0], data[0])//扩容后修改值s[0] 10fmt.Println(s, data)
} slice中cap重新分配规律 从下面输出可以看出slice扩容通常是以两倍原来容量重新分配底层数组。 建议一次性分配足够大的空间以减少内存分配和数据复制的开销或者初始化足够长的len属性改用索引号进行操作。 及时释放不再使用的slice对象避免持有过期数组造成GC无法回收。
package mainimport fmtfunc main() {s : make([]int, 0, 2)c : cap(s)for i : 0; i 50; i {s append(s, i)if c cap(s) {fmt.Printf(cap: %d-%d\n, c, cap(s))c cap(s)}}
} 9. 切片拷贝 函数copy在两个slice间复制数据复制长度以len小的为准。两个slice可指向同一底层数组允许元素区间重叠(本来创建切片不同的slice也可以指向同一个底层数组)。 应及时将所需数据copy到较小的slice以便释放超大号底层数组内存。
10. slice遍历 slice遍历实际和数组遍历一样。
package mainimport fmtfunc main() {data : [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}s1 : data[:]for index, value : range s1 {fmt.Printf(s1[%d] %d\n, index, value)}fmt.Println(-----------------------------)for i : 0; i len(s1); i {fmt.Printf(s1[%d] %d\n, i, s1[i])}
} 11. 切片调整大小 由于底层数组相同且数组空间连续。所以可以像下面赋值。 12. 数组和切片的内存分布 底层数组不一定要声明出来比如make的切片或者 s : []int{1, 2, 3} 13. 字符串和切片 string底层就是一个byte的数组因此也可以进行切片操作。
package mainimport fmtfunc main() {str : hello worlds : str[:5]fmt.Println(s)s1 : str[6:]fmt.Println(s1)
} 修改字符串
string本身是不可修改的要修改string需要先转成[]byte或[]rune进行修改再转成string。 英文字符串 package mainimport fmtfunc main() {str : hello world//字符串不能修改// s1 : str[:8]// s1[6] G// str[6] G//转成[]bytebt : []byte(str) //中文字符需要用[]rune(str)//修改[]bytebt[6] Gbt bt[:8]bt append(bt, !)str string(bt)fmt.Println(str)
} 含中文字符串 package mainimport fmtfunc main() {str : 你好 世界 hello world//字符串不能修改// s1 : str[:8]// s1[6] G// str[6] Grn : []rune(str)//修改[]bytern[3] 够rn[4] 浪rn rn[:12]rn append(rn, 好)//再转回字符串str string(rn)fmt.Println(str)
} 数组或切片转字符串
strings.Replace(strings.Trim(fmt.Sprint(array_or_slice), []), , ,, -1) golang slice data[:6:8]两个冒号的理解 对于data : [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} 常规slicedata[6:8]从data数组索引6到索引7长度len为2最大可扩充长度cap为4(slice底层数组为data指针指向索引6位置cap从索引6位置到data最后即6-9所以cap为4)。 另一种写法data[:6:8]每一个数字前面都有冒号slice内容为data索引从0到6长度len为6最大扩充项cap设置为8。 a[x:y:z]切片内容[x:y]前闭后闭切片长度y-x切片容量z-x
package mainimport fmtfunc main() {data : [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}s1 : data[6:8]fmt.Println(len(s1), cap(s1))//s2 : data[6: :8] 中间必须要有数字s2 : data[:6:8]fmt.Println(len(s2), cap(s2))fmt.Println(s2)
}