Go 数组、切片和 map - Go语言中文社区

Go 数组、切片和 map


数组

数组可以存放多个同一类型数据。数组也是一种数据类型,在 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…

  • 数组内存图

image

对上图的总结:

  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 = %vn", 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)) // 切片的容量是可以动态变化

内存图

image

package main

import "fmt"

func main()  {

	intArr := [...]int {0, 1, 2, 3, 4, 5}
	slice  := intArr[1:3]
	fmt.Printf("type %T, slice = %vn", 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] = %vn", 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

image

package main

import "fmt"

func main()  {

	str := "hello@guigu"
	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()
	}

}

image

初始化

  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 = %vn", 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 = %vn", 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] = %vn", v, a[v])
	}
}

map 使用细节

  1. map 是引用类型, 遵守引用类型传递的机制,在一个函数接收map,修改后,会直接修改原
    来的 map
  2. map 的容量达到后,再想 map 增加元素,会自动扩容,并不会发生 panic,也就是说 map 能动态的增长键值对(key-value)
  3. map 的 value 也经常使用 struct 类型,更适合管理复杂的数据(比前面value是-个map更好),比如 value 为 Student 结构体
版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/molaifeng/article/details/103327496
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢