Golang

关注公众号 jb51net

关闭
首页 > 脚本专栏 > Golang > Golang动态数组

Golang动态数组的实现示例

作者:勇者无畏404

动态数组能自动调整大小,与静态数组不同,其大小不固定,可根据需求变化,实现通常依赖于数据结构如链表或数组加额外信息,本文就来介绍一下Golang动态数组的实现示例,感兴趣的可以了解一下

什么是动态数组

动态数组(Dynamic Array)是一种在需要时能够自动改变其大小的数组。与静态数组(Static Array)不同,静态数组的大小在声明时就已确定,且之后不能更改,而动态数组的大小则是根据需要动态变化的。动态数组通常通过某种数据结构(如链表、数组加额外信息等)来实现,以便在需要时能够扩展或缩小其容量。(关键字:动态扩缩容,基于链表或数组实现)

在大多数现代编程语言中,如C++的std::vector、Java的ArrayList、Python的列表(list)等,都提供了动态数组的功能。这些实现通常会在内部使用一个静态数组来存储元素,并跟踪当前数组的大小和已分配的容量。当向动态数组添加元素而当前容量不足时,它们会自动分配一个新的、更大的数组,并将旧数组的元素复制到新数组中,然后再添加新元素。这个过程对用户是透明的,用户无需关心底层的内存管理细节。

Go语言中的动态数组——切片(slice)

Go语言(Golang)中的切片(slice)是一种引用类型,它提供了对数组的抽象。切片是对数组一个连续片段的引用,但它比数组更灵活、更强大。以下是Go语言切片的一些主要特点:

切片数据结构

type slice struct {
    // 底层数组指针(指向一块连续内存空间的起点)
    array unsafe.Pointer
    // 切片长度
    len   int
    // 切片容量   
    cap   int
}

 cap>=len

切片的创建和初始化

//通过字面量创建

	/**
	创建字符串切片,长度和容量都是3
	 */
	slice1 := []string{"aaa","bbb","ccc"}
	fmt.Println(slice1)  //[aaa bbb ccc]

	/**
	创建整形切片,长度和容量都是4
	 */
	slice2 := []int{10,20,30,40}
	fmt.Println(slice2) //[10 20 30 40]

	//当使用切片字面量创建切片时,还可以设置初始长度和容量。

	/**
	创建一个长度和容量都是100的切片 索引0 = 10,索引99 = 1
	 */
	slice3 := []int{0:10,99:1}
	fmt.Println(slice3) //[10 0 0 ... 1]

	//通过 make() 函数创建切片


	/**
	创建长度和容量都是5的切片
	 */
	slice4 := make([]int,5)
	fmt.Println(slice4)  //[0 0 0 0 0] 默认对应类型的零值

	/**
	创建长度=3,容量=5的切片
	 */
	slice5 := make([]string,3,5)
	fmt.Println(slice5)  //[  ]

	/**
	不允许创建容量小于长度的切片
	 */
	slice6 := make([]int,5,3)
	fmt.Println(slice6)  //invalid argument: length and capacity swapped

切片初始化源码

func makeslice(et *_type, len, cap int) unsafe.Pointer {

    mem, overflow := math.MulUintptr(et.size, uintptr(cap))
    if overflow || mem > maxAlloc || len < 0 || len > cap {

        mem, overflow := math.MulUintptr(et.size, uintptr(len))
        if overflow || mem > maxAlloc || len < 0 {
            panicmakeslicelen()
        }
        panicmakeslicecap()
    }

    return mallocgc(mem, et, true)
}

切片在方法之间的传递

func main() {
	sl := []int{1,2,3,4,5}
	fmt.Println(sl)  //[1 2 3 4 5]
	change(sl)
	fmt.Println(sl)  //[10 2 3 4 5]
}
func change(sl []int) {
	sl[0] = 10
}

每次在方法之间传递切片时,会将切片实例本身进行一次值拷贝(也可以叫浅拷贝),会将array指针,len长度,cap容量进行拷贝所以方法中和方法外的切边array指向同一片内存空间,因此在局部方法中执行修改操作时,还会根据这个地址信息影响到原 slice 所属的内存空间,从而对内容发生影响;

但是如果在函数中的切片发生了扩容,此时内外就是两个独立的切片;

截取切片

在Go语言中,可以通过指定起始索引和结束索引来截取一个新的切片(包前不包后);

slice[start:end]
//slice要截取的原始切片
//start截取切片的开始位置,不填默认为0
//end截取切片的结束位置,不填默认为切片长度
s := []int{1, 2, 3, 4, 5}
	// 截取整个切片
	subS1 := s[:]
	fmt.Println(subS1) // 输出: [1 2 3 4 5]

	// 截取索引1到4(不包含4)的元素
	subS2 := s[1:4]
	fmt.Println(subS2) // 输出: [2 3 4]

	// 从索引0开始截取到索引4(不包含4)
	subS3 := s[:4]
	fmt.Println(subS3) // 输出: [1 2 3 4]

	// 从索引2开始截取到切片末尾
	subS4 := s[2:]
	fmt.Println(subS4) // 输出: [3 4 5]

截取出的切片和原始切片的关系

在对切片 slice 执行截取操作时,本质上是一次引用传递操作,因为不论如何截取,底层复用的都是同一块内存空间中的数据,只不过,截取动作会创建出一个新的 slice header 实例;

 s := []int{2,3,4,5}
  s1 := s[1:]

往切片中新增元素(append)

通过append方法可以实现在切片的末尾添加元素

func main() {  
    s := []int{1, 2, 3}  
    // 使用 append 函数在切片末尾添加元素 4  
    s = append(s, 4)  
    fmt.Println(s) // 输出: [1 2 3 4]  
}

func main() {  
    s := []int{1, 2, 3}  
    // 使用 append 函数在切片末尾添加多个元素  
    s = append(s, 4, 5, 6)  
    fmt.Println(s) // 输出: [1 2 3 4 5 6]  
}

func main() {  
    s1 := []int{1, 2, 3}  
    s2 := []int{4, 5, 6}  
    // 使用 append 函数将 s2 添加到 s1 的末尾  
    s1 = append(s1, s2...) // 注意 s2 后的 ... 用于将 s2 展开为元素序列  
    fmt.Println(s1) // 输出: [1 2 3 4 5 6]  
}

func main() {  
    s := []int{1, 3, 4, 5}  
    // 在索引 1 的位置插入元素 2  
    // 首先,将索引 1 及其之后的元素向后移动一位  
    s = append(s[:1], append([]int{2}, s[1:]...)...)  
    fmt.Println(s) // 输出: [1 2 3 4 5]  
}

删除切片中的元素

在Go语言中,切片(slice)本身并不提供直接的删除元素的方法,因为切片是对底层数组的抽象,而数组的大小是固定的。但是,通过以下几种方式来实现“删除”切片中的元素;

//使用切片操作删除元素(适用于删除末尾元素)
s := []int{1, 2, 3, 4, 5}  
// 删除切片末尾的元素  
s = s[:len(s)-1]  
fmt.Println(s) // 输出: [1 2 3 4]


//使用append和切片操作删除任意位置的元素
s := []int{1, 2, 3, 4, 5}  
// 假设我们要删除索引为2的元素(值为3)  
index := 2  
// 将index之后的元素(不包括index)复制到前面,然后截断切片  
s = append(s[:index], s[index+1:]...)  
fmt.Println(s) // 输出: [1 2 4 5]

//使用循环和条件语句删除满足条件的元素
s := []int{1, 2, 3, 4, 5}  
// 删除所有值为3的元素  
var newS []int  
for _, value := range s {  
    if value != 3 {  
        newS = append(newS, value)  
    }  
}  
s = newS  
fmt.Println(s) // 输出: [1 2 4 5]

拷贝切片

slice 的拷贝可以分为简单拷贝和完整拷贝两种类型;

要实现简单拷贝(浅拷贝),我们只需要对切片的字面量进行赋值传递即可,这样相当于创建出了一个新的 slice header 实例,但是其中的指针 array、容量 cap 和长度 len 仍和老的 slice header 实例相同;

slice 的完整复制(深拷贝),指的是会创建出一个和 slice 容量大小相等的独立的内存区域,并将原 slice 中的元素一一拷贝到新空间中,在实现上,slice 的完整复制可以调用系统方法 copy;

s := []int{1, 2, 3}  
// 分配一个新的切片,长度和容量与s相同  
sCopy := make([]int, len(s))  
// 使用copy函数复制元素  
copy(sCopy, s)  
  
// 现在sCopy是s的一个独立拷贝  
fmt.Println(sCopy) // 输出: [1 2 3]

切片的扩容

什么时候会触发扩容

当 slice 当前的长度 len 与容量 cap 相等时,下一次 append 操作就会引发一次切片扩容;

扩容步骤

1.计算目标容量
case1:如果新切片的长度>旧切片容量的两倍,则新切片容量就为新切片的长度case2:

2.进行内存对齐
需要按照Go内存管理的级别去对齐内存,最终容量以这个为准

到此这篇关于Golang动态数组的实现示例的文章就介绍到这了,更多相关Golang动态数组内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文