golang数组与切片

本文详细介绍了Go语言中的数组和切片的区别与使用。数组是固定长度的值类型,而切片是引用类型,其在函数传递中表现为引用传递。切片的扩容遵循特定规则,当大小小于1024时每次翻倍,大于1024时按1.25倍增长。通过基准测试发现,切片在某些情况下可能产生额外的堆分配和性能开销。因此,选择数组或切片需根据具体场景权衡效率和内存使用。
摘要由CSDN通过智能技术生成
  1. 数组

  2. 切片

数组

  • 固定长度的特定类型元素组成的序列,与c语言区别,golang中的数组是值类型的.
func main () {
	arr := [2]int{1, 2}
	passArr(arr)
	fmt.Println(arr[0], " ", arr[1])
}

func passArr(arr [2]int) {
	arr[0] = 10
	fmt.Println(arr[0], " ", arr[1])
}
  • 值传递,在子方法中修改数组,不会影响原数组的数据。copy副本的方式,耗费大量空间

  • 使用指针可以减少空间的浪费,但不安全(在子方法中更改指针指向)。

func main () {
	arr := [2]int{1, 2}
	passArr(&arr)
	fmt.Println(arr[0], " ", arr[1])
}

func passArr(arr *[2]int) {
	arr[0] = 10
	arr2 := [2]int{100, 200}
	arr = &arr2
	fmt.Println(arr[0], " ", arr[1])
}

使用切片可以避免上述问题,切片是引用传递更具有效率,但依旧又反例:

func Array() (x [1024]int) {
	for i := 0; i < 1024; i++ {
		x[i] = i
	}
	return x
}

func Slice() (y []int) {
	y = make([]int, 1024)
	for i :=0; i < 1024; i++ {
		y[i] = i
	}
	return y
}

func BenchmarkArray(b *testing.B) {
	for i := 0; i < b.N; i++ {
		Array()
	}
}

func BenchmarkSlice(b *testing.B) {
	for y := 0; y < b.N; y++ {
		Slice()
	}
}

执行: go test -bench . -benchmem -gcflags “-N -l”

BenchmarkArray-8          364608              3747 ns/op               0 B/op          0 allocs/op
BenchmarkSlice-8          156194              6612 ns/op            8192 B/op          1 allocs/op

从benchmark结果开出来:在8核上,数组循环364608次,平均每次执行时间为3747,堆上内存分配与分配次数均为0;而切片则差写,循环156194次,平均执行时间为6612(近乎两倍),对上分配总内存8192,分配次数也是1

  • 如此,并非所有的时候都适合用切片替换数组,因为切片可能会在堆上分配内存,而且小数组在栈上copy也未必消耗比make大

切片

1. 数据结构

slice代表变长的序列,一个slice由三部分组成:指针、长度、容量,指针指向切片的第一个元素(存储底层数组中的某个元素的地址),长度对应slice的数目,容量是slice的最大长度(len函数),超过了需要动态扩容(cap函数)

2. slice作为参数真的是引用传递吗

func main() {
	arr := []int{1, 2}
	fmt.Printf("%p,\n", &arr)
	passArr(arr)
}

func passArr(arr []int) {
	fmt.Printf("%p,\n", &arr)
}

输出

0xc0000044a0,
0xc0000044e0,
  • 明显地址不同,能达到引用传递的效果是因为复制了底层数组的指针
  • golang没有引用传递(或者是同于c/c++一样的引用传递)

3. 切片比较?

slice can only be compared to nil

4. slice扩容原则

上源码

func growslice(et *_type, old slice, cap int) slice {
    if et.size == 0 {
        if cap < old.cap {
            panic(errorString("growslice: cap out of range"))
        }
        // append should not create a slice with nil pointer but non-zero len.
        // We assume that append doesn't need to preserve old.array in this case.
        return slice{unsafe.Pointer(&zerobase), old.len, cap}
    }

    newcap := old.cap
    doublecap := newcap + newcap
    if cap > doublecap {
        newcap = cap
    } else {
        // 小于1024,*2扩容
        if old.len < 1024 {
            newcap = doublecap
        } else {
            // 大于1024,*1.25
            for newcap < cap {
                newcap += newcap / 4
            }
        }
    }
  // 下面代码省略
  ....
}
  • 小于1024,每次扩容后的cap = oldCap * 2
  • 大于1024,每次扩容cap = oldCap * 1.25

注意:slice的扩容会涉及到数组的copy,然后产生新的数组,slice也指向新的数组,会给系统带来额外的开销。

5. slice 删除元素

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Golang数组切片是两种不同的数据类型,用于存储相同数据类型的容器。数组的长度是固定的,而切片的长度是可变的。在日常应用中,切片的使用更为普遍。 数组在声明时需要指定长度,并且在初始化时必须提供相同长度的元素。例如,`a := int{1, 2, 3}`就是一个长度为3的整数数组数组的长度一旦确定后就不能更改。 切片是基于数组的引用类型。它不需要指定固定的长度,并且可以根据需要动态扩展或缩小。切片包装着底层数组,通过指定起始索引和结束索引来指定子集。例如,`b := a[:]`就是一个切片,它包含了数组a的所有元素。 数组适用于需要固定长度的场景,而切片适用于长度可变的情况。在实际应用中,切片更常用,因为它提供了更大的灵活性和便利性。 总结: - 数组是长度固定的容器,切片是长度可变的容器; - 数组在声明时需要指定长度,切片则不需要; - 数组的长度一旦确定后就不能更改,而切片可以根据需要动态扩展或缩小; - 切片是基于数组的引用类型,可以通过指定起始索引和结束索引来指定子集。 参考资料: Golang中的「数组」和「切片」都是存储同一数据类型的容器,只不过Golang中的数组长度是固定的,而切片的长度是可变化的。 切片是引用类型,切片包装的数组称为该切片的底层数组。我们来看一段代码://a是一个数组,注意数组是一个固定长度的,初始化时候必须要指定长度,不指定长度的话就是切片了 a := int{1, 2, 3} //b是数组,是a...。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值