【go语言】数组和切片

一、数组

1.1 什么是数组

       数组是一组数:数组需要是相同类型的数据的集合;数组是需要定义大小的;数组一旦定义了大小是不可以改变的。

1.2 数组的声明

  • 数组和其他变量定义没有什么区别,唯一的就是这个是一组数,需要给定一个大小,比如:[6] int,[12] string。
  • 数组是一个相同类型数据的有序集合,通过下标来取出对应的数据。
  • 数组有以下几个特点:
    • 长度必须是确定的,如果不确定,就不是数组,大小不可以改变
    • 元素必须是相同类型,不能是多个类型混合(any 也是类型,可以存放任意类型的数据)
    • 数组中元素的类型,可以是我们学的所有类型:int、string、float、bool、array、slice、map
    • 长度不一样,数组的类型就不一样
package main
import "fmt"

func main() {
   // array数组定义,变量
   // 数组也是一个数据类型
   // 数组的定义:  [数组的大小size]变量的类型 ,
   // 我们定义了一组这个类型的数组集合,大小为size,最多可以保存size个数
   var arr1 [5]int
   // [0,0,0,0,0]
   // 给数组赋值,下标index,所有的数组下标都是从0开始的。
   arr1[0] = 100
   arr1[1] = 200
   arr1[2] = 300
   arr1[3] = 400
   arr1[4] = 500

   // 打印数组
   fmt.Println(arr1)

   // 取出数组中的某个元素
   fmt.Println(arr1[1])

   // 数组中的常用方法 len()获取数组的长度  cap() 获取数组的容量
   fmt.Println("数组的长度:", len(arr1))
   fmt.Println("数组的容量:", cap(arr1))

   // 修改数组的值,index 1 代表的第二个数据了
   arr1[1] = 10
   fmt.Println(arr1)
   fmt.Println(arr1[1])
}

1.3 初始化数组的几种方式

package main
import "fmt"

// 数组的赋值初始化
func main() {
	// 在定义数组的时候就直接初始化
	var arr1 = [5]int{1, 2, 3, 4, 5}
	fmt.Println(arr1)

	// 快速初始化 :=
	arr2 := [5]int{1, 2, 3, 4, 5}
	fmt.Println(arr2)

	// 比较特殊的点
	// 数据如果来自用户,我不知道用户给我多少个数据,数组
	// ... 代表数组的长度
	// Go的编译器会自动根据数组的长度来给 ... 赋值,自动推导长度
	// 注意点:这里的数组不是无限长的,也是固定的大小,大小取决于数组元素个数。
	var arr3 = [...]int{1, 2, 3, 4, 5, 5, 6, 7, 8, 8, 8}
	fmt.Println(len(arr3))
	fmt.Println(arr3)

	// 数组默认值,我只想给其中的某几个index位置赋值。
	// {index:值}
	var arr4 [10]int
	arr4 = [10]int{1: 100, 5: 500}
	fmt.Println(arr4) // [0 100 0 0 0 500 0 0 0 0]

}

1.4 遍历数组元素

package main

import "fmt"

/*
1、直接通过下标获取元素 arr[index]

2、 0-len i++ 可以使用for循环来结合数组下标进行遍历

3、for range:范围   (new)
*/
func main() {

   var arr1 = [5]int{1, 2, 3, 4, 5}
   fmt.Println(arr1[0])
   fmt.Println(arr1[1])
   fmt.Println(arr1[2])
   fmt.Println(arr1[3])
   fmt.Println(arr1[4])
   // 错误:index 5 out of bounds [0:5] 数组下标越界
   // 数组的长度只有5,你要取出6个元素,不可能取出
   //fmt.Println(arr1[5])
   fmt.Println("------------------")
   // 获取数组的长度  len()
   // 下标从0开始,不能<=
   for i := 0; i < len(arr1); i++ {
      fmt.Println(arr1[i])
   }
   fmt.Println("------------------")
   // goland 快捷方式 数组.for,未来循环数组、切片很多时候都使用for    range
   // for 下标,下标对应的值  range 目标数组切片
   // 就是将数组进行自动迭代。返回两个值 index、value
   // 注意点,如果只接收一个值,这个时候返回的是数组的下标
   // 注意点,如果只接收两个值,这个时候返回的是数组的下标和下标对应的值
   for _, value := range arr1 {
      fmt.Println(value)
   }

}

1.5 数组是值类型

1.5.1 介绍

       在 go 语言中,数值是值类型。这意味着,当你将一个数组赋值给另一个数组时,go 会创建一个副本,而不是引用同一块内存。这是与引用类型(比如切片、slice、映射 map、通道 channel)的区别之一。

package main

import "fmt"

func main() {
    arr1 := [3]int{1, 2, 3} // 创建一个数组
    arr2 := arr1              // 将 arr1 赋值给 arr2

    arr2[0] = 100             // 修改 arr2 的第一个元素

    fmt.Println("arr1:", arr1) // 输出 arr1: [1 2 3]
    fmt.Println("arr2:", arr2) // 输出 arr2: [100 2 3]
}

       在上面的例子中,arr1arr2 是两个独立的数组,它们分别存储在不同的内存位置。当你修改 arr2 时,arr1 不会受到影响,因为它们是各自的副本。

1.5.2 为什么数组是值类型

  • 数组在 Go 语言中具有固定的大小(即数组的长度是类型的一部分),这导致数组的值会在赋值时进行拷贝。赋值操作会复制整个数组的内容,而不是传递数组的引用。

1.5.3 和切片的区别

       切片slice)是 Go 中的引用类型,它并不存储数据本身,而是引用底层数组。当你将一个切片赋值给另一个切片时,它们共享底层数组的相同数据。

package main

import "fmt"

func main() {
    slice1 := []int{1, 2, 3} // 创建一个切片
    slice2 := slice1          // slice2 引用 slice1 底层的数组

    slice2[0] = 100           // 修改 slice2 的第一个元素

    fmt.Println("slice1:", slice1) // 输出 slice1: [100 2 3]
    fmt.Println("slice2:", slice2) // 输出 slice2: [100 2 3]
}

       在这个例子中,修改 slice2 会影响 slice1,因为它们共享相同的底层数组。

  • 数组是值类型:赋值时会复制整个数组的内容。
  • 切片是引用类型:赋值时两个切片会共享相同的底层数组

1.6 数组的比较

       在 go 语言中,数组是可以直接进行比较的,但是有一些限制。具体来说,go 语言允许你比较两个数组是否相等,只要他们的类型、长度和元素的值都相同。比较时,会逐一比较数组的元素,如果所有的元素都相同,数组就视为相等。

1.6.1 允许比较

  • 类型要求一致:两个数组必须具有相同的类型(包括相同的元素类型和相同的长度)。
  • 逐个元素比较:Go 会逐个元素进行比较,如果数组的所有元素都相等,则认为这两个数组相等。
package main

import "fmt"

func main() {
    arr1 := [3]int{1, 2, 3}
    arr2 := [3]int{1, 2, 3}
    arr3 := [3]int{3, 2, 1}
    arr4 := [4]int{1, 2, 3, 4}

    fmt.Println(arr1 == arr2) // 输出: true
    fmt.Println(arr1 == arr3) // 输出: false
    fmt.Println(arr1 == arr4) // 输出: 编译错误: 数组长度不同,不能比较
}
  1. arr1 == arr2 返回 true,因为它们的长度相同且每个元素的值都相同。
  2. arr1 == arr3 返回 false,因为数组的元素顺序不同。
  3. arr1 == arr4 会导致编译错误,因为这两个数组的长度不同,Go 不允许直接比较长度不同的数组。

1.6.2 不允许比较

  • 长度不同的数组:如上面的 arr1 和 arr4,它们的长度不同,因此无法进行比较。
  • 切片不能直接比较:切片是引用类型,不能直接用 == 进行比较。如果需要比较切片的内容,可以使用 reflect.DeepEqual 或者手动逐个比较切片元素。

        切片不能直接使用 == 比较,但你可以使用 reflect.DeepEqual 来比较切片内容。

package main

import (
    "fmt"
    "reflect"
)

func main() {
    slice1 := []int{1, 2, 3}
    slice2 := []int{1, 2, 3}
    slice3 := []int{3, 2, 1}

    fmt.Println(reflect.DeepEqual(slice1, slice2)) // 输出: true
    fmt.Println(reflect.DeepEqual(slice1, slice3)) // 输出: false
}
  • 在 Go 语言中,数组是可以直接比较的,前提是数组的长度和元素类型必须相同。
  • 切片不能直接进行比较,如果需要比较两个切片的内容,可以使用 reflect.DeepEqual 函数。

二、切片

2.1 介绍

       在 go 语言中,切片是对数组的抽象。go 数组的长度是不可改变的,在特定场景中这样的集合就不太适用,go 中就提供了一种灵活、功能强悍的内置类型——切片(动态数组)。与数组相比,切片的长度是不固定的,可以追加元素,在追加时可能使切片的容量增大

       切片是一种方便、灵活且强大的包装器,切片本身没有任何数据,他们只是对现有数组的引用。切片与数组相比,不需要设定长度,在 [ ] 中不用设定值,相对来说比较自由。

从概念来说,slice 像一个结构体,这个结构体包含了三种元素:

  • 指针:指向数组中 slice 指定的开始位置
  • 长度:即 slice 的长度
  • 最大长度:也就是 slice 开始位置到数组的最后位置的长度
package main

import "fmt"

// 定义切片
func main() {

   arr := [4]int{1, 2, 3, 4} // 定长
   fmt.Println(arr)

   var s1 []int // 变长,长度是可变的
   fmt.Println(s1)
   // 切片的空判断,初始的切片中,默认是 nil
   if s1 == nil {
      fmt.Println("切片是空的")
   }

   s2 := []int{1, 2, 3, 4} // 切片 变长
   fmt.Println(s2)
   fmt.Printf("%T,%T\n", arr, s2) // [4]int,[]int
   fmt.Println(s2[1])
}

2.2 切片的初始化

       在 go 语言中,切片(slice)可以通过几种不同的方式来初始化。这里总结了常见的几种方式:

2.2.1 使用 make 函数

       make 函数是 go 中用来创建切片的标准方式,可以指定切片的长度和容量。

slice := make([]int, 5)  // 创建一个长度为 5 的切片,初始值为零值 [0, 0, 0, 0, 0]

       可以指定切片的长度容量,如果只指定长度,容量默认为长度;如果指定了容量,切片的容量就会扩展到指定的大小。

slice := make([]int, 5, 10)  // 创建一个长度为 5,容量为 10 的切片

2.2.2 通过字面量初始化

       切片也可以通过字面量(literal)来初始化,直接给出一个初始值。这种方式不需要明确指定长度,Go 会根据提供的元素自动计算出长度。

slice := []int{1, 2, 3, 4, 5}  // 创建并初始化一个切片

2.2.3 通过 nil 初始化(默认值)

       当切片没有显式初始化时,它默认是 nil,它的长度和容量都是 0。

var slice []int  // 创建一个 nil 切片,长度和容量为 0
fmt.Println(slice == nil)  // 输出: true

2.2.4 通过数组创建切片

你也可以从一个数组中创建切片,利用数组的部分或全部元素。

arr := [5]int{1, 2, 3, 4, 5}
slice := arr[1:4]  // 创建一个切片,包含 arr 数组的第二到第四个元素 [2, 3, 4]

       在这里,切片 slice 是从数组 arr 中的一部分创建的,切片的长度和容量是基于数组的子集来决定的。

2.2.5 通过 copy 函数拷贝切片

通过 copy 函数,你可以从一个已有的切片创建一个新的切片。

slice1 := []int{1, 2, 3, 4, 5}
slice2 := make([]int, len(slice1))
copy(slice2, slice1)  // 将 slice1 的内容拷贝到 slice2

2.2.6 通过扩容创建切片 

如果切片的容量不足以容纳新的元素,可以通过 append 函数来动态扩容切片。

slice := []int{1, 2}
slice = append(slice, 3, 4)  // 在原切片基础上追加元素
  • make 函数:可以创建指定长度和容量的切片。
  • 字面量初始化:直接使用 []type{} 创建并初始化切片。
  • nil 初始化:默认情况下,未显式初始化的切片为 nil,长度和容量为 0。
  • 从数组创建切片:可以通过数组的部分或全部元素创建切片。
  • copy 函数:可以从现有的切片复制内容到新的切片。
  • append 函数:可以通过动态扩展切片的容量来添加元素。

2.3 切片的数据访问

2.3.1 切片的基础数据访问

       切片的元素可以通过索引进行访问,索引从 0 开始,类似于数组。在切片中,访问超出范围的索引会引发 panic 错误。

package main

import "fmt"

func main() {
    // 初始化一个切片
    slice := []int{10, 20, 30, 40, 50}

    // 通过索引访问切片元素
    fmt.Println(slice[0])  // 输出: 10
    fmt.Println(slice[2])  // 输出: 30

    // 修改切片中的元素
    slice[1] = 100
    fmt.Println(slice)  // 输出: [10 100 30 40 50]

    // 访问越界的索引会导致 panic
    // fmt.Println(slice[10]) // 运行时错误: panic: runtime error: index out of range
}

2.3.2 切片的切片操作

Go 的切片支持通过 slice[low:high] 语法进行切片操作,生成一个新的切片。这个操作包括:

  • 低索引low):指定切片的起始位置(包括该位置),默认是 0。
  • 高索引high):指定切片的结束位置(不包括该位置)。
  • 容量:新切片的容量是原切片从 low 到末尾的部分。

[start, end]

  1. 如果只有 start 没有 end,就表示从 start 开始到结尾的所有数据
  2. 如果没有 start 有 end,表示从0到 end 之前的所有数据
  3. 如果有 start 没有 end,表示从 start 开始到结尾的所有数据
  4. 如果有 start 有 end,表示全部数据
package main

import "fmt"

func main() {
    // 初始化一个切片
    slice := []int{10, 20, 30, 40, 50}

    // 获取从索引 1 到 3(不包括 3)部分的切片
    subSlice := slice[1:3]  
    fmt.Println(subSlice)  // 输出: [20 30]

    // 如果不指定低索引,则默认从 0 开始
    subSlice2 := slice[:3]  
    fmt.Println(subSlice2)  // 输出: [10 20 30]

    // 如果不指定高索引,则默认到切片的末尾
    subSlice3 := slice[2:]  
    fmt.Println(subSlice3)  // 输出: [30 40 50]

    // 获取完整的切片
    fullSlice := slice[:]  
    fmt.Println(fullSlice)  // 输出: [10 20 30 40 50]
}

2.3.3 切片的容量和长度

  • 长度(Length)len(slice) 返回切片的长度,即当前切片中元素的个数。
  • 容量(Capacity)cap(slice) 返回切片的容量,即切片在当前数组中的总容量。容量通常是切片分配的底层数组大小。
package main

import "fmt"

func main() {
    slice := []int{10, 20, 30, 40, 50}

    fmt.Println("Length:", len(slice))  // 输出: Length: 5
    fmt.Println("Capacity:", cap(slice)) // 输出: Capacity: 5

    // 扩展切片
    slice = append(slice, 60)
    fmt.Println("New Length:", len(slice))  // 输出: New Length: 6
    fmt.Println("New Capacity:", cap(slice)) // 输出: New Capacity: 10
}

2.3.4 使用 append 添加元素

  append 是 Go 切片的一个内建函数,它用于向切片末尾添加元素,并且在需要时自动扩容。如果追加的数据超过切片的容量,Go 会创建一个新的底层数组并将原数据和新数据复制过去。

package main

import "fmt"

func main() {
    slice := []int{10, 20, 30}

    // 向切片添加单个元素
    slice = append(slice, 40)
    fmt.Println(slice)  // 输出: [10 20 30 40]

    // 向切片添加多个元素
    slice = append(slice, 50, 60)
    fmt.Println(slice)  // 输出: [10 20 30 40 50 60]

    // 向切片添加一个切片
    slice2 := []int{70, 80}
    slice = append(slice, slice2...)
    fmt.Println(slice)  // 输出: [10 20 30 40 50 60 70 80]
}

2.3.5 切片的复制 

       切片可以通过 copy 函数将一个切片的内容复制到另一个切片中。需要注意,copy 会复制源切片的元素到目标切片,但目标切片的长度不一定和源切片一样大,复制的元素数量会受到目标切片长度的限制。

package main

import "fmt"

func main() {
    slice1 := []int{10, 20, 30, 40, 50}
    slice2 := make([]int, 3)

    // 将 slice1 的前 3 个元素复制到 slice2
    copy(slice2, slice1)
    fmt.Println(slice2)  // 输出: [10 20 30]

    // 如果目标切片更大,剩余的元素保持零值
    slice3 := make([]int, 7)
    copy(slice3, slice1)
    fmt.Println(slice3)  // 输出: [10 20 30 40 50 0 0]
}

2.3.6 使用 for 进行遍历

package main

import "fmt"

func main() {
	s1 := make([]int, 0, 5)
	fmt.Println(s1)
	// 切片扩容,append()
	s1 = append(s1, 1, 2)
	fmt.Println(s1)
	// 问题:容量只有5个,那能放超过5个的吗? 可以,切片是会自动扩容的。
	s1 = append(s1, 2, 3, 3, 4, 4, 5, 6, 6, 7, 7)
	fmt.Println(s1)

	// 切片扩容之引入另一个切片。
	// new : 解构   slice.. ,解出这个切片中的所有元素。
	s2 := []int{100, 200, 300, 400}
	// slice = append(slice, anotherSlice...)
	// ... 可变参数 ...xxx
	// [...] 根据长度变化数组的大小定义
	// anotherSlice... , slice...解构,可以直接获取到slice中的所有元素
	// s2... = {100,200,300,400}
	s1 = append(s1, s2...)

	// 遍历切片
	for i := 0; i < len(s1); i++ {
		fmt.Println(s1[i])
	}

	for i := range s1 {
		fmt.Println(s1[i])
	}
}

2.4  切片的元素删除

       在 Go 语言中,切片本身不提供直接删除元素的方法,但是可以通过切片的切片操作和 append 函数来间接实现删除元素的功能。

2.4.1 删除切片中的元素

如果你需要删除切片中的某个元素(比如删除指定位置的元素),可以通过以下两种方法来完成:

  • 通过切片拼接:你可以使用 append 函数和切片的切片操作,将切片分为两部分,排除要删除的元素,然后重新拼接这两部分。
package main

import "fmt"

func main() {
    // 初始化一个切片
    slice := []int{10, 20, 30, 40, 50}

    // 打印原始切片
    fmt.Println("Original Slice:", slice)

    // 删除索引为 2 的元素(即元素 30)
    i := 2
    slice = append(slice[:i], slice[i+1:]...)

    // 打印修改后的切片
    fmt.Println("Modified Slice:", slice)
}

2.4.2 删除多个元素

       如果你需要删除多个元素,可以按照相同的方式,通过切片拼接来删除多个索引的元素。你只需进行多个切片操作。

package main

import "fmt"

func main() {
    // 初始化一个切片
    slice := []int{10, 20, 30, 40, 50}

    // 打印原始切片
    fmt.Println("Original Slice:", slice)

    // 删除索引为 1 和 3 的元素
    slice = append(slice[:1], slice[2:3]...)
    slice = append(slice[:2], slice[3:]...)

    // 打印修改后的切片
    fmt.Println("Modified Slice:", slice)
}

2.4.3 删除切片中的特定元素值

如果你要删除某个具体的元素值(而不是指定索引),可以遍历切片并删除所有匹配的元素。

package main

import "fmt"

func main() {
    // 初始化一个切片
    slice := []int{10, 20, 30, 40, 30, 50}

    // 打印原始切片
    fmt.Println("Original Slice:", slice)

    // 删除值为 30 的所有元素
    target := 30
    newSlice := []int{}
    for _, v := range slice {
        if v != target {
            newSlice = append(newSlice, v)
        }
    }

    // 打印修改后的切片
    fmt.Println("Modified Slice:", newSlice)
}
  • 删除指定索引的元素:通过切片操作和 append 来拼接删除元素前后的部分。
  • 删除多个元素:通过多次拼接来删除多个索引的元素。
  • 删除指定值的元素:遍历切片并将不匹配的元素添加到新的切片中。

2.5 切片的底层原理

2.5.1 为什么要理解切片的底层原理

       go 中的 slice 在函数参数传递的时候是值传递还是引用传递:值传递,效果上呈现出了引用的效果(不完全是)。

  1. 切片本身是引用类型: 切片包含三个部分:指向底层数组的指针、切片的长度和切片的容量。切片本身并不存储数据,而是通过指针指向底层的数组。因此,切片的元素存储在底层数组中,而切片本身是一个结构体,包含了这个数组的一个视图。

  2. 切片作为函数参数传递时

    • 当切片作为参数传递给函数时,传递的是 切片的引用,即切片指向底层数组的指针。这样,在函数内部对切片的修改会影响原始切片。
    • 然而,切片本身的结构(例如,切片的长度、容量)是可以在函数中修改的,但这不会改变传递到函数中的切片的原始引用。也就是说,如果函数修改了切片的长度或容量,这些变化不会影响原始切片的长度和容量。
package main

import "fmt"

func modifySlice(s []int) {
    // 修改切片的内容
    s[0] = 99
}

func main() {
    slice := []int{1, 2, 3, 4}
    fmt.Println("Before:", slice)
    
    modifySlice(slice)
    
    fmt.Println("After:", slice)  // 由于切片是引用类型,内容会被修改
}
Before: [1 2 3 4]
After: [99 2 3 4]

但是,如果我们修改切片的长度,则情况会有所不同:

package main

import "fmt"

func modifySliceLength(s []int) {
    s = append(s, 5)  // 改变切片的长度
    fmt.Println("Inside function:", s)
}

func main() {
    slice := []int{1, 2, 3}
    fmt.Println("Before:", slice)
    
    modifySliceLength(slice)
    
    fmt.Println("After:", slice)  // 原始切片的长度没有变化
}
Before: [1 2 3]
Inside function: [1 2 3 5]
After: [1 2 3]

       在这个例子中,modifySliceLength 函数通过 append 操作改变了切片的长度。虽然在函数内部,切片的长度和内容都发生了变化,但由于 append 操作可能导致切片重新分配底层数组,函数外部的原始切片 slice 的长度并没有改变。

  • 切片是引用类型,因此传递给函数时是 引用传递,修改切片的内容会影响原始切片。
  • 切片的 长度 和 容量 是在切片的结构体中存储的,函数内对它们的修改不会影响原始切片,除非通过重新赋值或 append 等操作导致切片重新分配新的底层数组。

2.5.2 切片的底层原理

       在 Go 语言中,切片(slice)是一个非常重要的类型,它提供了一种灵活的方式来操作数组的部分内容。切片本身是对数组的一个抽象,它具有一些非常重要的特性。为了理解切片的底层原理,我们需要了解切片的内部结构及其与底层数组的关系。

2.5.2.1 切片的结构

切片本质上是一个结构体,包含以下三个部分:

  1. 指向底层数组的指针(ptr: 切片内部有一个指针指向底层的数组。这使得切片可以动态地调整大小,而不需要重新分配数组。

  2. 切片的长度(len: 这是切片中元素的数量,表示当前切片中包含的元素个数。

  3. 切片的容量(cap: 切片的容量表示从切片的开始位置到底层数组的末尾可用的元素数量。容量决定了切片在不重新分配的情况下,最多可以容纳多少个元素。

2.5.2.2 切片与底层数组的关系

       切片是对底层数组的一部分的引用,它并不直接存储数据。切片通过指针指向底层数组的一段区间,切片的长度和容量是基于该区间确定的。

  • 切片的指针:切片指向底层数组的某个位置(不是从数组的开头开始,可能是中间的某个位置)。
  • 切片的长度:切片当前使用的元素数量。
  • 切片的容量:从切片的起始位置到底层数组末尾的元素数量。
2.5.2.3 切片的底层存储

       当切片是通过数组或其他切片创建时,它会直接指向底层数组,底层数组是存储实际数据的地方。切片的大小(即元素个数)是可以调整的,但它们共享同一个底层数组。

       例如,创建一个切片时,Go 会根据你提供的长度和容量分配底层数组。如果你通过切片扩展(例如使用 append)使得切片的容量不足,Go 会分配一个新的更大的数组,并将数据复制到新数组中。

2.5.2.4 切片的扩容

       当我们使用 append 函数向切片添加元素时,Go 会检查切片的容量。如果当前容量足够,append 会在原有的数组上直接增加元素;如果容量不够,Go 会分配一个新的更大的数组,复制原始数组的内容,并将新元素加入。

2.5.2.5 扩容策略

       Go 使用一个增长的策略来扩展切片的容量。通常情况下,当切片容量不足时,Go 会将容量加倍(或者增长 1.25 倍左右)。这种扩容策略使得 append 操作的平均时间复杂度为 O(1),即使在多次调用中,切片的扩容操作不会每次都需要重新分配和复制大量数据。

  • 切片是一个包含指向底层数组的指针、长度和容量的结构体。
  • 切片本身并不包含数据,而是对数组的一个视图,它提供了对底层数组的引用。
  • 切片的容量决定了它能够容纳的最大元素数量,超过容量时,Go 会扩容底层数组。
  • 切片是一个非常灵活且高效的数据结构,广泛用于Go的标准库和用户的应用程序中。
2.5.2.6 扩容的内存分析
  1. 每个切片引用了一个底层的数组
  2. 切片本身不存储任何数据,都是底层的数组来存储的,所以修改了切片也就是修改了这个数组中的数据
  3. 向切片中添加数据的时候,如果没有超过容量,直接添加,如果超过了这个容量,就会自动扩容,成倍的增加, copy
  4. 切片一旦扩容,就是重新指向一个新的底层数组。
package main

import "fmt"

// 切片扩容的内存分析
// 结论
// 1、每个切片引用了一个底层的数组
// 2、切片本身不存储任何数据,都是底层的数组来存储的,所以修改了切片也就是修改了这个数组中的数据
// 3、向切片中添加数据的时候,如果没有超过容量,直接添加,如果超过了这个容量,就会自动扩容,成倍的增加, copy
//   - 分析程序的原理
//   - 看源码
//
// 4、切片一旦扩容,就是重新指向一个新的底层数组。
func main() {
   // 1、cap 是每次成倍增加的
   // 2、只要容量扩容了,地址就会发生变化
   s1 := []int{1, 2, 3}
   fmt.Println(s1)
   fmt.Printf("len:%d,cap:%d\n", len(s1), cap(s1)) // len:3,cap:3
   fmt.Printf("%p\n", s1)                          // 0xc000016108

   s1 = append(s1, 4, 5)
   fmt.Printf("len:%d,cap:%d\n", len(s1), cap(s1)) // len:5,cap:6
   fmt.Printf("%p\n", s1)                          // 0xc000010390

   s1 = append(s1, 6, 7, 8)
   fmt.Printf("len:%d,cap:%d\n", len(s1), cap(s1)) // len:8,cap:12
   fmt.Printf("%p\n", s1)                          // 0xc00005e060

   s1 = append(s1, 9, 10)
   fmt.Printf("len:%d,cap:%d\n", len(s1), cap(s1)) // len:10,cap:12
   fmt.Printf("%p\n", s1)                          // 0xc00005e060

   s1 = append(s1, 11, 12, 13, 14)
   fmt.Printf("len:%d,cap:%d\n", len(s1), cap(s1)) // len:14,cap:24
   fmt.Printf("%p\n", s1)                          // 0xc00010c000
}

2.6 深拷贝、浅拷贝

深拷贝:拷贝是数据的本身

  • 值类型的数据,默认都是深拷贝,array、int、float、string、bool、struct....

浅拷贝:拷贝是数据的地址,会导致多个变量指向同一块内存。

  • 引用类型的数据: slice、map
  • 因为切片是引用类的数据,直接拷贝的是这个地址

切片如何实现深拷贝??

package main

import "fmt"

// 切片实现深拷贝
func main() {
   // 将原来切片中的数据拷贝到新切片中
   s1 := []int{1, 2, 3, 4}
   s2 := make([]int, 0) // len:0 cap:0
   for i := 0; i < len(s1); i++ {
      s2 = append(s2, s1[i])
   }
   fmt.Println(s1)
   fmt.Println(s2)
   s1[0] = 100
   fmt.Println(s1)
   fmt.Println(s2)
}

2.7 函数中参数传递问题

按照数据的存储特点来分:

  • 值类型的数据:操作的是数据本身、int 、string、bool、float64、array...
  • 引用类型的数据:操作的是数据的地址 slice、map、channal....
package main

import "fmt"

func main() {
   arr1 := [4]int{1, 2, 3, 4}
   fmt.Println("arr1:", arr1)
   update(arr1)
   fmt.Println("end arr1:", arr1)

   s1 := []int{1, 2, 3, 4}
   fmt.Println("s1:", s1)
   update2(s1)
   fmt.Println("end s1:", s1)
}

// 函数补充:在使用函数的时候,一定要特别注意参数问题,如果是值类型的,很多传递是无效的。
// 一些值传递的类型的参数,如果我们想通过函数来进行修改对应的值,这个时候就需要使用指针

// 指针变量 -> 指向原来变量的地址

// 数组是值类型的
func update(arr [4]int) {
   fmt.Println("--> arr:", arr)
   arr[0] = 100
   fmt.Println("--> end arr:", arr)
}

// 切片是引用类型的
func update2(s []int) {
   fmt.Println("--> s:", s)
   s[0] = 100
   fmt.Println("--> end s:", s)
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/962975.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

51单片机 01 LED

一、点亮一个LED 在STC-ISP中单片机型号选择 STC89C52RC/LE52RC&#xff1b;如果没有找到hex文件&#xff08;在objects文件夹下&#xff09;&#xff0c;在keil中options for target-output- 勾选 create hex file。 如果要修改编程 &#xff1a;重新编译-下载/编程-单片机重…

HTML一般标签和自闭合标签介绍

在HTML中&#xff0c;标签用于定义网页内容的结构和样式。标签通常分为两类&#xff1a;一般标签&#xff08;也称为成对标签或开放闭合标签&#xff09;和自闭合标签&#xff08;也称为空标签或自结束标签&#xff09;。 以下是这两类标签的详细说明&#xff1a; 一、一般标…

【EasyX 图形化编程保姆级喂嘴里教程】(C/C++) graphics.h 头文件库安装

文章目录 EasyXEasyX 是什么&#xff1f;超低的学习成本超多的应用场景超轻的发布过程 EasyX安装下载好后打开安装文件, 点击下一步。它自动检测已有的IDE&#xff0c;自行选择安装点击安装会提示安装成功接下来就可以在代码中使用 graphics.h 头文件库 EasyX EasyX 是什么&am…

吊打同类软件免费又可批量使用

聊一聊 对于经常用到席卡的人来说&#xff0c;每次打印都觉得麻烦&#xff0c;要是有个软件&#xff0c;直接输入名称就能打印就好了。 这不&#xff0c;只要你想&#xff0c;就肯定能实现&#xff1b;如果没实现&#xff0c;就说明你不够想。 这个软件我测试了下&#xff0…

2.攻防世界PHP2及知识点

进入题目页面如下 意思是你能访问这个网站吗&#xff1f; ctrlu、F12查看源码&#xff0c;什么都没有发现 用kali中的dirsearch扫描根目录 命令如下&#xff0c;根据题目提示以及需要查看源码&#xff0c;扫描以php、phps、html为后缀的文件 dirsearch -u http://61.147.17…

网络工程师 (11)软件生命周期与开发模型

一、软件生命周期 前言 软件生命周期&#xff0c;也称为软件开发周期或软件开发生命周期&#xff0c;是指从软件项目的启动到软件不再被使用为止的整个期间。这个过程可以细分为多个阶段&#xff0c;每个阶段都有其特定的目标、任务和产出物。 1. 问题定义与需求分析 问题定义…

深度学习练手小例子——cifar10数据集分类问题

CIFAR-10 是一个经典的计算机视觉数据集&#xff0c;广泛用于图像分类任务。它包含 10 个类别的 60,000 张彩色图像&#xff0c;每张图像的大小是 32x32 像素。数据集被分为 50,000 张训练图像和 10,000 张测试图像。每个类别包含 6,000 张图像&#xff0c;具体类别包括&#x…

力扣257. 二叉树的所有路径(遍历思想解决)

Problem: 257. 二叉树的所有路径 文章目录 题目描述思路复杂度Code 题目描述 思路 遍历思想(利用二叉树的先序遍历) 利用先序遍历的思想&#xff0c;我门用一个List变量path记录当前先序遍历的节点&#xff0c;当遍历到根节点时&#xff0c;将其添加到另一个List变量res中&…

力扣第149场双周赛

文章目录 题目总览题目详解找到字符串中合法的相邻数字重新安排会议得到最多空余时间I 第149场双周赛 题目总览 找到字符串中合法的相邻数字 重新安排会议得到最多空余时间I 重新安排会议得到最多空余时间II 变成好标题的最少代价 题目详解 找到字符串中合法的相邻数字 思…

算法题(54):插入区间

审题&#xff1a; 需要我们把newinterval的区间与interval的区间合并起来&#xff0c;并返回合并后的二维数组地址 思路&#xff1a; 方法一&#xff1a;排序合并区间 我们可以先把newinterval插入到interval中&#xff0c;进行排序然后复用合并区间的代码 方法二&#xff1a;模…

网工_HDLC协议

2025.01.25&#xff1a;网工老姜学习笔记 第9节 HDLC协议 9.1 HDLC高级数据链路控制9.2 HDLC帧格式&#xff08;*控制字段&#xff09;9.2.1 信息帧&#xff08;承载用户数据&#xff0c;0开头&#xff09;9.2.2 监督帧&#xff08;帮助信息可靠传输&#xff0c;10开头&#xf…

[免费]微信小程序智能商城系统(uniapp+Springboot后端+vue管理端)【论文+源码+SQL脚本】

大家好&#xff0c;我是java1234_小锋老师&#xff0c;看到一个不错的微信小程序智能商城系统(uniappSpringboot后端vue管理端)&#xff0c;分享下哈。 项目视频演示 【免费】微信小程序智能商城系统(uniappSpringboot后端vue管理端) Java毕业设计_哔哩哔哩_bilibili 项目介绍…

nth_element函数——C++快速选择函数

目录 1. 函数原型 2. 功能描述 3. 算法原理 4. 时间复杂度 5. 空间复杂度 6. 使用示例 8. 注意事项 9. 自定义比较函数 11. 总结 nth_element 是 C 标准库中提供的一个算法&#xff0c;位于 <algorithm> 头文件中&#xff0c;用于部分排序序列。它的主要功能是将…

CF 581A.Vasya the Hipster(Java实现)

题目分析 红色袜子数量a&#xff0c;蓝色袜子数量b&#xff0c;题目是个潮哥儿&#xff0c;首先选择两种袜子混搭&#xff0c;搭不出来就纯色 思路分析 混搭数量取决于最小数量&#xff0c;剩余的纯色数量取决于哪个还有剩余且数量要/2 代码 import java.util.*;public class…

C基础寒假练习(6)

一、终端输入行数&#xff0c;打印倒金字塔 #include <stdio.h> int main() {int rows;printf("请输入倒金字塔的行数: ");scanf("%d", &rows);for (int i rows; i > 0; i--) {// 打印空格for (int j 0; j < rows - i; j) {printf(&qu…

Python在线编辑器

from flask import Flask, render_template, request, jsonify import sys from io import StringIO import contextlib import subprocess import importlib import threading import time import ast import reapp Flask(__name__)RESTRICTED_PACKAGES {tkinter: 抱歉&…

ASP.NET Core 中间件

目录 一、常见的内置中间件 二、自定义中间件 三、中间件的执行顺序 四、其他自动逸中间件案例 1. 身份验证中间件 2、跨域中间件&#xff08;CORS&#xff09; ASP.NET Core 中&#xff0c;中间件&#xff08;Middleware&#xff09;是处理 HTTP 请求和响应的组件链。你…

LevelDB 源码阅读:写入键值的工程实现和优化细节

读、写键值是 KV 数据库中最重要的两个操作&#xff0c;LevelDB 中提供了一个 Put 接口&#xff0c;用于写入键值对。使用方法很简单&#xff1a; leveldb::Status status leveldb::DB::Open(options, "./db", &db); status db->Put(leveldb::WriteOptions…

2007-2019年各省科学技术支出数据

2007-2019年各省科学技术支出数据 1、时间&#xff1a;2007-2019年 2、来源&#xff1a;国家统计局、统计年鉴 3、指标&#xff1a;行政区划代码、地区名称、年份、科学技术支出 4、范围&#xff1a;31省 5、指标解释&#xff1a;科学技术支出是指为促进科学研究、技术开发…

2025年1月22日(网络编程 udp)

系统信息&#xff1a; ubuntu 16.04LTS Raspberry Pi Zero 2W 系统版本&#xff1a; 2024-10-22-raspios-bullseye-armhf Python 版本&#xff1a;Python 3.9.2 已安装 pip3 支持拍摄 1080p 30 (1092*1080), 720p 60 (1280*720), 60/90 (640*480) 已安装 vim 已安装 git 学习…