一架梯子,一头程序猿,仰望星空!

go语言数组和切片(slice)


数组类型,是一组同类型数据的集合,通过从0开始的下标,访问元素值。数组初始化后长度是固定的,无法修改数组长度。

1.数组定义

语法:

var 变量名 [数组大小]元素类型

例子:

// 定义拥有10个元素的数组a,元素类型是int
var a [10]int

// 定义拥有2个元素的字符串数组
var arr [2]string 

2.数组初始化

可以在定义数组的时候直接初始化数组元素

// 数组大小为6, 后面花括号直接初始化6个元素
primes := [6]int{2, 3, 5, 7, 11, 13}

3.读写数组元素

通过下标引用数组元素,下标从0开始计算,最大不能超过数组大小。

引用数组语法:

数组变量名[下标]

例子:

// 定义字符串数组a, 数组大小等于2
var a [2]string
// 给数组第一个元素赋值
a[0] = "Hello"
// 给数组第二个元素赋值
a[1] = "World"

// 打印数组第1和2个元素
fmt.Println(a[0], a[1])

输出:

Hello World

4.获取数组大小

可以通过len获取数组大小

例子:

primes := [6]int{2, 3, 5, 7, 11, 13}
// 打印数组大小
fmt.Println(len(primes))

5.slice(切片)

数组的长度是固定的,在实际应用中非常不方便,因此go语言提供了slice机制,我们一般翻译成切片,可以将切片当成动态数组用,动态数组指的是数组的长度可以动态调整。

切片底层依赖数组存储数据,切片本身是不存储数据,如果底层数组无法存储更多的数据,就会自动新申请一个更大存储空间的数组,将老的数组中的数据拷贝到新的数组,这样我们看起来slice就像动态数组一样可以存储任意数量的数据。

从底层存储角度看,切片(slice)就是数组的引用。

5.1. 定义切片

切片类型语法:

[]数据类型

切片类型跟数组类型的区别就是没有数组大小。

切片语法:

数组变量或者切片[low : high]

从数组变量中切一块 low <= 元素下标范围 < high 的数组引用, 所以叫做切片,就是数组中切了一块数据。

例子:

primes := [6]int{2, 3, 5, 7, 11, 13}

// 定义一个切片s, 通过切片语法,从primes数组中,切割了数组下标从1到3,3个元素
// 因为切片语法要求要小于4,所以不包括4
var s []int = primes[1:4]

// 打印切片s,引用的数组
fmt.Println(s)

输出:

[3 5 7]

提示:切片只是引用数组,所以效率非常高,例如在函数传参的时候,使用切片传递数组参数,不会复制数组。

例子2:

// 直接创建一个切片,底层会定义一个数组,由这个切片引用
r := []bool{true, false, true, true, false, true}

fmt.Println(r)

切片的定义和初始化跟数组非常类似,区别就是没有指定数组大小。

5.2. 切片语法应用

切片语法应用,可以根据需要,忽略切片的开始位置,或者结束位置,甚至全部忽略。

有下面4中情况:

// 切割 0 <= 下标范围 < 10 范围的元素
a[0:10]  

// 忽略开始位置,代表从0开始,这里的含义为:切割 0 <= 下标范围 < 10 范围的元素
a[:10]  

// 忽略结束位置,代表结束位置等于数组大小,这里含义为:切割 0 <= 下标范围 < len(a) 范围的元素
a[0:]

// 忽略开始和结束位置,代表切割所有数据,相当于引用整个数组
a[:]

例子:

package main

import "fmt"

func main() {
	s := []int{2, 3, 5, 7, 11, 13}

	s = s[1:4]
	fmt.Println(s)

        // 注意,这里是从上面切片中,定义一个新的切片
	s = s[:2]
	fmt.Println(s)

        // 注意,这里是从上面切片中,定义一个新的切片
	s = s[1:]
	fmt.Println(s)
}

输出:

[3 5 7]
[3 5]
[5]

提示:可以从数组中定义切片,也可以从切片中定义新的切片。

5.3. 切片的大小和容量

  • 切片大小指的是切片包含多少个元素
  • 切片容量指的是切片引用的数组大小

通过len函数可以获取切片大小

s := []int{2, 3, 5, 7, 11, 13}
fmt.Println(len(s))

通过cap函数,获取切片容量

fmt.Println(cap(s))

5.4. 切片默认值

切片如果没有初始化,默认值是nil

var s []int
if s == nil {
    fmt.Println("nil!")
}

5.5. 通过make创建切片

创建int类型切片,包含5个元素

a := make([]int, 5)  // len(a)=5

创建int类型切片,包含0个元素,切片容量为5

b := make([]int, 0, 5) // len(b)=0, cap(b)=5

提示:如果可以提前预估数组大小,建议根据预估的大小,创建切片,避免切片动态调整数组大小带来的数据拷贝的性能消耗。

5.6. 向切片添加元素

我们可以通过append函数,向切片尾部添加元素。

例子:

package main

import "fmt"

func main() {
    var s []int
    printSlice(s)

    // 将0增加到切片尾部
    s = append(s, 0)
    printSlice(s)

    // 将1增加到切片尾部
    s = append(s, 1)
    printSlice(s)

    // append函数支持一次性添加多个元素
    // 将2, 3, 4添加到切片尾部
    s = append(s, 2, 3, 4)
    printSlice(s)
}

// 打印切片数据
func printSlice(s []int) {
    fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
}

输出:

len=0 cap=0 []
len=1 cap=1 [0]
len=2 cap=2 [0 1]
len=5 cap=6 [0 1 2 3 4]

5.7. 如何读写切片元素

切片的读写操作跟数组一样,通过下标引用即可。

需要注意的是,因为切片底层引用的是数组,如果多个切片引用同一个数组,修改其中一个切片的元素,会影响关联的所有切片。

例子:

package main

import "fmt"

func main() {
	names := [4]string{
		"John",
		"Paul",
		"George",
		"Ringo",
	}
	fmt.Println(names)
        // 创建a,b两个切片
	a := names[0:2]
	b := names[1:3]
	fmt.Println(a, b)
        
        // 修改切片b的第一个元素
	b[0] = "XXX"
	fmt.Println(a, b)
	fmt.Println(names)
}

输出:

[John Paul George Ringo]
[John Paul] [Paul George]
[John XXX] [XXX George]
[John XXX George Ringo]

5.8. 遍历切片

通过range关键词结合for语句遍历切片

例子:

package main

import "fmt"
// 定义切片
var pow = []int{1, 2, 4, 8, 16, 32, 64, 128}

func main() {
    // range 切片变量名
    for i, v := range pow {
        // 这里循环的时候定义了i,v两个变量,i 代表切片下标,v 代表对应的元素值
	fmt.Printf("2**%d = %d\n", i, v)
    }
}

当然我们可以忽略下标或者元素值,例子:

// 忽略元素值
for i, _ := range pow
// 忽略下标
for _, value := range pow
// 如果只写一个变量,则忽略元素值
for i := range pow