欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Go 数组、切片和 map

程序员文章站 2024-01-30 23:13:46
...

数组

数组可以存放多个同一类型数据。数组也是一种数据类型,在 Go 中,数组是值类型

package main

import "fmt"

func main()  {

	var hen [6]float64
	hen[0] = 1.00
	hen[1] = 1.00
	hen[2] = 2.00
	hen[3] = 3.00
	hen[4] = 4.00
	hen[5] = 50.00

	var sum float64
	for i := 0; i < len(hen); i++ {
		sum += hen[i]
	}
	
	/**
	sum / float64(len(hen)) 这里之所以强转,是因为 len 是有数据类型的, 为 int
	如果直接写常量 6 就不需要
	 */
	average := fmt.Sprintf("%.2f", sum / float64(len(hen)))
	fmt.Println("average = ", average)

}

数组定义和内存布局

  • 数组的定义

var 数组名[数组大小]数据类型

var a (5]int

赋初值

a[0]= 1

a[1]=30…

  • 数组内存图

Go 数组、切片和 map

对上图的总结:

  1. 数组的地址可以通过数组名来获取 &intArray
  2. 数组的第一个元素的地址,就是数组的首地址
  3. 数组的各个元素的地址间隔是依据数组的类型决定,比如 int64 ->8 int32->4 …

四种初始化

  1. var numsArray [3]int = [3]int {1,2, 3)
  2. var nunsArray = [3]int {1, 2, 3)
  3. var numsArray = […]int {6, 7, 8}
  4. 可以指定元素值对应的下标
    var nartes = [3]string{1:“tom”, 0: “jack”, 2:“marry”]
package main

import "fmt"

func main()  {

	var arr1 [3]int = [3]int {0, 1, 2}
	fmt.Println("arr1 = ", arr1)

	var arr2 = [3]int {0, 1, 2}
	fmt.Println("arr2 = ", arr2)

	var arr3 = [...]int {0, 1, 2}
	fmt.Println("arr3 = ", arr3)

	var arr4 = [...]int {2: 900, 1: 800, 0:222}
	fmt.Println("arr4 = ", arr4)

}

数组的遍历

  1. 常规遍历:
    前面已经讲过了,不再赘述。
  2. for-range 结构遍历
    这是 Go 语言-种独有的结构,可以用来遍历访可数组的元素。
for index, value := range array {
    
}

如果 index 没有用到,可以使用 _ 忽略

var arr = [...]int {2: 900, 1: 800, 0:222}

for value,index := range arr {
	fmt.Printf("value = %v, index = %v\n", value, index)
}

数组使用注意事项和细节

  1. 数组是多个相同类型数据的组合,一个数组一旦声明/定义了,其长度是固定的,不能动态变化。
  2. var arr []int 这时 arr 就是一个 slice 切片,切片后面专门讲解,不急哈.
  3. 数组中的元素可以是任何数据类型,包括值类型和引用类型,但是不能混用。
  4. 数组创建后,如果没有赋值,有默认值

数值类型数组:默认值为 0

字符串数组:默认值为 “”

bool 数组:默认值为 false
5) 使用数组的步骤1.声明数组并开辟空间 2给数组各个元素赋值 3使用数组
6) 数组的下标是从 0 开始的。
7) 数组下标必须在指定范围内使用,否则报 panic: 数组越界,比如
var arr [5]int 则有效下标为0-4
8) Go 的数组属值类型,在默认情况下是值传递,因此会进行值拷贝。数组间不会相互影响
9) 如想在其它函数中,去修改原来的数组,可以使用引用传递(指针方式)
10) 长度是数组类型的一部分,在传递函数参数时需要考虑数组的长度

// 这种写法是错误的,因为没有指定数组长度
func test(arr []int) {
    
}
package main

import (
	"fmt"
	"math/rand"
	"time"
)

func main()  {

    // 随机生成 10 个数,并反转
	var arr [10]int
	rand.Seed(time.Now().Unix())
	for i := 0; i < len(arr); i++ {
		arr[i] = rand.Intn(100)
	}
	fmt.Println("before ", arr)

	tmp := 0
	for i := 0;i < len(arr) / 2; i++ {
		tmp = arr[len(arr) - 1 - i]
		arr[len(arr) - 1 - i] = arr[i]
		arr[i] = tmp
	}
	fmt.Println("after ", arr)

}

切片

  1. 切片的英文是 slice
  2. 切片是数组的一个引用,因此切片是引用类型,在进行传递时,遵守引用传递的
    机制。
  3. 切片的使用和数组类似,遍历切片、访问切片的元素和求切片长度 len(slice) 都一样。
  4. 切片的长度是可以变化的,因此切片是一个可以动态变化数组
  5. 切片定义的基本语法:
var 变量名 []类型
比如: var a [] int
// 演示切片的基本使用
var intarr [5]int = [...]int{1, 22, 33, 66, 99}
// 声明/定义一个切片
// slice := intArr[1:3] 
// 1. slice 就是切片名
// 2. intArr[1:3]表示slice 引用到intArr这个数组
// 3. 引用intArr数组的起始下标为1 ,最后的下标为3(但是不包含3)
slice := intArr[1:3]
fmt.Println( "intArr=" , intArr)
fmt.Println("slice的元素是=", slice) // 22, 33
fmt.Println("slice 的元素个数=", len(slice)) // 2
fmt.Println("slice 的容量=”, cap(slice)) // 切片的容量是可以动态变化

内存图

Go 数组、切片和 map

package main

import "fmt"

func main()  {

	intArr := [...]int {0, 1, 2, 3, 4, 5}
	slice  := intArr[1:3]
	fmt.Printf("type %T, slice = %v\n", slice, slice)
	fmt.Println("slice 元素个数是 ", len(slice), ", 容量是 ", cap(slice))

	slice[0] = 11
	fmt.Println("intArr =", intArr)

}

切片的使用

  • 第一种方式: 定义一个切片,然后让切片去引用一个已经创建好的数组
intArr := [...]int {0, 1, 2, 3, 4, 5}
slice  := intArr[1:3]
  • 第二种方式:通过make来创建切片.

基本语法: var 切片名 []type = make([], len, [cap])

参数说明: type 就是数据类型, len 大小,cap 指定切片容量,可选

package main

import "fmt"

func main()  {

	var slice []int = make([]int, 5)
	for i := 0; i < len(slice); i++ {
		slice[i] = i
	}
	fmt.Println("slice = ", slice)

}
  1. 通过 make 方式创建切片可以指定切片的大小和容量
  2. 如果没有给切片的各个元素赋值,那么就会使用默认值 【int,float=>0 string =>"" bool => false]
  3. 通过 make 方式创建的切片对应的数组是由 make 底层维护,对外不可见,即只能通过 slice 去访问各个元素.
  • 第3种方式:定义一个切片,直接就指定具体数组,使用原理类似 make 的方式。
package main

import "fmt"

func main()  {

	var slice []int = []int {0, 1, 2, 3, 4}
	fmt.Println("slice = ", slice)

}

方式1和方式2的区别(面试)

方式一是直接引用数组,这个数组是事先存在的,程序员是可见的。

方式二是通过 make 来创建切片,make 也会创建一个数组,是由切片在底层进行维护【此数组没有名称】,程序员是看不见的。

切片的遍历方式

  1. for 循环常规方式遍历
  2. for range 结构遍历切片
package main

import "fmt"

func main()  {

	var slice []int = []int {0, 1, 2, 3, 4}

	for i := 0; i < len(slice); i++ {
		fmt.Printf("slice[%v] = %v\n", i, slice[i])
	}

	for key,value := range slice {
		fmt.Println("key = ", key, ", value = ", value)
	}
	
}

切片注意事项和细节说明

  • 切片初始化时 var slice = arr[startIndex:endIndex]

说明:从 arr 数组 下标为startIndex,取到下标为endIndex的元素(不含
arrendIndex])。

  • 切片初始化时,仍然不能越界。范围在[0-len(arr)]之间, 但是可以动态增长.
  1. var slice = arr[0:end]可以简写var slice = arr[:end]
  2. var slice = arr[start:len(arr)]可以简写: var slice = arr[start:]
  3. var slice = arr[(0:len(arr)]可以简写: var slice = arr[:]
  • cap 是一个内置函数,用于统计切片的容量,即最大可以存放多少个元素。
  • 切片定义完后,还不能使用,因为本身是一个空的,需要让其引用到一个数组,
    或者 make 一个空间供切片来使用
  • 切片可以继续切片

动态增长

  • 用 append 内置函数,可以对切片进行动态追加

切片 append 操作的底层原理分析:

  1. 切片append 操作的本质就是对数组扩容
  2. go 底层会创建一个新的数组 newAr (安装扩容后大小)
  3. 将 slice 原来包含的元素拷贝到新的数组 newArr
  4. slice 重新引用到 newArr
  5. 注意 newArr 是在底层来维护的,程序员不可见.
package main

import "fmt"

func main()  {

	var slice []int = []int {0, 1, 2, 3, 4}
	slice1 := append(slice, 5, 6, 7, 8)
	slice = append(slice1, slice...)
	fmt.Println("slice1", slice1)
	fmt.Println("slice", slice)

}

也可以对没有 make 的切片直接 append

func test()  {

	var t []int
	t = append(t,1)
	t = append(t,1)
	t = append(t,1)
	t = append(t,1)
	fmt.Println(t)

}

切片的拷贝

  • 切片使用 copy 内置函数完成拷贝
    • 说明: copy(para1, para2): para1 和 para2 都是切片类型。

(1) copy(para1, para2)参数的数据类型是切片
(2) 按照上面的代码来看,slice4 和 slice5 的数据空间是独立,相互不影响,也就是说 slice4[0]= 999 slice5[0] 仍然是1

package main

import "fmt"

func main()  {

	var slice []int = []int {0, 1, 2, 3, 4}
	slice2 := make([]int, 10)
	copy(slice2, slice)
	slice2[0] = 100
	fmt.Println("slice = ", slice)
	fmt.Println("slice2 = ", slice2)

}

注意:切片和数组的定义

var slice []int = []int {0, 1, 2, 3, 4} // 切片,[]不需要声明长度
var slice [5]int = [5]int {0, 1, 2, 3, 4} // 数组,[5]需要声明长度

string 和 slice

  1. string 底层 是一个 byte 数组,因此 string 也可以进行切片处理
  2. string 和切片 在内存的形式
  3. string 是不可变的, 也就说不能通过 str[0]= ‘z’ 方式来修改字符串
  4. 如果需要修改字符串,可以先将 string -> []byte 或者 []rune->修改->重写转成 string

Go 数组、切片和 map

package main

import "fmt"

func main()  {

	str := "aaa@qq.com"
	slice := str[6:]
	fmt.Println("slice", slice)

    // []byte(str)可以处理中文和数字,但是中文就会出现乱码
	// bt := []byte(str)
	bt := []rune(str) // 这个是按字符来处理的,而不是按一个一个字节处理
	bt[0] = 'H'
	str = string(bt)
	fmt.Println("str", str)
	

}

细节,我们转成[]byte后,可以处理英文和数字,但是不能处理中文
原因是[]byte 字节来处理,而一个汉字,是 3 个字节,因此就会出现乱码
解决方法是将 string 转成 []rune 即可,因为 []rune 是按字符处理,兼容汉字

package main

import "fmt"

// 把斐波那契数列放入切片
func fbn(n int) ([]uint64)  {
	slice := make([]uint64, n)
	slice[0] = 1
	slice[1] = 1
	for i := 2; i < n; i++ {
		slice[i] = slice[i - 1] + slice[i - 2]
	}
	return slice
}

func main()  {

	fbn := fbn(10)
	fmt.Println("fbn = ", fbn)

}

二维数组

使用方式:先声明/定义再赋值

  1. 语法: var 数组名[大小][大小]类型
  2. 比如: var arr [2][3]int 再赋值。
package main

import "fmt"

func main()  {

	var arr [4][6]int
	arr[1][2] = 1
	arr[2][1] = 2
	arr[2][3] = 3

	for i := 0; i < 4; i++ {
		for j := 0; j < 6; j++ {
			fmt.Print(arr[i][j], " ")
		}
		fmt.Println()
	}

}

Go 数组、切片和 map

初始化

  1. 声明: var 数组名[大小][大小]类型= [大小[大小类型{初值.}{初值.}}
  2. 赋值(有默认值,比如 int 类型的就是 0)
  3. 使用演示
  4. 说明:二维数组在声明/定义时也对应有四种写法[和一维数组类似]
    var数组名 [大小][大小]类型= [大小][大小]类型{初值. },{初值 }
    var数组名 [大小][大小]类型= [大小]类型({初值.,{初值. }}
    var数组名= [大小][大小]类型{初值.3{初值}}
    var数组名= […]大小]类型{{初值…},{初值…}}

二维数组的遍历

  1. 双层 for 循环完成遍历
  2. for-range方式完戒遍历
package main

import (
	"fmt"
)

func main()  {

	var arr  = [4][6]int {{1, 2, 3}, {4, 5, 6}}
	for i := 0; i < len(arr); i++ {
		for j := 0; j < len(arr[i]); j++ {
			fmt.Printf("arr[%v][%v] = %v, ", i, j, arr[i][j])
		}
		fmt.Println()
	}
	for k, v := range arr {
		for kk, vv := range v {
			fmt.Printf("arr[%v][%v] = %v,", k, kk, vv)
		}
		fmt.Println()
	}
}

map

map 是 key-value 数据结构,又称为字段或者关联数组。类似其它编程语言的集合,
在编程中是经常使用到。

map 的声明

基本语法

var map变量名 map[keytype]valuetype

  • key 可以是什么类型

golang 中的 map 的 key 可以是很多种类型,比如 bool,数字,string, 指针,channel 还可以是只包含前面几个类型的接口,结构体,数组,通常为int、string

注意 slice, map 还有function 不可以,因为这几个没法用 == 来判断

基本语法

var map变量名 map[keytype]valuetype

  • valuetype 可以是什么类型

valuetype 的类型和 key 基本一样,这里我就不再赘述了

通常为:数字(整数,浮点数),string,map struct

map声明的举例:

var a map[string]string

var a map[string]int

var a map[int]string

var a map[string]map[string]string

注意:声明是不会分配内存的,初始化需要 make,分配内存后才能赋值和使用。

  1. map 在使用前一定要 make
  2. map 的 key 是不能重复,如果重复了,则以最后这个 key-value 为准
  3. map 的 value 是可以相同的.
  4. map 的 key-value 是无序

三种声明

package main

import "fmt"

func main()  {

    // 1
	var a map[string]string
	a = make(map[string]string)
	a["no1"] = "宋江"
	a["no2"] = "宋江"
	a["no3"] = "宋江"
	a["no4"] = "宋江"
	fmt.Println(a)

    // 2
	cities := make(map[string]string)
	cities["no1"] = "北京"
	cities["no2"] = "北京"
	cities["no3"] = "北京"
	cities["no4"] = "北京"
	fmt.Println(cities)

    // 3
	heroes := map[string]string {
		"no1" : "heroes1",
		"no2" : "heroes1",
		"no3" : "heroes1",
		"no4" : "heroes1",
	}
	fmt.Println(heroes)

}

复杂类型的

students := make(map[string]map[string]string)
students["01"] = make(map[string]string)
students["01"]["sex"] = "male"
students["01"]["age"] = "15"
students["01"]["address"] = "beijing"
students["02"] = make(map[string]string)
students["02"]["sex"] = "male"
students["02"]["age"] = "15"
students["02"]["address"] = "beijing"

map 的增删改查操作

  • map 增加和更新:

map["'key"] = value // 如果 key 还没有,就是增加,如果 key 存在就是修改。

  • map 删除:

delete(map, “key”) ,delete 是一个内置函数,如果 key 存在,就删除该 key-value,如果 key 不存在,不操作,但是也不会报错

➢细节说明

  1. 如果我们要删除 map 的所有 key ,没有一个专门的方法一次删除,可以遍历下 key , 逐个删除
  2. 或者 map = mak…, make- 一个新的,让原来的成为垃城,被 gc 回收
delete(students, "01")
fmt.Println(students)

students = make(map[string]map[string]string)
fmt.Println(students)
  • 查找
city, ok := cities["no3"]
if ok {
	fmt.Println("yes, city is ", city)
} else {
	fmt.Println("no")
}
  • map 遍历:

案列演示相对复杂的 map 遍历:该 map 的 value 又是一个 map

map的遍历使用for-range的结构遍历

package main

import "fmt"

func main()  {

	var a map[string]string
	a = make(map[string]string)
	a["no1"] = "宋江"
	a["no2"] = "宋江"
	a["no3"] = "宋江"
	a["no4"] = "宋江"

	for key, value := range a {
		fmt.Printf("key = %v, value = %v\n", key, value)
	}


	students := make(map[string]map[string]string)
	students["01"] = make(map[string]string)
	students["01"]["sex"] = "male"
	students["01"]["age"] = "15"
	students["01"]["address"] = "beijing"
	students["02"] = make(map[string]string)
	students["02"]["sex"] = "male"
	students["02"]["age"] = "15"
	students["02"]["address"] = "beijing"
	for key, value := range students {
		fmt.Println("key = ", key)
		for k, v := range value {
			fmt.Printf("k = %v, v = %v\n", k, v)
		}
	}

}
  • map 的长度:
fmt.Println("students len is ", len(students))

map 切片

基本介绍

切片的数据类型如果是 map,则我们称为 slice of map, map 切片,这样使用则
map 个数就可以动态变化了。

package main

import "fmt"

func main()  {

	var monsters []map[string]string
	monsters = make([]map[string]string, 2)

	if monsters[0] == nil {
		monsters[0] = make(map[string]string, 2)
		monsters[0]["name"] = "牛魔王"
		monsters[0]["age"] = "500"
	}
	if monsters[1] == nil {
		monsters[1] = make(map[string]string, 2)
		monsters[1]["name"] = "玉兔精"
		monsters[1]["age"] = "400"
	}
	newMonster := map[string]string {
		"name" : "火云邪神",
		"age"  : "100",
	}
	monsters = append(monsters, newMonster)

	fmt.Println(monsters)

}

map排序

  1. golang 中没有-个专门的方法针对 map 的 key 进行排序
  2. golang 中的 map 默认是无序的,注意也不是按照添加的顺序存放的,你每次遍
    历,得到的输出可能不一样
  3. golang 中 map 的排序,是先将 key 进行排序,然后根据 key 值遍历输出即可
package main

import (
	"fmt"
	"sort"
)

func main()  {


	//如果按照map的key的顺序进行排序输出
	//1. 先将map的key 放入到切片中
	//2. 对切片排序
	//3. 遍历切片,然后按照key来输出map的值
	a := make(map[int]int)
	a[0] = 0
	a[1] = 1
	a[2] = 2
	a[3] = 3
	a[4] = 4

	var slice []int
	for index, _ := range a {
		slice = append(slice, index)
	}
	sort.Ints(slice)
	fmt.Println(slice)

	for _, v := range slice {
		fmt.Printf("a[%v] = %v\n", v, a[v])
	}
}

map 使用细节

  1. map 是引用类型, 遵守引用类型传递的机制,在一个函数接收map,修改后,会直接修改原
    来的 map
  2. map 的容量达到后,再想 map 增加元素,会自动扩容,并不会发生 panic,也就是说 map 能动态的增长键值对(key-value)
  3. map 的 value 也经常使用 struct 类型,更适合管理复杂的数据(比前面value是-个map更好),比如 value 为 Student 结构体