> 文章列表 > Golang学习+深入(九)-数组/切片/map

Golang学习+深入(九)-数组/切片/map

Golang学习+深入(九)-数组/切片/map

目录

一、概述

1、数组

1.1、四种初始化数组的方式

1.2、数组遍历

1.3、数组使用注意事项和细节

1.4、二维数组

1.5、二维数组的遍历

2、切片slice

2.1、切片的使用

2.2、切片的遍历

2.3、string和slice

3、map

3.1、map使用的方式

3.2、map的增删改查操作

3.3、map切片

3.4、map排序

3.5、map使用细节

二、排序和查找

1、排序

2、查找


一、概述

1、数组

  1. 数组可以存放多个同一类型数据,数组也是一种数据类型,在Go中,数组是值类型
  2. 数组的地址可以通过数组名来获取&arr
  3. 数组的第一个元素的地址,就是数组的首地址
  4. 数组的各个元素的地址间隔是依据数组的类型决定的
数组的定义var 数组名 [数组大小]数据类型var a[5]int赋初值 a[0] =1 a[1]=20 .....
访问数组元素数组名[下标]  
package main
import ("fmt"
)
func main(){var arr [3]intarr[0] =10arr[1] =20arr[2] =30fmt.Printf("arr的地址=%p \\narr[0]的地址%p \\narr[1]的地址%p \\narr[2]的地址%p\\n",&arr,&arr[0],&arr[1],&arr[2])
}

1.1、四种初始化数组的方式

package main
import ("fmt"
)
func main(){var arr1 [3]int =[3]int {1,2,3}var arr2 =[3]int {1,2,3}var arr3 =[...]int {6,7,8,9}var arr4 =[3]string {1:"t",2:"m",0:"r"}fmt.Println(arr1)fmt.Println(arr2)fmt.Println(arr3)fmt.Println(arr4)
}

1.2、数组遍历

方式1:var arr1 [3]int =[3]int {1,2,3}for i:=0;i<len(arr1);i++ {fmt.Println(arr1[i])}
方式2:for...range结构遍历
基本语法:for index,value := range arr1{...}
//arr1:数组名称
//第一个返回值index是数组的下标
//第二个value是在该下标位置的值
//index,value都是仅在for循环内部可见的局部变量
//如果不想使用index,可以用_替换
//index和value名称不是固定的,可以自定义var arr1 [3]int =[3]int {1,2,3}for index,value := range arr1 {fmt.Println(index,value)}

1.3、数组使用注意事项和细节

  1. 数组是多个相同类型数据的组合,一个数组一旦声明/定义了,其长度是固定的,不能动态变化。
  2. var arr []int 这时 arr就是一个slice切片。
  3. 数组中的元素可以是任何数据类型,包括值类型和引用类型,但是不能混用。
  4. 数组创建后,如果没有赋值,有默认值
    1. 数值类型数组:默认值为0
    2. 字符串数组: 默认值为""
    3. bool数组: 默认值为false
  5. 使用数组的步骤 1.声明数组并开辟空间 2.给数组各个元素赋值 3.使用数组
  6. 数组的下标是从0开始的
  7. 数组下标必须在指定范围内使用,否则报panic:数组越界
  8. Go的数组属值类型,在默认情况下是值传递,因此会进行值拷贝。数组间不会相互影响。
  9. 如想在其他函数中,去修改原来的数组,可以使用引用传递(指针方式)
  10. 长度是数组类型的一部分,在传递函数参数时,需要考虑数组的长度。

1.4、二维数组

基本语法:var 数组名 [大小][大小]类型
方式2:直接初始化var 数组名 [大小][大小]类型 = [大小][大小]类型{{初值..},{初值..}}二维数组在声明时也有对应的四种写法(和一维数组类似)
var arr1 [3][3]int =[3][3]int {{1,2,3},{4,5,6},{7,8,9}}
var arr2 [3][3]int =[...][3]int {{1,2,3},{4,5,6},{7,8,9}}
var arr3 =[3][3]int {{1,2,3},{4,5,6},{7,8,9}}
var arr4 =[...][3]int {{1,2,3},{4,5,6},{7,8,9}}
fmt.Println(arr1)
fmt.Println(arr2)
fmt.Println(arr3)
fmt.Println(arr4)

1.5、二维数组的遍历

方式1:双层for循环完成遍历
方式2:for...range方式遍历
var arr [3][3]int =[3][3]int {{1,2,3},{4,5,6},{7,8,9}}
for i := 0;i <len(arr);i++{for j := 0;j<len(arr[i]);j++{fmt.Printf("%v\\t",arr[i][j])}fmt.Println()
}
for i,v :=range arr{for j,v2 := range v{fmt.Printf("arr[%v][%v]=%v\\t",i,j,v2)}fmt.Println()
}

2、切片slice

  1. 切片的英文是slice
  2. 切片是数组的一个引用,因此切片是引用类型,在进行传递时,遵守引用传递的机制。
  3. 切片的使用和数组类似,遍历切片、访问切片的元素和求切片长度len(slice)都一样
  4. 切片的长度是可以变化的,因此切片是一个可以动态变化数组
  5. 切片定义的基本语法:
    1. var 变量名 []类型
    2. 比如:var a []int
  6. 切片初始化时,仍然不能越界。范围[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[:]
  1. cap是一个内置函数,用于统计切片的容量,即最大可以放多少个元素
  2. 切片定义完还不能使用,因为本身是一个空的,需要让其引用到一个数组,或者make一个空间供切片来使用。
  3. 切片可以继续切片
var arr [5]int = [...]int{11,22,33,44,55}
slice := arr[1:4]
slice2 := slice[1:2]
  1. 用append内置函数,可以对切片进行动态追加
package main
import ("fmt"
)
func main(){var slice []int = []int {1,2,3}slice = append(slice,10,20,30)//追加具体元素var a = []int {100,200}slice = append(slice,a...)//在切片上追加切片fmt.Println("slice=",slice)fmt.Println("slice 的len=",len(slice))fmt.Println("slice 的容量=",cap(slice))
}
-------------------------------------
切片append操作的底层原理分析:
1.切片append操作的本质就是对数组扩容
2.go底层会创建一下新的数组newArr(安装扩容后大小)
3.将slice原来包含的元素拷贝到新的数组newArr
4.slice重新引用到newArr
5.注意newArr是在底层来维护的,程序员不可见
  1. 切片使用copy内置函数完成拷贝

copy(p1,p2):p1和p2都是切片类型

package main
import ("fmt"
)
func main(){var a = []int {100,200}var slice []int = make([]int,10)fmt.Println("slice=",slice)copy(slice,a)fmt.Println("slice=",slice)
}
-------------------------------------
面试题:
下面代码有没有错误
var a = []int {1,2,3,4,5}
var slice []int = make([]int,2)
fmt.Println("slice=",slice)
copy(slice,a)
fmt.Println("slice=",slice)
//没有错误,可以运行,拷贝满就不拷贝了。
  1. 切片是引用类型,所以在传递时,遵守引用传递机制。
package main
import ("fmt"
)
func main(){var arr [5]int = [...]int{11,22,33,44,55}slice := arr[1:3]fmt.Println("arr=",arr)fmt.Println("slice=",slice)fmt.Println("slice 的len=",len(slice))//slice 的len= 2fmt.Println("slice 的容量=",cap(slice))//slice 的容量= 4fmt.Printf("arr[1]的地址=%p\\n",&arr[1])//arr[1]的地址=0xc0000123c8fmt.Printf("slice[0]的地址=%p\\n",&slice[0])//slice[0]的地址=0xc0000123c8
}

slice从底层来说,其实就是一个数据结构(struct结构体)

type slice struct {ptr *[2]intlen intcap int
}

2.1、切片的使用

方式1: 定义一个切片,然后让切片去引用一个已经创建好的数组,比如上面的案例

方式2: 通过make来创建切片。

基本语法:var 切片名 []type = make([],len,[cap])参数说明:type:就是数据类型 len:大小 cap:指定切片容量,可选
------------------------------------------------------
1.通过make方式创建切片可以指定切片的大小和容量
2.如果没有给切片赋值,那么会使用默认值
3.通过make方式创建的切片对应的数组是由make底层维护,对外不可见,即只能通过slice去访问各个元素
package main
import ("fmt"
)
func main(){var slice []int = make([]int,4)fmt.Println("slice=",slice)//slice= [0 0 0 0]fmt.Println("slice 的len=",len(slice))//slice 的len= 4fmt.Println("slice 的容量=",cap(slice))//slice 的容量= 4
}

方式3: 定义一个切片,直接就指定具体数组,使用原理类似make的方式

package main
import ("fmt"
)
func main(){var slice []int = []int {1,2,3}fmt.Println("slice=",slice)fmt.Println("slice 的len=",len(slice))fmt.Println("slice 的容量=",cap(slice))
}
面试题:
方式1和方式2的区别
方式1是直接引用数组,这个数组是事前存在的,程序员是可见的。
方式2是通过make来创建切片,make也会创建一个数组,是由切片在底层进行维护,程序员是看不见的

2.2、切片的遍历

切片的遍历和数组一样,也有两种方式
1.for循环常见方式遍历
2.for-range结构遍历切片

2.3、string和slice

  1. string底层是一个byte数组,因此string也可以进行切片处理
  2. string和切片在内存的形式
  3. stirng是不可变的,也就是说不能通过str[0]='z'方式来修改字符串
  4. 如果需要修改字符串,可以先将string-->[]byte /或者[]rune-->修改-->重写转成string
package main
import ("fmt"
)
func main(){str := "hello@world!"slice := str[6:]fmt.Println("slice=",slice)arr := []byte(str)//byte不能处理中文--中文处理替换为rune因为rune按字符处理,兼容汉字arr[0]='z'str = string(arr)fmt.Println("str=",str)
}
//斐波那契数列输出实现如下:
提示:斐波那契数组形式:
a[0]=1,a[1]=1,a[2]=a[1]+a[0].....
----------------------------------------
package main
import ("fmt"
)
func main(){fSlice := fbn(10)fmt.Println("fSlice=",fSlice)
}func fbn(n int) ([]uint64){fSlice := make([]uint64,n)fSlice[0] =1fSlice[1] =1for i :=2;i<n;i++{fSlice[i] = fSlice[i-1] + fSlice[i-2]}return fSlice
}

3、map

map是key-value数据结构,又称为字段或者关联数组。

基本语法:var map变量名 map[keytype]valuetype
key可以是什么类型Golang中的map的key可以是很多种类型,比如 bool,数字,string,指针,channel,
还可以是只包含前面几个类型的 接口,结构体,数组
通常为int、string
注意:slice,map还有function不可以,因为这几个没法用==来判断
value可以是什么类型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,分配内存后才能赋值和使用。
package main
import ("fmt"
)
func main(){var a map[string]stringa = make(map[string]string,10)a["n01"]="s1"a["n02"]="s2"a["n03"]="s3"a["n01"]="s10"fmt.Println(a)
}
//map在使用前一定要make
//map的key是不能重复,如果重复了,则以最后这个key-value为准
//map的value是可以相同的
//map的key-value是无序
//make内置函数

3.1、map使用的方式

方式1://先声明,后makevar a map[string]stringa = make(map[string]string,10)
方式2://声明,就直接makevar a =make(map[string]string)
方式3://声明,直接赋值var a map[string]string = map[string]string{"n01":"s20",}a["n02"] = "s30"

3.2、map的增删改查操作

map增加和更新:

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

map删除:

func delete(m map[Type]Type1, key Type):内建函数delete按照指定的键将元素从映射中删除。
若m为nil或无此元素,delete不进行操作。
delete(map,"key"):delete是一个内置函数,如果key存在,就删除该key-value,
如果key不存在,不操作,但是也不会报错var a map[string]string = map[string]string{"n01":"s20",}a["n02"] = "s30"delete(a,"n01")
1.如果我们要删除map的所有key,没有一个专门的方法一次删除,可以遍历一下key,逐个删除
2.或者map =make(...),make一个新的,让原来的成为垃圾,被gc回收

map查找:

package main
import ("fmt"
)
func main(){var a map[string]stringa = make(map[string]string,10)a["n01"]="s1"a["n02"]="s2"val,ok := a["n01"]if ok {fmt.Println("找到了val=",val)}else{fmt.Println("没有n01这个key")}fmt.Println(a)
}
//说明:如果a这个map中存在"n01",那么ok就会返回true,否则返回false

map遍历:

map的遍历使用for-range的结构遍历sMap := make(map[string]string)sMap["n01"]="s1"sMap["n02"]="s2"sMap["n03"]="s3"for k,v := range sMap {fmt.Printf("k=%v v=%v\\n",k,v)}

map的长度

fmt.Println(len(sMap))

3.3、map切片

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

package main
import ("fmt"
)
func main(){var sMap []map[string]stringsMap = make([]map[string]string,2)a := make(map[string]string)a["name"]="n1"a["age"]="18"sMap =append(sMap,a)fmt.Println(sMap)
}

3.4、map排序

  1. map默认是无序的
  2. map排序,是先将key进行排序,然后根据key值遍历输出即可
package main
import ("fmt""sort"
)
func main(){map1 := make(map[int]int,10)map1[1] =100map1[3] =6map1[19] =33map1[2] =2map1[8] =9fmt.Println(map1)var keys []intfor k,_ := range map1{keys = append(keys,k)}sort.Ints(keys)fmt.Println(keys)for _,k := range keys{fmt.Printf("map1[%v]=%v\\n",k,map1[k])}
}

3.5、map使用细节

  1. map是引用类型,遵守引用类型传递的机制,在一个函数接收map,修改后,会直接修改原来的map。
  2. map的容量达到后,再想map增加元素,会自动扩容,并不会发生panic,也就是说,能动态增长键值对
  3. map的value也经常使用struct类型

二、排序和查找

1、排序

排序是将一组数据,依指定的顺序进行排列的过程。

排序的分类:

1、内部排序:

指将需要处理的所有数据都加载到内部存储器中进行排序。包括(交换式排序法,选择式排序法和插入式排序法)

  • 交换式排序法
    1. 冒泡排序
    2. 快速排序

2.外部排序法

数据量过大,无法全部加载到内存中,需要借助外部存储进行排序。包括(合并排序法和直接合并排序法)。

package main
import ("fmt"
)
//冒泡排序
func BubbleSort(arr []int){fmt.Println("排序前arr=",arr)for i :=0;i<len(arr) -1;i++{for j :=0;j<len(arr)-1-i;j++{if arr[j]>arr[j+1]{tmp := arr[j]arr[j]=arr[j+1]arr[j+1]=tmp}}}fmt.Println("排序后arr=",arr)
}func main(){slice := []int {7,4,6,9,1}BubbleSort(slice)
}

2、查找

在Golang中,常用的查找有2种:顺序查找/二分查找(前提是该数组是有序的)

package main
import ("fmt"
)
//二分查找 arr从小到大
func BinaryFind(arr []int,leftIndex int,rightIndex int,findVal int){if leftIndex > rightIndex {fmt.Println("找不到")return}middle := (leftIndex+rightIndex)/2if arr[middle] > findVal{BinaryFind(arr,leftIndex,middle-1,findVal)}else if arr[middle]==findVal{fmt.Printf("找到了,下标为%v\\n",middle)}else{BinaryFind(arr,middle+1,rightIndex,findVal)}
}func main(){arr := []int{1,8,10,89,100,290}BinaryFind(arr,0,len(arr)-1,10)
}

干我们这行,啥时候懈怠,就意味着长进的停止,长进的停止就意味着被淘汰,只能往前冲,直到凤凰涅槃的一天!