一 数组(array)
Go

1.1 数组的定义

var 数组名 [数组长度] 数据类型
[5]int[10]int
package main

import "fmt"

func main()  {
  var a [3]int
  var b [4]int
  fmt.Printf("a: %T b: %T\n", a, b) // a: [3]int b: [4]int
}

1.2 数组的初始化

数组的初始化也有很多方式

方式一

初始化数组时可以使用初始化列表来设置数组元素的值

var 数组名 = [数组长度]类型{初始化列表}

示例

package main

import "fmt"

func main()  {
	var a [3]int  // 默认使用类型的零值初始化
 	var b = [4]int{1,2,3,4}  // 使用1,2,3,4初始化数组
	var cityArray = [3]string{"北京", "上海", "深圳"}
	fmt.Println(a, b, cityArray)  // [0 0 0] [1 2 3 4] [北京 上海 深圳]
	a = [3]int{5, 6, 7}
	fmt.Println(a)  // [5 6 7]
}

方式二

根据初始值的个数自行推断数组的长度

var 数组名 = [...]类型{初始化列表}

示例

package main

import "fmt"

func main()  {
	var a [3]int  // 默认使用类型的零值初始化
 	var b = [...]int{1,2,3,4}  // 使用1,2,3,4初始化数组
	var cityArray = [...]string{"北京", "上海", "深圳"}
	fmt.Println(a, b, cityArray)  // [0 0 0] [1 2 3 4] [北京 上海 深圳]
	a = [...]int{5, 6, 7}
	fmt.Println(a)  // [5 6 7]
}

方式三

指定索引值的方式来初始化数组

var 数组名 = [数组长度]类型{index: value}

示例

package main

import "fmt"

func main()  {
	var a [3]int  // 默认使用类型的零值初始化
	a = [3]int{0: 5, 2:7} // 指定索引初始化
	fmt.Println(a)  // [5 0 7]
}

1.3 数组的遍历

索引遍历

package main

import "fmt"

func main()  {
	cityArray := [...]string{"北京", "上海", "深圳"}
	for i := 0; i < len(cityArray); i++ {
		city := cityArray[i]
		fmt.Println(city)
	}
}

range遍历

package main

import (
	"fmt"
)

func main()  {
	cityArray := [...]string{"北京", "上海", "深圳"}
	for _, v := range cityArray {
		fmt.Println(v)
	}
	
}

1.4 多维数组

Go

1.4.1 二维数组声明

var 数组名 [外层长度][内层长度]数据类型

示例

package main

import (
	"fmt"
)

func main()  {
	var a [3][2] int  // 定义一个三行两列的二维数组. 一维数组中保持了三个只有2个元素一维数组
	fmt.Println(a)  // [[0 0] [0 0] [0 0]]
}

1.4.2 二维数组的初始化

方式一

使用初始化列表来设置数组元素的值

var 数组名 = [外层长度][内层长度]数据类型{数据列表}

示例

package main

import (
	"fmt"
)

func main()  {
	var a [3][2] int  // 定义一个三行两列的二维数组. 一维数组中保持了三个只有2个元素一维数组
	fmt.Println(a)  // [[0 0] [0 0] [0 0]]
	a = [3][2]int{{1,2},{3,4},{5,6}}
	fmt.Println(a)  // [[1 2] [3 4] [5 6]]
}

方式二

根据初始值的个数自行推断数组的长度

var 数组名 = [...][内层长度]数据类型{数据列表}

  • 仅支持外层自动推算元素个数

示例

package main

import (
	"fmt"
)

func main()  {
	var a [3][2] int  // 定义一个三行两列的二维数组. 一维数组中保持了三个只有2个元素一维数组
	fmt.Println(a)  // [[0 0] [0 0] [0 0]]
	a = [...][2]int{{2,3},{4,5},{6,7}}  // 仅支持外层自动推算元素个数,不支持内层自动推算元素个数
	fmt.Println(a)  // [[2 3] [4 5] [6 7]]
}

方式三

指定索引值的方式来初始化数组

var 数组名 = [外层长度][内层长度]数据类型{index: {内存数据列表}}

示例

package main

import (
	"fmt"
)

func main()  {
	var a [3][2] int  // 定义一个三行两列的二维数组. 一维数组中保持了三个只有2个元素一维数组
	fmt.Println(a)  // [[0 0] [0 0] [0 0]]
    a = [3][2]int{0: {1,2}, 2: {5,6}}
    fmt.Println(a)  // [[1 2] [0 0] [5 6]]
}

1.4.3 二维数组遍历

package main

import (
	"fmt"
)

func main()  {
	var a [3][2] int  // 定义一个三行两列的二维数组. 一维数组中保持了三个只有2个元素一维数组
	fmt.Println(a)  // [[0 0] [0 0] [0 0]]
	a = [3][2]int{{1,2},{3,4},{5,6}}
	for i:=0; i < 3;i++ {
		for j:=0; j < 2; j++ {
			fmt.Println(a[i][j])
		}
	}
	
	for _, v1 := range a {
		for _, v := range v1{
			fmt.Println(v)
		}
	}
}

1.5 数组是值类型

数组是值类型,赋值和传参会复制整个数组。因此改变副本的值
不会改变本身的值

package main

import (
	"fmt"
)

func main()  {
	b := [...]int{1, 2, 3}
	b1 := b
	b1[0]=3

	fmt.Println("b:", b)  // b: [1 2 3]
	fmt.Println("b1:", b1)  // b1: [3 2 3]
}

注意

==!=[n]*T*[n]T

数组练习

package main

import "fmt"

func main()  {
	a1 := [...]int {1, 3, 5, 7, 8}
	sum := 0
	for _, v := range a1 {
		sum += v
	}
	fmt.Printf("sum = %d\n", sum)

    for i, v1 := range a1 {
        for j:=i+1; j < len(a1); j++{
            if v1 + a1[j] == 8{
              fmt.Printf("(%d, %d)", i, j) // (0, 3)(1, 2)
            }
        }
    }
}
二 切片(slice)

因为数组的长度是固定的并且数组长度属于类型的一部分,所以数组有很多的局限性

package main
func arraySum(x [3]int) int{
    sum := 0
    for _, v := range x{
        sum = sum + v
    }
    return sum
}
x[3]int

另外

a := [3]int{1, 2, 3}
a

为此,go语言引入了切片来解决这些问题。

切片(Slice)是一个拥有相同类型元素的可变长度的序列
它是基于数组类型做的一层封装。它非常灵活,支持自动扩容

地址长度容量

2.1 声明切片

声明切片类型的基本语法如下:

var 切片名 [] 数据类型

2.2 切片初始化

var 切片名 = []数据类型{初始化列表}

示例

package main

import "fmt"

func main() {
	var slice = []int{1,2,3,4}
	fmt.Println(slice)  // [1 2 3 4]
	var slice2 = []string{"北京", "上海", "沙河"}
	fmt.Println(slice2)  // [北京 上海 沙河]
}

2.3 引用类型

nil
golangnilpointerchannelfuncinterfacemapslicec
package main

import "fmt"

func main() {
	var s1 [] int   // 声明一个切片
	fmt.Println(s1 == nil)  // true
	// 在golang中nil代表了pointer, channel, Function, interface, map 或者 slice 的零值,类似与c语言中的空指针.
	var s2 = []int{1,2,3,4}
	fmt.Println(s2 == nil)  //false
}

2.4 切片长度和容量

切片拥有自己的长度和容量,

  • 长度: 切片保存的数据的个数
  • 容量: 底层数组的长度 - 切片第一个元素在底层数组中的位置
len()cap()
package main

import "fmt"

func main() {
	var s1 [] int
	fmt.Println(len(s1), cap(s1))  // 0 0
	var s2 = []int{1,2,3,4}
	fmt.Println(len(s2), cap(s2))  // 4 4
}

2.5 切片表达式

字符串数组指向数组或切片的指针lowhighlowhigh

简单切片表达式

切片的底层就是一个数组,所以我们可以基于数组通过切片表达式得到切片

lowhigha1<=索引值<4s长度=high-low

从数组得到切片

数组名[low:high]

示例

package main

import "fmt"

func main() {
	// 由数组得到切片
	var array = [...]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
	s1 := array[0:8]
	fmt.Printf("s1:%v,type(s1): %T, len(s1): %d, cap(s1): %d", s1, s1, len(s1), cap(s1))// s1:[1 2 3 4 5 6 7 8],type(s1): []int, len(s1): 8, cap(s1): 10
	
}
low0high
package main

import "fmt"

func main() {
	// 由数组得到切片
	var array = [...]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
	s1 := array[0:8]
	fmt.Printf("s1:%v,type(s1): %T, len(s1): %d, cap(s1): %d\n", s1, s1, len(s1), cap(s1))// s1:[1 2 3 4 5 6 7 8],type(s1): []int, len(s1): 8, cap(s1): 10

	s2 := array[:5]
	s3 := array[5:]
	s4 := array[:]
	fmt.Println(s2)  // [1 2 3 4 5]
	fmt.Println(s3)  // [1 2 3 4 5]
	fmt.Println(s4)  // [1 2 3 4 5 6 7 8 9 10]
}
0 <= low <= high <= len(a)

从切片得到切片

切片名[low:high]
highcap(a)len(a)intlowhighlow <= highpanic

示例

package main

import "fmt"

func main() {
	var array = [...]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
	slice := array[:5]

	s1 := slice[3: 5]  // s1的容量是底层数组array的长度减去s1的
	fmt.Printf("s1: %v, cap(s1): %d\n", s1, cap(s1))  // s1: [4 5], cap(s1): 7

	slice = array[5:]
	//s2 := slice[:6]  // out of range
	s2 := slice[:5]
	fmt.Printf("s2: %v, cap(s2): %d", s2, cap(s2))  // s2: [6 7 8 9 10], cap(s2): 5
}

完整切片表达式

数组指向数组的指针切片
变量名[low:high:max]
变量名[low: high]max-low0

示例

package main

import "fmt"

func main() {
	var array = [...]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
	s1 := array[:5:6]
	fmt.Printf("s1: %v, cap(s1): %d\n", s1, cap(s1)) // s1: [1 2 3 4 5], cap(s1): 6
	s2 := array[4:6:10]
	fmt.Printf("s2: %v, cap(s2): %d", s2, cap(s2))  // s2: [5 6], cap(s2): 6
}
0 <= low <= high <= max <= cap(a)

2.6 make函数构造切片

如果需要动态的创建一个切片,我们就需要使用内置的make()函数,格式如下

make([]T, size, cap)
Tsizecapsize

示例

package main

import "fmt"

func main() {
	slice := make([]int, 10, 20)
	fmt.Printf("slice: %v, len(slice): %d, cap(slice):%d\n", slice, len(slice), cap(slice))  // slice: [0 0 0 0 0 0 0 0 0 0], len(slice): 10, cap(slice):20
}
slice2010len(slice)cap(slice)

2.7 切片本质

底层数组的指针切片的长度(len)切片的容量(cap)
a := [8]int{0, 1, 2, 3, 4, 5, 6, 7}s1 := a[:5]s2 := a[3:6]

切片不能直接比较

==nilnilnil00nil
nilnil00nil
package main

import "fmt"

func main() {
	var s1 []int         //len(s1)=0;cap(s1)=0;s1==nil
	s2 := []int{}        //len(s2)=0;cap(s2)=0;s2!=nil
	s3 := make([]int, 0) //len(s3)=0;cap(s3)=0;s3!=nil

	fmt.Printf("len(s1): %d, cap(s1): %d, s1 is nil: %t\n", len(s1), cap(s1), s1 == nil)  // len(s1): 0, cap(s1): 0, s1 is nil: true
	fmt.Printf("len(s2): %d, cap(s2): %d, s2 is nil: %t\n", len(s2), cap(s2), s2 == nil)  // len(s2): 0, cap(s2): 0, s2 is nil: false
	fmt.Printf("len(s3): %d, cap(s3): %d, s3 is nil: %t\n", len(s3), cap(s3), s3 == nil)  // len(s3): 0, cap(s3): 0, s3 is nil: false
}
len(s) == 0s == nil

2.8 再谈引用类型

切片是一个引用类型数据,赋值和参数传递不会完整复制底层数据,会共用同一个底层数组

package main

import "fmt"

func main() {
	s1 := make([]int, 3) //[0 0 0]
	s2 := s1             // 将s1直接赋值给s2,s1和s2共用一个底层数组
	s2[0] = 100
	fmt.Println(s1)  // [100 0 0]
	fmt.Println(s2)  // [100 0 0]
}

切片遍历

切片的遍历方式和数组是一致的,支持索引遍历和for range遍历

package main

import "fmt"

func main() {
	s := []int{1, 3, 5}

	for i := 0; i < len(s); i++ {
		fmt.Println(i, s[i])
	}

	for index, value := range s {
		fmt.Println(index, value)
	}
}

2.9 append函数

append
Goappend()...
package main

import "fmt"

func main() {
	var s []int
	s = append(s, 1)        // 添加一个元素
	fmt.Printf("s: %v, len(s): %d, cap(s): %d\n", s, len(s), cap(s))
	s = append(s, 2, 3, 4)  // 添加三个元素
	fmt.Printf("s: %v, len(s): %d, cap(s): %d\n", s, len(s), cap(s))
	s2 := []int{5, 6, 7}
	s = append(s, s2...)  // 添加另一个切片的元素
	fmt.Printf("s: %v, len(s): %d, cap(s): %d\n", s, len(s), cap(s))
}
varappend()append()append

切片自动扩容扩容

package main

import "fmt"

func main() {
	var numSlice []int
	for i := 0; i < 10; i++ {
		numSlice = append(numSlice, i)
		fmt.Printf("%v  len:%d  cap:%d  ptr:%p\n", numSlice, len(numSlice), cap(numSlice), numSlice)
	}
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0jZMbBRv-1619921849366)(.img/切片扩容.png)]

append()numSlice1,2,4,8,162

扩容策略
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-02U7LCIq-1619921849370)(.img/切片扩容源码.png)]

cap2old.capnewcapcap1024newcapold.capnewcap=doublecap1024newcapold.cap1/4newcap=old.capfor {newcap += newcap/4}newcapcapnewcap >= capcapcapcap
intstring

2.10 copy复制

copy
package main

import "fmt"

func main() {
	a := []int{1, 2, 3, 4, 5}
	b := a
	fmt.Println(a) //[1 2 3 4 5]
	fmt.Println(b) //[1 2 3 4 5]
	b[0] = 1000
	fmt.Println(a) //[1000 2 3 4 5]
	fmt.Println(b) //[1000 2 3 4 5]
}
ababab
Gocopy()copy()
copy(destSlice, srcSlice []T)
srcSlicedestSlice
package main

import "fmt"

func main() {
	a := []int{1, 2, 3, 4, 5}
	c := make([]int, 5, 5)
	copy(c, a)     //使用copy()函数将切片a中的元素复制到切片c
	fmt.Println(a) //[1 2 3 4 5]
	fmt.Println(c) //[1 2 3 4 5]
	c[0] = 1000
	fmt.Println(a) //[1 2 3 4 5]
	fmt.Println(c) //[1000 2 3 4 5]

}

2.11 删除切片中的元素

Go语言中并没有删除切片元素的专用方法,我们可以使用切片本身的特性来删除元素

a = append(a[:index], a[index+1:]...)
aindex
package main

import "fmt"

func main() {
	// 从切片中删除元素
	a := []int{30, 31, 32, 33, 34, 35, 36, 37}
	fmt.Println(a)  // [30 31 32 33 34 35 36 37]
	// 要删除索引为2的元素
	a = append(a[:2], a[3:]...)
	fmt.Println(a)  // [30 31 33 34 35 36 37]
}

再谈切片本质

package main

import "fmt"

func main() {
    a := [...]int{30, 31, 32, 33, 34, 35, 36, 37}
    s := a[:]
    fmt.Printf("%p ", s)  // 0xc0000c4040
    fmt.Println(s, len(s), cap(s))  //  [30 31 32 33 34 35 36 37] 8 8
  
    s = append(s[:2], s[3:]...)
    fmt.Printf("%p ", s)  // 0xc0000c4040
    fmt.Println(s, len(s), cap(s))  // [30 31 33 34 35 36 37] 7 8
    fmt.Println(a)  // [30 31 33 34 35 36 37 37]
}
指针、长度、容量

练习

package main

import "fmt"

func main() {
	var a = make([]int, 5, 10)
	for i := 0; i < 10; i++ {
		a = append(a, i)
	}
	fmt.Println(a) // [0 0 0 0 0 0 1 2 3 4 5 6 7 8 9]
}

排序

a := []int{3,1,7,5,0}
sort.Ints(a)
fmt.Println(a) // [0 1 3 5 7]

三 map
maphash

map的声明与使用

Gomap
var map变量 map[KeyType]ValueType

KeyTypeValueType
mapnilmake()map
map变量 = make(map[KeyType]ValueType, [cap])

capmapmap

获取map中保存的值

value, bool_value := map变量[key]

keymapvaluekeymapbool_valuekeytruefalse

map示例

package main

import "fmt"

func main() {
    var m map[string]int
    m = make(map[string]int, 2)
    m["age"] = 18
    m["money"] = 200
    fmt.Printf("%#v\n", m) // map[string]int{"age":18, "money":200}
  
    value, exists := m["hello"]
    if exists {
      fmt.Println(value, exists) // value默认为对应类型的零值
    } else {
      fmt.Println("该key不在map中")
    }
}

map的遍历

mapfor range
package main

import "fmt"

func main() {
  scoreMap := make(map[string]int)
  scoreMap["张三"] = 90
  scoreMap["小明"] = 100
  scoreMap["娜扎"] = 60
  // 遍历key-value
  for k, v := range scoreMap {
    fmt.Println(k, v)
  }
  // 遍历key
  for k := range scoreMap {
    fmt.Println(k)
  }
}

删除键值

delete()map
delete(map, key)
mapmapkeykey
package main

import "fmt"

func main() {
	scoreMap := make(map[string]int)
	scoreMap["张三"] = 90
	scoreMap["小明"] = 100
	scoreMap["娜扎"] = 60
	delete(scoreMap, "小明")
	for k, v := range scoreMap {
		fmt.Println(k, v)
	}

    delete(scoreMap, "hello")  // hello不存在,delete不操作
}
  • key不存在,delete函数不做任何操作

map的有序遍历

go

元素为map类型的切片

切片中的元素为map类型,定义格式

var slice [] map[keyType]valueType

  • 该方式声明了一个元素为map的切片

通过初始化列表初始化

slice = []map[keyType]valueType{{key: value}, {key: value}}

通过make函数初始化

slice = make([] map[keyType]valueType, len, cap)  // 初始化切片
slice[i] = make(map[keyType]valueType, cap) // 初始化map

示例

package main

import "fmt"

func main() {
	var slice1 []map[string]int
	slice1 = []map[string]int{{"dyp": 19}, {"dyy": 12}}  // 初始化列表初始化
	fmt.Println(slice1)
	var slice2 []map[string]int
	slice2 = make([]map[string]int, 3)  // 初始化切片
	slice2[0] = make(map[string]int, 3)  // 初始化map
	slice2[0]["dyp"] = 19
	slice2[0]["dyy"] = 12
	fmt.Println(slice2)
}

元素为切片的map

mapkeykeyvalue

map中值为切片类型声明

var sliceMap1 map[keyType][]Type
keyTypemap[]Typevalue

通过初始化列表初始化

sliceMap1 = map[keyType][]Type{"key": {value1, value2, ..., value_n}}

通过make初始化

sliceMap1 = make(map[keyType][]Type, cap) // 先初始化map
sliceMap1["key"] = make([]Type, len, cap)  // 在初始化切片

示例

package main

import "fmt"

func main() {
	var sliceMap1 map[string][]int
	sliceMap1 = map[string][]int{"北京": {1, 2, 3}, "上海": {4, 5, 6}}  // 初始化列表初始化
	fmt.Println(sliceMap1)
	
	var sliceMap2 map[string][]int
	sliceMap2 = make(map[string][]int, 3) // 先初始化map
	sliceMap2["北京"] = make([]int, 0, 3)  // 在初始化切片
	sliceMap2["北京"] = append(sliceMap2["北京"], 1, 2, 3)  // 向切片添加值
	fmt.Println(sliceMap2)
}