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…
- 数组内存图
对上图的总结:
- 数组的地址可以通过数组名来获取 &intArray
- 数组的第一个元素的地址,就是数组的首地址
- 数组的各个元素的地址间隔是依据数组的类型决定,比如 int64 ->8 int32->4 …
四种初始化
- var numsArray [3]int = [3]int {1,2, 3)
- var nunsArray = [3]int {1, 2, 3)
- var numsArray = […]int {6, 7, 8}
- 可以指定元素值对应的下标
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)
}
数组的遍历
- 常规遍历:
前面已经讲过了,不再赘述。 - 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)
}
数组使用注意事项和细节
- 数组是多个相同类型数据的组合,一个数组一旦声明/定义了,其长度是固定的,不能动态变化。
- var arr []int 这时 arr 就是一个 slice 切片,切片后面专门讲解,不急哈.
- 数组中的元素可以是任何数据类型,包括值类型和引用类型,但是不能混用。
- 数组创建后,如果没有赋值,有默认值
数值类型数组:默认值为 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)
}
切片
- 切片的英文是 slice
-
切片是数组的一个引用,因此切片是引用类型,在进行传递时,遵守引用传递的
机制。 - 切片的使用和数组类似,遍历切片、访问切片的元素和求切片长度 len(slice) 都一样。
- 切片的长度是可以变化的,因此切片是一个可以动态变化数组。
- 切片定义的基本语法:
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)) // 切片的容量是可以动态变化
内存图
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)
}
- 通过 make 方式创建切片可以指定切片的大小和容量
- 如果没有给切片的各个元素赋值,那么就会使用默认值 【int,float=>0 string =>"" bool => false]
- 通过 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 也会创建一个数组,是由切片在底层进行维护【此数组没有名称】,程序员是看不见的。
切片的遍历方式
- for 循环常规方式遍历
- 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)]之间, 但是可以动态增长.
- var slice = arr[0:end]可以简写var slice = arr[:end]
- var slice = arr[start:len(arr)]可以简写: var slice = arr[start:]
- var slice = arr[(0:len(arr)]可以简写: var slice = arr[:]
- cap 是一个内置函数,用于统计切片的容量,即最大可以存放多少个元素。
- 切片定义完后,还不能使用,因为本身是一个空的,需要让其引用到一个数组,
或者 make 一个空间供切片来使用 - 切片可以继续切片
动态增长
- 用 append 内置函数,可以对切片进行动态追加
切片 append 操作的底层原理分析:
- 切片append 操作的本质就是对数组扩容
- go 底层会创建一个新的数组 newAr (安装扩容后大小)
- 将 slice 原来包含的元素拷贝到新的数组 newArr
- slice 重新引用到 newArr
- 注意 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
- string 底层 是一个 byte 数组,因此 string 也可以进行切片处理
- string 和切片 在内存的形式
- string 是不可变的, 也就说不能通过 str[0]= ‘z’ 方式来修改字符串
- 如果需要修改字符串,可以先将 string -> []byte 或者 []rune->修改->重写转成 string
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)
}
二维数组
使用方式:先声明/定义再赋值
- 语法: var 数组名[大小][大小]类型
- 比如: 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()
}
}
初始化
- 声明: var 数组名[大小][大小]类型= [大小[大小类型{初值.}{初值.}}
- 赋值(有默认值,比如 int 类型的就是 0)
- 使用演示
- 说明:二维数组在声明/定义时也对应有四种写法[和一维数组类似]
var数组名 [大小][大小]类型= [大小][大小]类型{初值. },{初值 }
var数组名 [大小][大小]类型= [大小]类型({初值.,{初值. }}
var数组名= [大小][大小]类型{初值.3{初值}}
var数组名= […]大小]类型{{初值…},{初值…}}
二维数组的遍历
- 双层 for 循环完成遍历
- 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,分配内存后才能赋值和使用。
- map 在使用前一定要 make
- map 的 key 是不能重复,如果重复了,则以最后这个 key-value 为准
- map 的 value 是可以相同的.
- 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 不存在,不操作,但是也不会报错
➢细节说明
- 如果我们要删除 map 的所有 key ,没有一个专门的方法一次删除,可以遍历下 key , 逐个删除
- 或者 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排序
- golang 中没有-个专门的方法针对 map 的 key 进行排序
- golang 中的 map 默认是无序的,注意也不是按照添加的顺序存放的,你每次遍
历,得到的输出可能不一样 - 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 使用细节
-
map 是引用类型, 遵守引用类型传递的机制,在一个函数接收map,修改后,会直接修改原
来的 map - map 的容量达到后,再想 map 增加元素,会自动扩容,并不会发生 panic,也就是说 map 能动态的增长键值对(key-value)
- map 的 value 也经常使用 struct 类型,更适合管理复杂的数据(比前面value是-个map更好),比如 value 为 Student 结构体
上一篇: ftp虚拟用户访问
下一篇: .netcore-abp-分布式缓存