Go 数组、切片与map

Go数组、切片与map

数组

数组具有以下特点:

  • 数组长度是固定的,不能动态变化。
  • 数组中每个元素的类型和大小都相同。
  • 数组中的元素在内存中是连续存储的,可以随机访问。
  • Go中的数组是值类型,传递时复制整个数组。这意味着当它们被分配给一个新变量时,将把原始数组的副本分配给新变量。如果对新变量进行了更改,则不会在原始数组中反映。
  • 如果要修改函数中的数组,必须使用指针。
  • 比较两个数组(array)是否相等(使用 == 运算符)时,比较的是它们对应位置的值是否相等,而不是它们的地址或引用。

示例代码:

var a [4] float32 // 等价于:var arr2 = [4]float32{}
fmt.Println(a) // [0 0 0 0]
var b = [5] string{"ruby", "王二", "rose"}
fmt.Println(b) // [ruby 王二 rose  ]
var c = [5] int{'A', 'B', 'C', 'D', 'E'} // byte
fmt.Println(c) // [65 66 67 68 69]
d := [...] int{1,2,3,4,5}// 根据元素的个数,设置数组的大小
fmt.Println(d)//[1 2 3 4 5]
e := [5] int{4: 100} // [0 0 0 0 100]
fmt.Println(e)
f := [...] int{0: 1, 4: 1, 9: 1} // [1 0 0 0 1 0 0 0 0 1]
fmt.Println(f)

切片

切片的长度是不固定的,可以追加元素,在追加时可能使切片的容量增大。切片是一种方便、灵活且强大的包装器。

  • 切片本身没有任何数据,它们只是对现有数组的引用。
  • 切片可以理解为对数组的一个引用,它由指向数组的指针、切片的长度和切片的容量组成。切片的长度表示切片中实际存储的元素个数,而容量表示切片底层数组的大小。切片相比于数组的优势在于它的大小是动态可变的,可以根据需要进行扩容或缩减。

创建切片

var slice []int             // 声明一个切片
slice := make([]int, 0)     // 创建一个切片,并指定长度为0

var slice1 []type = make([]type, len)
// 也可以简写为
slice1 := make([]type, len)

s := arr[startIndex:endIndex] 
// 将arr中从下标startIndex到endIndex-1 下的元素创建为一个新的切片(前闭后开),长度为endIndex-startIndex
s := arr[startIndex:] 

s := arr[:endIndex] 

修改切片

对切片进行修改非常方便,可以通过索引进行赋值或修改现有元素的值。此外,还可以使用切片作为参数传递给某个函数,并在函数内部对切片进行修改。这种特性使得切片在处理大量数据时非常高效。

  • slice没有自己的任何数据。它只是底层数组的一个表示。对slice所做的任何修改都将反映在底层数组中。
  • 当多个片共享相同的底层数组时,每个元素所做的更改将在数组中反映出来。

示例代码:

package main

import (  
    "fmt"
)

func main() {  
    numa := [3]int{78, 79 ,80}
    nums1 := numa[:] //creates a slice which contains all elements of the array
    nums2 := numa[:]
    fmt.Println("array before change 1",numa)  //[78 79 80]
    nums1[0] = 100
    fmt.Println("array after modification to slice nums1", numa)  //  [100 79 80]  
    nums2[1] = 101
    fmt.Println("array after modification to slice nums2", numa)  //  [100 101 80]  
}

空切片

一个切片在未初始化之前默认为 nil,长度为 0。

示例代码:

package main

import "fmt"

func main() {
   var numbers []int

   printSlice(numbers)

   if(numbers == nil){
      fmt.Printf("切片是空的")
   }
}

func printSlice(x []int){
   fmt.Printf("len=%d cap=%d slice=%v\n",len(x),cap(x),x)
}

输出:

len=0 cap=0 slice=[]
切片是空的

len()函数和cap()函数

Golang提供了两个内置函数len()和cap()来获取切片的长度和容量。len()函数返回切片中实际存储的元素个数,而cap()函数返回切片底层数组的大小。

  • len()和cap()函数只能用于切片和数组,并且只能对第一维度的长度和容量进行计算。如果我们有一个切片的切片,那么len()函数将返回第一维度的长度,而不会返回第二维度的长度。

示例代码:

package main

import "fmt"

func main() {
   var numbers = make([]int,3,5)

   printSlice(numbers)
}

func printSlice(x []int){
   fmt.Printf("len=%d cap=%d slice=%v\n",len(x),cap(x),x)
   //len=3 cap=5 slice=[0 0 0]
}

append()函数

用于向切片中添加元素,可以一次添加一个或多个元素。

  • append()函数接受两个参数:第一个参数是要添加元素的切片,第二个参数是要添加的元素。
  • 添加完元素后,append()函数会返回一个新的切片,我们通常需要将其赋值给原始的切片。
  • 除了添加元素,append()函数还有一个重要的功能——动态扩容。当切片的容量不足以容纳新添加的元素时,append()函数会自动进行扩容操作。它会创建一个新的底层数组,并将原始数据复制到新数组中。然后,它会将新添加的元素放入新数组中,并返回一个新的切片。这样,我们就可以使用扩容后的切片了。
  • append函数会改变slice所引用的数组的内容,从而影响到引用同一数组的其它slice。 但当slice中没有剩余空间(即(cap-len) == 0)时,此时将动态分配新的数组空间。返回的slice数组指针将指向这个空间,而原数组的内容将保持不变;其它引用此数组的slice则不受影响。

copy() 函数

copy()函数用于将一个切片的内容复制到另一个切片中。

  • copy()函数接受两个参数:第一个参数是目标切片,第二个参数是源切片。copy()函数会将源切片中的元素复制到目标切片中,并返回复制的元素个数。
  • 与append()函数不同,copy()函数不会进行扩容操作。它仅将源切片中的元素按照顺序复制到目标切片中。因此,需要注意两个切片的大小要一致,否则可能会造成数据丢失或越界访问。

示例代码:

package main

import "fmt"

func main() {
    /*
        slice := arr[start:end]
             切片中的数据:[start,end)
             arr[:end],从头到end
             arr[start:]从start到末尾

         从已有的数组上,直接创建切片,该切片的底层数组就是当前的数组。
             长度是从start到end切割的数据量。
            但是容量从start到数组的末尾。
    */
    a := [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
    fmt.Println("----------1.已有数组直接创建切片--------------------")
    s1 := a[:5]  //1-5
    s2 := a[3:8] //4-8
    s3 := a[5:]  // 6-10
    s4 := a[:]   // 1-10
    fmt.Println("a:", a)
    fmt.Println("s1:", s1)
    fmt.Println("s2:", s2)
    fmt.Println("s3:", s3)
    fmt.Println("s4:", s4)

    fmt.Printf("%p\n", &a)
    fmt.Printf("%p\n", s1)

    fmt.Println("----------2.长度和容量--------------------")
    fmt.Printf("s1    len:%d,cap:%d\n", len(s1), cap(s1)) //s1,len:5,cap:10
    fmt.Printf("s2    len:%d,cap:%d\n", len(s2), cap(s2)) //s2,len:5,cap:7
    fmt.Printf("s3    len:%d,cap:%d\n", len(s3), cap(s3)) //s3,len:5,cap:5
    fmt.Printf("s4    len:%d,cap:%d\n", len(s4), cap(s4)) //s4,len:10,cap:10

    fmt.Println("----------3.更改数组的内容--------------------")
    a[4] = 100
    fmt.Println(a)
    fmt.Println(s1)
    fmt.Println(s2)
    fmt.Println(s3)

    fmt.Println("----------4.更改切片的内容--------------------")
    s2[2] = 200
    fmt.Println(a)
    fmt.Println(s1)
    fmt.Println(s2)
    fmt.Println(s3)

    fmt.Println("----------4.更改切片的内容--------------------")
    s1 = append(s1, 1, 1, 1, 1)
    fmt.Println(a)
    fmt.Println(s1)
    fmt.Println(s2)
    fmt.Println(s3)

    fmt.Println("----------5.添加元素切片扩容--------------------")
        fmt.Println(len(s1), cap(s1))
    s1 = append(s1, 2, 2, 2, 2, 2)
    fmt.Println(a)
    fmt.Println(s1)
    fmt.Println(s2)
    fmt.Println(s3)
    fmt.Println(len(s1), cap(s1))
    fmt.Printf("%p\n", s1)
    fmt.Printf("%p\n", &a)
}

输出:

----------1.已有数组直接创建切片--------------------
a: [1 2 3 4 5 6 7 8 9 10]
s1: [1 2 3 4 5]
s2: [4 5 6 7 8]
s3: [6 7 8 9 10]
s4: [1 2 3 4 5 6 7 8 9 10]
0xc000018190
0xc000018190
----------2.长度和容量--------------------
s1      len:5,cap:10
s2      len:5,cap:7
s3      len:5,cap:5
s4      len:10,cap:10
----------3.更改数组的内容--------------------
[1 2 3 4 100 6 7 8 9 10]
[1 2 3 4 100]
[4 100 6 7 8]
[6 7 8 9 10]
----------4.更改切片的内容--------------------
[1 2 3 4 100 200 7 8 9 10]
[1 2 3 4 100]
[4 100 200 7 8]
[200 7 8 9 10]
----------4.更改切片的内容--------------------
[1 2 3 4 100 1 1 1 1 10]
[1 2 3 4 100 1 1 1 1]
[4 100 1 1 1]
[1 1 1 1 10]
----------5.添加元素切片扩容--------------------
9 10
[1 2 3 4 100 1 1 1 1 10]
[1 2 3 4 100 1 1 1 1 2 2 2 2 2]
[4 100 1 1 1]
[1 1 1 1 10]
14 20
0xc0000240a0
0xc000018190

map

map是无序的,每次打印出来的map都会不一样,它不能通过index获取,而必须通过key获取。

  • map的长度是不固定的,也就是和slice一样,也是一种引用类型,当将映射分配给一个新变量时,它们都指向相同的内部数据结构。因此,一个的变化会反映另一个。
  • 内置的len函数同样适用于map,返回map拥有的key的数量。
  • map的key可以是所有可比较的类型,如布尔型、整数型、浮点型、复杂型、字符串型。
  • 当key如果不存在的时候,我们会得到该value值类型的默认值,比如string类型得到空字符串,int类型得到0。但是程序不会报错。所以我们可以使用ok-idiom获取值,可知道key/value是否存在。
  • map不能使用==操作符进行比较。

例一

// 例一
package main

import "fmt"
 
func main() {
    //1.创建map
    var map1 map[int]string         //没有初始化,nil
    var map2 = make(map[int]string) //map2 是通过 make 初始化的,即使它是空的,也不是 nil,因此 map2 == nil 返回 false
    var map3 = map[string]int{"Go": 98, "Python": 87, "Java": 79, "Html": 93}
    fmt.Println(map1)
    fmt.Println(map2)
    fmt.Println(map3)

    fmt.Println(map1 == nil)
    fmt.Println(map2 == nil)
    fmt.Println(map3 == nil)
    
    map2[1] = "11"
    fmt.Println(map1[40]) //"" 是空的,但是不报错

    v1, ok := map2[40]
    if ok {
        fmt.Println("对应的数值是:", v1)
    } else {
        fmt.Println("操作的key不存在,获取到的是零值:", v1)
    }
}

输出:

map[]
map[]
map[Go:98 Html:93 Java:79 Python:87]
true
false
false

操作的key不存在,获取到的是零值:

例二

// 例二
package main

import "fmt"

func main() {
    map4 := make(map[string]string)
    map4["小王"] = "美"
    map4["小李"] = "富"
    map4["小文"] = "白"

    fmt.Println(map4)

    map5 := map4
    fmt.Println(map5)

    map5["小昭"] = "富"
    fmt.Println(map4)
    fmt.Println(map5)
}

输出:

map[小文:白 小李:富 小王:美]
map[小文:白 小李:富 小王:美]
map[小文:白 小昭:富 小李:富 小王:美]
map[小文:白 小昭:富 小李:富 小王:美]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值