Go语言学习(数组、切片、map)

数组

什么是数组

数组的特点:

  • 长度必须是确定的,如果不确定,就不是数组,大小不可以改变;

  • 数组元素必须是相同类型的,不能多个不同类型的混合;

  • 数组中的元素类型,可以是学的所有类型,int、float、string、bool、array、slice、map等

数组的声明

package main

import "fmt"

func main() {
    // 数组的定义:[大小size]类型
    // 定义了一组数的集合,表示最多可以保存size个数
    var arr [5]int

    // 给数组赋值,所有数组的下标都是从0开始的
    arr[0] = 100
    arr[1] = 200
    arr[2] = 300
    arr[3] = 400
    arr[4] = 500

    // 打印数组
    fmt.Println(arr) // [100 200 300 400 500]

    // 取出数组第二个数据
    fmt.Println(arr[1]) // 200

    // 数组中常用方法,len()获取数组的长度,cap()获取数组的容量
    fmt.Printf("数组的长度:%d\n", len(arr)) // 5
    fmt.Printf("数组的容量:%d\n", cap(arr)) // 5

    // 修改数组第二个数据的值
    arr[1] = 20
    fmt.Println(arr) // [100 20 300 400 500]
}

初始化数组的几种方式

package main

import "fmt"

func main() {
    //    1、在定义数组的时候直接初始化
    var arr1 = [5]int{1, 2, 3, 4, 5} // [1 2 3 4 5]
    fmt.Println(arr1)

    // 2、快速初始化 :=
    arr2 := [5]int{1, 2, 3, 4, 5} // [1 2 3 4 5]
    fmt.Println(arr2)

    // 特殊的地方
    // 如果用户不知需要存放多少个数据,可以使用 ... 代表数组长度
    // Go编译器会自动根据存放数据的长度给 ... 赋值,自动推导长度
    // 注意:这样使用的数组不是无限长,也是固定大小的,长度取决于存放的数据个数
    var arr3 = [...]int{1, 2, 3, 4, 5, 6, 7, 8}
    fmt.Println(arr3)      // [1 2 3 4 5 6 7 8]
    fmt.Println(len(arr3)) // 8

    // 数组的默认值为0
    // 如果只想给指定的index下标赋值,可以{index:值}
    var arr4 [6]int
    arr4 = [6]int{0: 1, 5: 6}
    fmt.Println(arr4) // [1 0 0 0 0 6]
}

遍历数组元素

  1. 直接通过下标获取元素,arr[index];

  1. 可以使用for循环,结合数组下标进行遍历;

  1. 使用 for range

package main

import "fmt"

func main() {
    var arr = [5]int{1, 2, 3, 4, 5}
    fmt.Println(arr[0])
    fmt.Println(arr[1])
    fmt.Println(arr[2])
    fmt.Println(arr[3])
    fmt.Println(arr[4])
    // 错误:invalid argument: index 5 out of bounds [0:5] 数组下标越界
    // 数组长度只有5,不可能取出第六个元素
    // fmt.Println(arr[5])
    fmt.Println("---------------------")

    for i := 0; i < len(arr); i++ {
        fmt.Println(arr[i])
    }
    fmt.Println("----------------------")

    // 快捷方式 数组.for
    //    循环数组、切片,经常会使用 for   range
    // 就是将数组进行自动迭代,返回index、value
    // 注意:如果只接受一个值,则返回的是数组下标
    // 注意:如果接受两个值,则返回的是数组下标和下标对应的值
    for index, value := range arr {
        fmt.Printf("数组下标:%d 下标对应的值:%d\n", index, value)
    }
    for _, value := range arr {
        fmt.Printf("下标对应的值:%d\n", value)
    }
}

数组是值类型

数组是值类型:所有赋值的对象被修改值后,不会影响原来对象值

package main

import "fmt"

func main() {
    arr1 := [5]int{1, 2, 3, 4, 5}
    arr2 := [2]string{"str1", "str2"}
    // 打印数组类型 [size]type
    fmt.Printf("%T\n", arr1) // [5]int
    fmt.Printf("%T\n", arr2) // [2]string

    // 数组的值传递和int等基本类型是一致的
    arr3 := arr1
    fmt.Println(arr3) // [1 2 3 4 5]
    // 修改arr3观察arr1是否也变化
    arr3[0] = 10
    fmt.Println(arr1) // [1 2 3 4 5] 
    fmt.Println(arr3) // [10 2 3 4 5] 所以数组是值传递,arr3拷贝了一个新的内存空间
}

数组的冒泡排序

package main

import "fmt"

func main() {
    var arr = [10]int{5, 4, 7, 0, 8, 3, 2, 1, 6, 9}
    for i := 0; i < len(arr)-1; i++ {
        for j := 0; j < len(arr)-i-1; j++ {
            if arr[j] > arr[j+1] {
                arr[j], arr[j+1] = arr[j+1], arr[j]
            }
        }
    }
    fmt.Println(arr)
}

多维数组

一维数组: 线性的,一组数

二维数组: 表格性的,数组套数组

三维数组: 立体空间性的,数组套数组套数组

package main

import "fmt"

func main() {
    // 二维数组定义
    arr := [3][4]int{
        {1, 2, 3, 4},
        {5, 6, 7, 8},
        {9, 10, 11, 12},
    }
    // 二维数组相当于一维数组存放的是一个数组
    fmt.Println(arr[0])    // [1 2 3 4]
    fmt.Println(arr[0][0]) // 1

    // 二维数组遍历
    for i := 0; i < len(arr); i++ {
        for j := 0; j < len(arr[i]); j++ {
            fmt.Println(arr[i][j])
        }
    }
    for i, v := range arr {
        fmt.Println(i, v)
    } 
    // 0 [1 2 3 4]
    // 1 [5 6 7 8]
    // 2 [9 10 11 12]
}

切片

什么是切片

Go语言中,切片是对数组的一个抽象

Go数组长度是不能改变的,在许多特定的场景下就不太适用。因此Go提供了一种灵活且功能强悍的内置类型切片(“动态数组”)。

与数组相比,切片的长度是不固定的,可以追加元素,在追加时可能会使切片的容量增大。

切片是一种方便、灵活且强大的包装器,切片本身没有数据,只是对现有数组的引用。

在定义切片时,不需要设定长度,比较自由。

从概念上讲,slice像是一个结构体,其中包含:

  1. 指针:指向数组中slice指定的开始位置

  1. 长度:即slice的长度

  1. 最大长度:即slice开始位置到数组最后位置的长度

定义切片

package main

import "fmt"

func main() {
    arr := [5]int{1, 2, 3, 4, 5} // 数组定长
    fmt.Println(arr)

    var s1 []int    // 切片变长,长度是可变的
    fmt.Println(s1) // []
    // 切片的空判断 初始的切片中,默认是nil
    if s1 == nil {
        fmt.Println("切片是空的") // 切片是空的
    }

    s2 := []int{1, 2, 3, 4, 5}
    fmt.Printf("%T %T\n", arr, s2) // [5]int []int
    fmt.Println(s2) // [1 2 3 4 5]
    fmt.Println(s2[1]) // 2
}

make创建切片

package main

import "fmt"

func main() {
    // make([]Type, length, capacity) 创建一个切片,长度,容量
    s1 := make([]int, 5, 10)
    fmt.Println(s1)                                   // [0 0 0 0 0]
    fmt.Printf("长度:%d 容量:%d\n", len(s1), cap(s1)) // 长度:5 容量:10

    // 思考:容量为10,长度为5 的切片可以给第六个数据元素赋值吗?
    s1[0] = 10
    s1[6] = 70 // 运行时错误 runtime error: index out of range [6] with length 5 
    // 切片的底层还是数组,所以直接赋值会报错的
}

切片扩容

package main

import "fmt"

func main() {
    s1 := make([]int, 0, 5)
    fmt.Println(s1) // []
    // append() 切片扩容
    s1 = append(s1, 1, 2)
    fmt.Println(s1)                                   // [1 2]
    fmt.Printf("长度:%d 容量:%d\n", len(s1), cap(s1)) // 长度:2 容量:5

    // 思考:容量只有5个,可以存放超过5个的吗?
    s1 = append(s1, 3, 4, 5, 6, 7, 8)
    fmt.Println(s1)                                   // [1 2 3 4 5 6 7 8]
    fmt.Printf("长度:%d 容量:%d\n", len(s1), cap(s1)) // 长度:8 容量:10
    // 答案是可以的,切片会自动扩容的

    // 切片可以追加另一个切片
    s2 := []int{9, 10, 11, 12, 13}
    // slice...  解构,和javascript中的结构差不多,可以直接获取slice中的所有元素
    s1 = append(s1, s2...)
    fmt.Println(s1)                                   // [1 2 3 4 5 6 7 8 9 10 11 12 13]
    fmt.Printf("长度:%d 容量:%d\n", len(s1), cap(s1)) // 长度:13 容量:20
}

遍历切片

package main

import "fmt"

func main() {
    s1 := []int{1, 2, 3, 4, 5}
    for i := 0; i < len(s1); i++ {
        fmt.Println(s1[i])
    }
    for i := range s1 {
        fmt.Println(s1[i])
    }
}

扩容的内存分析

  1. 每个切片引用了一个底层数组;

  1. 切片本身不存储任何数据,都是底层的数组存储的,所以修改了切片也就是修改了底层数组的数据;

  1. 向切片中添加数据的时候,如果没有超过容量,直接添加;如果超过了,就会自动扩容,成倍的增加;

  1. 切片扩容后,会指向一个新的底层数组。

package main

import "fmt"

func main() {
    s1 := []int{1, 2, 3, 4}
    fmt.Println(s1) // [1 2 3 4]
    fmt.Printf("len:%d cap:%d\n", len(s1), cap(s1)) // len:4 cap:4
    fmt.Printf("%p\n", s1)  // 0xc0000121a0
    fmt.Println("-------------------------------")
    s1 = append(s1, 5, 6, 7)
    fmt.Println(s1)   // [1 2 3 4 5 6 7]
    fmt.Printf("len:%d cap:%d\n", len(s1), cap(s1)) // len:7 cap:8
    fmt.Printf("%p\n", s1)   //  0xc00001a380
    fmt.Println("-------------------------------")
    s1 = append(s1, 8, 9)
    fmt.Println(s1)  //  [1 2 3 4 5 6 7 8 9]
    fmt.Printf("len:%d cap:%d\n", len(s1), cap(s1)) // len:9 cap:16
    fmt.Printf("%p\n", s1)   //  0xc00010a080
}

手动模拟底层扩容 copy方法

package main

import "fmt"

func main() {
    s1 := []int{1, 2, 3, 4}
    fmt.Printf("len=%d, cap=%d, slice=%v\n", len(s1), cap(s1), s1) 
    // len=4, cap=4, slice=[1 2 3 4]

    // 使用make方法创建切片扩容
    s2 := make([]int, len(s1), cap(s1)*2)
    // func copy(dst, src []Type) int
    // 将原来底层数组的值拷贝到新的数组中
    copy(s2, s1)
    fmt.Printf("len=%d, cap=%d, slice=%v\n", len(s2), cap(s2), s2) 
    // len=4, cap=8, slice=[1 2 3 4]
}

使用数组创建切片

package main

import "fmt"

func main() {
    arr := [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
    // 通过数组来创建切片 数组的截取 [start, end)
    s1 := arr[:5]
    s2 := arr[3:7]
    s3 := arr[5:]
    s4 := arr[:]

    fmt.Println(s1) // [1 2 3 4 5]
    fmt.Println(s2) // [4 5 6 7]
    fmt.Println(s3) // [6 7 8 9 10]
    fmt.Println(s4) // [1 2 3 4 5 6 7 8 9 10]

    // 查看切片的长度和容量
    fmt.Printf("len: %d cap: %d\n", len(s1), cap(s1)) // len: 5 cap: 10
    fmt.Printf("len: %d cap: %d\n", len(s2), cap(s2)) // len: 4 cap: 7
    fmt.Printf("len: %d cap: %d\n", len(s3), cap(s3)) // len: 5 cap: 5
    fmt.Printf("len: %d cap: %d\n", len(s4), cap(s4)) // len: 10 cap: 10

    // 查看数组和切片的内存地址
    fmt.Printf("%p\n", &arr) // 0xc000014690
    fmt.Printf("%p\n", s1)   // 0xc000014690 指向了原数组

    fmt.Printf("%p\n", s2)      // 0xc0000146a8 指向了开始截断的下标
    fmt.Printf("%p\n", &arr[3]) // 0xc0000146a8

    fmt.Printf("%p\n", s3)      // 0xc0000146b8 指向了开始截断的下标
    fmt.Printf("%p\n", &arr[5]) // 0xc0000146b8

    fmt.Printf("%p\n", s4) // 0xc000014690 指向了原数组
    fmt.Println("----------------------------------")
    // 修改数组值,切片也随之变化
    arr[0] = 10
    fmt.Println(arr) // [10 2 3 4 5 6 7 8 9 10]
    fmt.Println(s1)  // [10 2 3 4 5]
    fmt.Println(s2)  // [4 5 6 7]
    fmt.Println(s3)  // [6 7 8 9 10]
    fmt.Println(s4)  // [10 2 3 4 5 6 7 8 9 10]
    // 修改切片的数据,数组的数据也随之变化(本质:修改的是底层数组)
    s2[2] = 20
    fmt.Println(arr) // [10 2 3 4 5 20 7 8 9 10]
    fmt.Println(s1)  // [10 2 3 4 5]
    fmt.Println(s2)  // [4 5 20 7]
    fmt.Println(s3)  // [20 7 8 9 10]
    fmt.Println(s4)  // [10 2 3 4 5 20 7 8 9 10]
    fmt.Println("----------------------------------")
    // 切片扩容
    // 如果在切片容量内添加数据,会导致原数组数据发生修改
    //s1 = append(s1, 11, 12, 13, 14, 15)
    //fmt.Println(s1) // [10 2 3 4 5 11 12 13 14 15]
    //fmt.Println(arr) // [10 2 3 4 5 11 12 13 14 15]
    // 只有在扩容之后,才会发生拷贝,指向新数组
    s1 = append(s1, 11, 12, 13, 14, 15, 16)
    fmt.Println(s1)  // [10 2 3 4 5 11 12 13 14 15 16]
    fmt.Println(arr) // [10 2 3 4 5 20 7 8 9 10]
    // 此时,切片地址和原数组地址不一样了
    fmt.Printf("%p\n", s1) // 0xc0000240a0
    fmt.Printf("%p\n", &arr) // 0xc000014690
}

切片是引用类型

package main

import "fmt"

func main() {
    // 数组是值类型
    arr1 := [5]int{1, 2, 3, 4, 5}
    arr2 := arr1
    arr1[0] = 10
    fmt.Println(arr1) // [10 2 3 4 5]
    fmt.Println(arr2) // [1 2 3 4 5]

    // 切片是引用类型
    s1 := []int{1, 2, 3, 4, 5}
    s2 := s1
    s1[0] = 10
    fmt.Println(s1) // [10 2 3 4 5]
    fmt.Println(s2) // [10 2 3 4 5]
    // s1,s2指向了同一个地址
    fmt.Printf("%p %p\n", s1, s2) // 0xc00000e3f0 0xc00000e3f0
}

深拷贝、浅拷贝

  1. 深拷贝:拷贝的是数据本身

  • 值类型的数据,默认都是深拷贝,array、int、float、string、bool、struct...

  1. 浅拷贝:拷贝的是数据的地址,会造成多个变量指向同一块内存

  • 引用类型的数据:slice、map

切片实现深拷贝 copy
package main

import "fmt"

func main() {
    // 切片实现深拷贝 将原来切片中的数据拷贝到新切片中
    // 第一种方法
    s1 := []int{1, 2, 3, 4, 5}
    s2 := make([]int, 0) // len: 0 cap: 0
    for i := 0; i < len(s1); i++ {
        s2 = append(s2, s1[i])
    }
    fmt.Println(s1, s2) // [1 2 3 4 5] [1 2 3 4 5]
    s1[0] = 10
    fmt.Println(s1, s2) // [10 2 3 4 5] [1 2 3 4 5]

    // 第二种方法 copy
    s3 := []int{5, 6, 7, 8}
    fmt.Println(s2, s3) // [1 2 3 4 5] [5 6 7 8]
    // 将s2中的元素拷贝到s3中
    copy(s3, s2)
    fmt.Println(s2, s3) // [1 2 3 4 5] [1 2 3 4]
}

函数中参数传递问题

按照数据的存储特点来分

  • 值类型的数据:操作的是数据本身,int 、string、bool、float64、array...

  • 引用类型的数据:操作的是数据的地址, slice、map、chan....

值传递 引用传递
package main

import "fmt"

func main() {
    arr1 := [5]int{1, 2, 3, 4, 5}
    fmt.Printf("start arr1: %d\n", arr1) // start arr1: [1 2 3 4 5]
    update(arr1)
    fmt.Printf("end arr1: %d\n", arr1) // end arr1: [1 2 3 4 5]

    fmt.Println("----------------------------------")

    s1 := []int{1, 2, 3, 4, 5}
    fmt.Printf("start s1: %d\n", s1) // start s1: [1 2 3 4 5]
    update2(s1)
    fmt.Printf("end s1: %d\n", s1) // end s1: [10 2 3 4 5]
}

// 数组是值类型的
func update(arr [5]int) {
    fmt.Printf("start arr: %d\n", arr) // start arr: [1 2 3 4 5]
    arr[0] = 10
    fmt.Printf("end arr: %d\n", arr) // end arr: [10 2 3 4 5]
}

// 切片是引用类型的
func update2(s []int) {
    fmt.Printf("start s: %d\n", s) // start s: [1 2 3 4 5]
    s[0] = 10
    fmt.Printf("end s: %d\n", s) // end s: [10 2 3 4 5]
}

注意:在使用函数的时候,一定要特别注意参数的问题。如果是值类型的,很多传递都会是无效的。值类型的参数,如果想用函数来进行修改对应值,需要使用指针,指针变量->指向原来变量的值。

map

map的定义

Map是一种无序的键值对的集合。

  • 无序:map[key]

  • 键值对:key-value

Map最重要的是通过key快速检索数据,key类似于索引,指向数据的值。

Map是一种集合,所以可以像迭代数组和切片一样迭代数组。

但是,Map是无序的,无法决定返回的顺序。Map也是引用类型。

package main

import "fmt"

// map 集合,保存数据的一种结构
func main() {
    // 创建map变量,数据类型是map[key]value
    var map1 map[int]string // 只是声明,但没有初始化,默认为nil,不能使用
    if map1 == nil {
        fmt.Println("map1==nil") // map1==nil
    }
    // 更多是使用make方法创建map变量
    var map2 = make(map[string]string)
    fmt.Println(map1) // map[]
    fmt.Println(map2) // map[]
    // 声明的时候初始化数据,map[string]int {key:value, key:value, ...}
    var map3 = map[string]int{"Go": 100, "Java": 200, "Vue": 300}
    fmt.Println(map3)        // map[Go:100 Java:200 Vue:300]
    fmt.Printf("%T\n", map3) // map[string]int
}

map的使用

  • 创建并初始化map

  • map[key] = value 将value赋值给对应map的key

  • 判断key是否存在,value, ok := map[key]

  • 删除map中的数据,delete(map, key)

  • 如果key不存在,新增数据 map[key] = value

  • 如果key存在,修改数据 map[key] = value

  • 查看map的大小,len(map)

package main

import "fmt"

func main() {
    var map1 map[int]string // 只是声明,还没有初始化,不可使用
    // 初始化map1,可赋值使用
    map1 = make(map[int]string)
    // 一个key对应一个value,如果key重复,就会覆盖
    map1[100] = "Go"
    map1[100] = "Vue"
    map1[200] = "Java"
    fmt.Println(map1) // map[100:Vue 200:Java]
    // 获取数据 map[key]
    fmt.Println(map1[200]) // Java
    fmt.Println(map1[1])   // 不存在,默认值 string ""
    // 在map中因为没有index下标,但取值需要判断key是否存在
    // value = map[key] 隐藏的返回值 ok-idiom 可选参数
    value, ok := map1[1]
    if ok {
        fmt.Println("map key 存在,value:", value)
    } else {
        fmt.Println("map key 不存在") // map key 不存在
    }
    // 如果key存在,修改数据
    map1[200] = "Spring"
    fmt.Println(map1) // map[100:Vue 200:Spring]
    // 如果key不存在,新增数据
    map1[300] = "Python"
    fmt.Println(map1) // map[100:Vue 200:Spring 300:Python]
    // map中的数据删除,delete
    delete(map1, 300)
    fmt.Println(map1) // map[100:Vue 200:Spring]

    // map的大小
    fmt.Println(len(map1)) // 2
}

map的遍历

  • map是无序的,可能每次循环打印出来的结果不一样,不能通过index来获取,只能通过key

  • map的长度不是固定的,是引用类型

  • len可以用于查询map中数据的数量,但cap无法使用

  • map的key可以是 布尔类型、整数、浮点数、字符串

package main

import "fmt"

func main() {
    var map1 = map[string]int{"Go": 99, "Java": 52, "Vue": 66, "C": 14}
    // map的循环遍历,只能通过for   range
    for key, value := range map1 {
        fmt.Println(key, value)
    }
}

map结合slice切片使用

需求:

  • 创建map存储用户信息,name、age、gender、addr

  • 将map中的每条数据存入slice中

  • 打印这些数据

package main

import "fmt"

func main() {
    user1 := make(map[string]string)
    user1["name"] = "张三"
    user1["age"] = "20"
    user1["gender"] = "男"
    user1["addr"] = "安徽"

    user2 := make(map[string]string)
    user2["name"] = "李四"
    user2["age"] = "25"
    user2["gender"] = "女"
    user2["addr"] = "江苏"

    var user3 = map[string]string{"name": "王五", "age": "18", "gender": "男", "addr": "北京"}

    // 将map数据存入切片中,以便后续使用
    userDatas := make([]map[string]string, 0, 3)
    userDatas = append(userDatas, user1)
    userDatas = append(userDatas, user2)
    userDatas = append(userDatas, user3)
    // [map[addr:安徽 age:20 gender:男 name:张三] map[addr:江苏 age:25 gender:女 name:李四] map[addr:北京 age:18 gender:男 name:王五]]
    fmt.Println(userDatas)

    for _, user := range userDatas {
        // 张三 20 男 安徽
        // 李四 25 女 江苏
        // 王五 18 男 北京
        fmt.Println(user["name"], user["age"], user["gender"], user["addr"])
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值