数组切片和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的初始化为xx
array := [3]string{2: "xx"}

但是仅对某个下标初始化 很多会直接用赋值

    array := [3]string{}
    array[2] = "xx"

省略号帮我们自动定义长度,下面代码为例,array长度也是3
array := [...]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的需求,比较灵活。但性能不佳。

文档更新时间: 2023-04-05 22:39   作者:young