数组切片和map
数组的基本用法
有定义长度的为数组,每定义长度的为切片,虽然很像但不是一个东西。
var array2 [3]string
不用长度的数组时不能赋值的
var array1 [3]string
var array2 [5]string
array1 = array2//这个会报错,因为不同长度
###数组的初始化
完整版var array [3]string = [3]string{"abc","慕课网","xx"}
简化版var array = [3]string{"abc", "慕课网", "xx"}
冒号方式赋值较为常用array := [3]string{"abc", "慕课网", "xx"}
仅为某个key(index)做初始化,例如为下标为2的初始化为xxarray := [3]string{2: "xx"}
但是仅对某个下标初始化 很多会直接用赋值
array := [3]string{}
array[2] = "xx"
省略号帮我们自动定义长度,下面代码为例,array长度也是3array := [...]string{"abc","慕课网","xx"}
多维数组
array2 := [3][4]string{{"a", "b", "c", "d"}, {"aa", "bb", "cc", "dd"}, {"aaa", "bbb", "ccc", "ddd"}}
for x, y := range array2 {
for k, v := range y {
fmt.Println(x, y, k, v)
}
}
array2 := [3][4]string{}
array2[0] = [4]string{"a", "b", "c", "d"}
array2[1] = [4]string{"aa", "bb", "cc", "dd"}
array2[2] = [4]string{"aaa", "bbb", "ccc", "ddd"}
for x, y := range array2 {
for k, v := range y {
fmt.Println(x, y, k, v)
}
}
也可以用for 的下标,但是range比较好用,不再展示了。
ps 数组可以直接比较,但是长度得相等。。
切片的定义和赋值
定义
var courses []string
fmt.Printf("%T", courses)
//[]string
追加数据
var courses []string
courses = append(courses, "go")
courses = append(courses, "体系课")
fmt.Println(courses)
//[go 体系课]
切片的初始化
第一种 从数组直接创建
allCourses := [4]string{"go", "xxx", "aaa", "bbb"}
coursesSlice := allCourses[0:2]
fmt.Println(coursesSlice)
//[go xxx]
allCourses := [4]string{"go", "xxx", "aaa", "bbb"}
coursesSlice := allCourses[0:len(allCourses)]
fmt.Println(coursesSlice)
第二种,开局直接赋值
courses := []string{"aa", "bb", "cc", "dd"}
fmt.Println(courses)
//[aa bb cc dd]
第三种 使用make,并提前给空间
allCourses := make([]string, 3)
allCourses[0] = "aa"
allCourses[1] = "bb"
allCourses[2] = "cc"
fmt.Println(allCourses)
//[aa bb cc]
如果没给空间只能用append去赋值。。
访问切片的元素
allCourses := make([]string, 3)
allCourses[0] = "aa"
allCourses[1] = "bb"
allCourses[2] = "cc"
fmt.Println(allCourses[1], allCourses[0:2])
//bb [aa bb]
ps:
切片内容 allCourses[start:end]
如果有start 没有end 则取start 到结尾
如果没有start 有end 则取end之前的数据
如果都没写,就是全取
如果即有start也有end 则取start和end之前的数据
(end = index + 1)
省略号添加数据
courses := []string{"x", "xx"}
courses = append(courses, "xxx", "xxxx", "xxxxx")
fmt.Println(courses)
//[x xx xxx xxxx xxxxx]
切片合并
合并时要记得不要漏了省略号,如果没省略号是不能追加。
courses := []string{"x", "xx"}
courses2 := []string{"xxx", "xxxx"}
courses = append(courses, courses2[:]...)
fmt.Println(courses)
[x xx xxx xxxx]
切片的删除和拷贝
删掉数据比较麻烦,以下例子为删掉下标为3也就是 xxx的这个元素
如果不是删中间的,可以用上面的 切片[start:end]方式去截取
courses := []string{"x", "xx", "xxx", "xxxx", "xxxxx", "xxxxxx"}
x := append(courses[:2], courses[3:]...)
fmt.Println(x)
[x xx xxxx xxxxx xxxxxx]
拷贝
copy的话创建的切片得指明空间大小,否则初始空间0为拷贝一个空
copy的数据不会随着原始切片的改变而改变,但是用courses2这种方法去赋值的话,原数据改变会跟着改变
courses := []string{"x", "xx", "xxx", "xxxx", "xxxxx", "xxxxxx"}
courses2 := courses[:]
var coursescopy = make([]string, len(courses))
copy(coursescopy, courses)
fmt.Println(courses, courses2, coursescopy)
courses[0] = "ooo"
fmt.Println(courses, courses2, coursescopy)
[x xx xxx xxxx xxxxx xxxxxx] [x xx xxx xxxx xxxxx xxxxxx] [x xx xxx xxxx xxxxx xxxxxx]
[ooo xx xxx xxxx xxxxx xxxxxx] [ooo xx xxx xxxx xxxxx xxxxxx] [x xx xxx xxxx xxxxx xxxxxx]
为什么要懂原理
go的切片再函数参数传递的时候是值传递还是引用传递。 实际是值传递,效果又呈现大致是引用传递的效果
package main
import (
"fmt"
"strconv"
)
func printSlice(data []string) {
data[0] = "java"
for i := 0; i < 10; i++ {
data = append(data, strconv.Itoa(i))
}
fmt.Println(data, "函数内的打印")
}
func main() {
courses := []string{"x", "xx", "xxx", "xxxx", "xxxxx", "xxxxxx"}
printSlice(courses)
fmt.Println(courses, "函数外的打印")
}
[java xx xxx xxxx xxxxx xxxxxx 0 1 2 3 4 5 6 7 8 9] 函数内的打印
[java xx xxx xxxx xxxxx xxxxxx] 函数外的打印
改变他的值时,又跟着改变,但是append时又没有加进来,下一章
切片的底层原理
import "fmt"
import "strconv"
func printSlice(data []string) {
for i := 0; i < 10; i++ {
data = append(data, strconv.Itoa(i))
}
data[0] = "java"
fmt.Println(data, "函数内的打印")
}
func main() {
courses := []string{"x", "xx", "xxx", "xxxx", "xxxxx", "xxxxxx"}
printSlice(courses)
fmt.Println(courses, "函数外的打印")
}
[java xx xxx xxxx xxxxx xxxxxx 0 1] 函数内的打印
[x xx xxx xxxx xxxxx xxxxxx] 函数外的打印
由于切片传进去时是被被拷贝一份新数据,但是地址指向的是相同空间的地址。经过append如果扩容了空间,会赋予新的地址。(两边代码的 data[0] = “java”)位置改变了下,输出就不同了。
在append扩容前他们是使用相同地址空间,所以改了受影响。。
扩容是成倍扩容,低于1024个时成倍,如果高于1024时则扩容1.25就是原来的4分之一。假设2000个空间则扩容到 2500个。
我们看下上面的代码,我们看下他的空间
package main
import "fmt"
import "strconv"
func printSlice(data []string) {
for i := 0; i < 10; i++ {
data = append(data, strconv.Itoa(i))
}
data[0] = "java"
fmt.Println(data, "函数内的打印")
fmt.Println(len(data),cap(data))
//data由于append了10次,加上原来的6个长度等于16个长度,但是空间是原来的6个然后append了1下变成12个,然后又append了6次变成24个空间。
}
func main() {
courses := []string{"x", "xx", "xxx", "xxxx", "xxxxx", "xxxxxx"}//长度6个,空间6个
printSlice(courses)
fmt.Println(courses, "函数外的打印")
fmt.Println(len(courses),cap(courses))
输出
[java xx xxx xxxx xxxxx xxxxxx 0 1 2 3 4 5 6 7 8 9] 函数内的打印
16 24
[x xx xxx xxxx xxxxx xxxxxx] 函数外的打印
6 6
}
也就是一个16长度,24空间。外面的依旧是6长度6个空间。
ps 扩容会影响性能,如果需要多次扩容建议刚开始给定空间大点。避免多次扩容。
map 初始化和赋值
map 是无序的。
参考链接:https://topgoer.com/go%E5%9F%BA%E7%A1%80/Map.html
定义和取值
var courseMap = map[string] string{
"go":"go工程师",
"php":"php工程师",
"java":"kava工程师",
}
fmt.Println(courseMap,courseMap["go"])
定义完必须初始化,否则会报错。可以不赋值var courseMap = map[string] string{}
make方式初始化var courseMap = make(map[string] string,3)
遍历
var courseMap = map[string] string{
"go":"go工程师",
"php":"php工程师",
"java":"kava工程师",
}
for key,val := range courseMap{
fmt.Println(key,val)
}
for key := range courseMap{
fmt.Println(key)
}
输出
go go工程师
php php工程师
java kava工程师
go
php
java
判断map是否存在元素、删除元素
判断元素是否在里面得用 data,ok:= mpa[key]这种方式去判断。下面例子key存在和不存在都能进python的判断。
var courseMap = map[string] string{
"go":"go工程师",
"php":"php工程师",
"java":"kava工程师",
}
if courseMap["python"] ==""{
fmt.Println("这么判断可能会错,如果有key没值也会报错,所以用下面方式")
}
courseMap["python"] = ""
if courseMap["python"] ==""{
fmt.Println("这么判断可能会错,如果有key没值也会报错,所以用下面方式")
}
data,ok := courseMap["mysql"]
if ok{
fmt.Println(data)
}
data1,ok := courseMap["go"]
if ok{
fmt.Println(data1)
}
输出
这么判断可能会错,如果有key没值也会报错,所以用下面方式
这么判断可能会错,如果有key没值也会报错,所以用下面方式
go工程师
缩写
if data,ok := courseMap["go"]; ok{
fmt.Println(data)
}
删除 delete
var courseMap = map[string] string{
"go":"go工程师",
"php":"php工程师",
"java":"kava工程师",
}
delete(courseMap,"php")
fmt.Println(courseMap)
ps 不是线程安全。以后做多线程得用sync的map
参考链接底部介绍。
参考:https://topgoer.com/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/sync.html
list和slice的区别
list每个元素都是独立空间,新增不用在额外申请空间。例如slice申请了6个空间,如果长度已经到达6时如果在append数据进去,会在申请6个空间。而list仅需要申请一个。
list占用空间大,因为他是链式,每个元素都有一个next指定下个元素。
如果我们删除list中间元素,直接删掉就行,而slice则比较麻烦,要创建新空间给他们用。
优点比较灵活,但性能不佳,也很少用。
list的使用
import "container/list"
//声明lishi
var mylist list.List
//尾部插入
mylist.PushBack("go")
mylist.PushBack("php")
mylist.PushBack("java")
//正向遍历
for i := mylist.Front(); i != nil; i = i.Next() {
fmt.Println(i.Value)
}
fmt.Println()
//头部插入
mylist.PushFront("mysql")
//反向遍历
for i := mylist.Back(); i != nil; i = i.Prev() {
fmt.Println(i.Value)
}
输出
go
php
java
java
php
go
mysql
插入数据 需要遍历到element
var mylist list.List
//尾部插入
mylist.PushBack("go")
mylist.PushBack("php")
mylist.PushBack("java")
//取出element 再执行插入数据
i := mylist.Front()
for ; i != nil; i = i.Next() {
if i.Value == "php" {
break
}
}
mylist.InsertAfter("B", i)
mylist.InsertBefore("c", i)
for i := mylist.Front(); i != nil; i = i.Next() {
fmt.Println(i.Value)
}
输出
go
c
php
B
java
删除调用 mylist.Remove(i)
具体去源码调用。。
集合类型 1.数组 有长度限制,2.slice 动态数组,高性能,用起来方便。3.map 很常用,4list也能完成slice的需求,比较灵活。但性能不佳。