11-Go语言数组和切片-指趣学院

数组

  • 和C语言一样,Go语言中也有数组的概念, Go语言中的数组也是用于保存一组相同类型的数据
  • 和C语言一样,Go语言中的数组也分为一维数组多维数组

一维数组

  • 格式:var arr [元素个数]数据类型
    • 和C语言中数组不同, Go语言中数组定义之后就有默认的初始值
    • 默认初始值就是保存数据类型的默认值(零值)
    package main
    import "fmt"
    func main() {
        // 1.定义一个数组
        var arr [3]int
        // 2.打印数组
        fmt.Println(arr) //[0 0 0]
    
        // 1.定义一个数组
        var arr [3]bool
        // 2.打印数组
        fmt.Println(arr) //[false false  false]
    }
    
    • 和C语言一样,Go语言中的数组也提供了好几种初始化方式
    package main
    import "fmt"
    func main() {
          // 1.定义的同时完全初始化
          var arr1 [3]int = [3]int{1, 3, 5}
          // 2.打印数组
          fmt.Println(arr1) // [1 3 5]
    
          // 1.定义的同时部分初始化
          var arr4 [3]int = [3]int{8, 9}
          // 2.打印数组
          fmt.Println(arr4) // [8 9 0]
    
          // 1.定义的同时指定元素初始化
          var arr5 [3]int = [3]int{0:8, 2:9}
          // 2.打印数组
          fmt.Println(arr5) // [8 0 9]
    
          // 1.先定义再逐个初始化
          var arr3 [3]int
          arr3[0] = 1
          arr3[1] = 2
          arr3[2] = 3
          // 2.打印数组
          fmt.Println(arr3) // [1 2 3]
    }
    
    • 和C语言中的数组不同,Go语言中数组除了可以定义的同时初始化以外,还可以先定义再一次性初始化
    package main
    import "fmt"
    func main() {
          // 1.先定义再一次性初始化
          var arr2 [3]int
          arr2 = [3]int{2, 4, 6}
          // 2.打印数组
          fmt.Println(arr2) // [2 4 6]
    }
    
    • 和C语言一样,Go语言中如果定义数组的同时初始化,那么元素个数可以省略,但是必须使用...来替代
      • ...会根据初始化元素个数自动确定数组长度
    package main
    import "fmt"
    func main() {
      // 1.定义的同时完全初始化
      var arr1  = [...]int{1, 3, 5}
      // 2.打印数组
      fmt.Println(arr1) // [1 3 5]
    
      // 1.定义的同时指定元素初始化
      var arr2  = [...]int{6:5}
      // 2.打印数组
      fmt.Println(arr2) // [0 0 0 0 0 0 5]
    }
    
  • Go语言中数组的访问和使用方式和C语言一样都是通过数组名称[索引]的方式
package main
import "fmt"
func main() {
    arr := [...]int{1, 3, 5}
    // 使用数组, 往数组中存放数据
    arr[1] = 666
    // 访问数组, 从数组中获取数据
    fmt.Println(arr[0])
    fmt.Println(arr[1])
    fmt.Println(arr[2])
}
  • 遍历数组
    • Go语言中提供了两种遍历数组的方式, 一种是通过传统for循环遍历, 一种是通过for...range循环遍历
package main
import "fmt"
func main() {
    arr := [...]int{1, 3, 5}
    // 传统for循环遍历
    for i:=0; i<len(arr); i++{
        fmt.Println(i, arr[i])
    }
    // for...range循环遍历
    for i, v := range arr{
        fmt.Println(i, v)
    }
}
  • 数组注意点
    • Go语言中数组长度也是数据类型的一部分
    package main
    import "fmt"
    func main() {
        var arr1 [3]int
        var arr2 [3]int
        //var arr3 [2]int
        fmt.Println(arr1 == arr2) // true
        //fmt.Println(arr1 == arr3) // 编译报错, 不是相同类型不能比较
    }
    
    • 如果元素类型支持==、!=操作时,那么数组也支持此操作
    package main
    import "fmt"
    func main() {
        var arr1 [3]int = [3]int{1, 3, 5}
        var arr2 [3]int = [3]int{1, 3, 5}
        var arr3 [3]int = [3]int{2, 4, 6}
        // 首先会判断`数据类型`是否相同,如果相同会依次取出数组中`对应索引的元素`进行比较, 
        // 如果所有元素都相同返回true,否则返回false
        fmt.Println(arr1 == arr2) // true
        fmt.Println(arr1 == arr3) // false
     }
    
    • Go语言中的数组是值类型, 赋值和传参时会复制整个数组
    package main
    import "fmt"
    func main() {
      var arr1 [3]int = [3]int{1, 3, 5}
      var arr2 [3]int = arr1
      arr2[0] = 666
      fmt.Println(arr1) // [1 3 5]
      fmt.Println(arr2) // [666 3 5]
     }
    

二维数组

  • 用法和C语言数组一样, 只是创建的格式不同
  • 格式: [行数][列数]类型
  package main
  import "fmt"
  func main() {
    // 创建一个两行三列数组
    arr := [2][3]int{
        {1, 2, 3},
        {4, 5, 6}, //注意: 数组换行需要以逗号结尾
    }
    fmt.Println(arr)// [[1 2 3] [4 5 6]]
   }
  • 创建多维数组时只允许第一维度使用...
  • 格式: [...][列数]类型
package main
  import "fmt"
  func main() {
    // 创建一个两行三列数组
    arr := [...][3]int{
        {1, 2, 3},
        {4, 5, 6},
    }
    fmt.Println(arr)// [[1 2 3] [4 5 6]]
   }

切片

  • 无论是C语言中的数组还是Go语言中的数组,数组的长度一旦确定就不能改变, 但在实际开发中我们可能事先不能确定数组的长度, 为了解决这类问题Go语言中推出了一种新的数据类型切片
  • 切片可以简单的理解为长度可以变化的数组, 但是Go语言中的切片本质上是一个结构体
    • 切片源码
    type slice struct{
      array unsafe.Pointer // 指向底层数组指针
      len int // 切片长度(保存了多少个元素)
      cap int // 切片容量(可以保存多少个元素)
    }
    

  • 切片创建的三种方式
  • 方式一: 通过数组创建切片array[startIndex:endIndex]
    package main
    import "fmt"
    func main() {
        var arr = [5]int{1, 3, 5, 7, 9}
        // 从数组0下标开始取,一直取到2下标前面一个索引
        var sce = arr[0:2]
        fmt.Println(sce) // [1 3]
        // 切片len = 结束位置 - 开始位置
        fmt.Println(len(sce)) // 2 - 0 = 2
        fmt.Println(cap(sce)) // 5 - 0 = 5
        // 数组地址就是数组首元素的地址
        fmt.Printf("%p\n", &arr) // 0xc04200a330
        fmt.Printf("%p\n", &arr[0]) // 0xc04200a330
        // 切片地址就是数组中指定的开始元素的地址
        //  arr[0:2]开始地址为0, 所以就是arr[0]的地址
        fmt.Printf("%p\n", sce) // 0xc04200a330
    }
    
  • image.png
    package main
    import "fmt"
    func main() {
      var arr = [5]int{1, 3, 5, 7, 9}
      // 根据数组的索引片段创建切片
      var sce = arr[2:4]
      fmt.Println(sce) // [5 7]
      fmt.Println(len(sce)) // 4 - 2 = 2
      fmt.Println(cap(sce)) // 5 - 2 = 3
      fmt.Printf("%p\n", &arr[2]) // 0xc042076070
      fmt.Printf("%p\n", sce) // 0xc042076070
    }
    
  • 指定起始位置时有三种方式可以指定
    • 开始位置和结束位置都指定
    • 只指定开始位置或结束位置
    • 开始位置和结束位置都不指定
  package main
  import "fmt"
  func main() {
    var arr = [5]int{1, 3, 5, 7, 9}
    // 同时指定开始位置和结束位置
    var sce1 = arr[0:2]
    fmt.Println(sce1) // [1 3]

    // 只指定结束位置
    var sce3 = arr[:2]
    fmt.Println(sce3) // [1 3]

    // 只指定开始位置
    var sce2 = arr[0:]
    fmt.Println(sce2) // [1 3 5 7 9]

    // 都不指定
    var sce4 = arr[:]
    fmt.Println(sce4) // [1 3 5 7 9]
  }
  • 方式二: 通过make函数创建make(类型, 长度, 容量)
    • 内部会先创建一个数组, 然后让切片指向数组
    • 如果没有指定容量,那么容量和长度一样
 package main
 import "fmt"
 func main() {
    // 第一个参数: 指定切片数据类型
    // 第二个参数: 指定切片的长度
    // 第三个参数: 指定切片的容量
    var sce = make([]int, 3, 5)
    fmt.Println(sce) // [0 0 0]
    fmt.Println(len(sce)) // 3
    fmt.Println(cap(sce)) // 5
    /*
    内部实现原理
    var arr = [5]int{0, 0, 0}
    var sce = arr[0:3]
    */
 }
  • 方式三:通过Go提供的语法糖快速创建
    • 和创建数组一模一样, 但是不能指定长度
    • 通过该方式创建时, 切片的长度和容量相等
 package main
 import "fmt"
 func main() {
    var sce = []int{1, 3, 5}
    fmt.Println(sce) // [1 3 5]
    fmt.Println(len(sce)) // 3
    fmt.Println(cap(sce)) // 3
 }

  • 切片的使用
    • 切片的基本使用方式和数组一样, 可以通过切片名称[索引]方式操作切片
     package main
     import "fmt"
     func main() {
        var sce = []int{1, 3, 5}
        // 使用切片, 往切片中存放数据
        sce[1] = 666
        // 访问切片, 从切片中获取数据
        fmt.Println(sce) // [1 666 5]
     }
    
    • 和数组一样, 如果通过切片名称[索引]方式操作切片, 不能越界
      package main
      import "fmt"
      func main() {
         var sce = []int{1, 3, 5}
         // 编译报错, 越界
         sce[3] = 666
      }
    
    • 如果希望切片自动扩容,那么添加数据时必须使用append方法
      • append函数会在切片末尾添加一个元素, 并返回一个追加数据之后的切片
      • 利用append函数追加数据时,如果追加之后没有超出切片的容量,那么返回原来的切片, 如果追加之后超出了切片的容量,那么返回一个新的切片
      • append函数每次给切片扩容都会按照原有切片容量*2的方式扩容
      package main
      import "fmt"
      func main() {
       var sce = []int{1, 3, 5}
       fmt.Println("追加数据前:", sce) // [1 3 5]
       fmt.Println("追加数据前:", len(sce)) // 3
       fmt.Println("追加数据前:", cap(sce)) // 3
       fmt.Printf("追加数据前: %p\n", sce) // 0xc0420600a0
       // 第一个参数: 需要把数据追加到哪个切片中
       // 第二个参数: 需要追加的数据, 可以是一个或多个
       sce = append(sce, 666)
       fmt.Println("追加数据后:", sce) // [1 3 5 666]
       fmt.Println("追加数据后:", len(sce)) // 4
       fmt.Println("追加数据后:", cap(sce)) // 6
       fmt.Printf("追加数据前: %p\n", sce) // 0xc042076b60
      }
    
    • 除了append函数外,Go语言还提供了一个copy函数, 用于两个切片之间数据的快速拷贝
      • 格式: copy(目标切片, 源切片), 会将源切片中数据拷贝到目标切片中
       package main
       import "fmt"
       func main() {
      var sce1 = []int{1, 3, 5}
      var sce2 = make([]int, 5)
      fmt.Printf("赋值前:%p\n", sce1) // 0xc0420600a0
      fmt.Printf("赋值前:%p\n", sce2) // 0xc042076060
      // 将sce2的指向修改为sce1, 此时sce1和sce2底层指向同一个数组
      sce2 = sce1
      fmt.Printf("赋值后:%p\n", sce1) // 0xc0420600a0
      fmt.Printf("赋值后:%p\n", sce2) // 0xc0420600a0
      //copy(sce2, sce1)
      fmt.Println(sce1) // [1 3 5]
      fmt.Println(sce2) // [1 3 5]
      sce2[1] = 666
      fmt.Println(sce1) // [1 666 5]
      fmt.Println(sce2) // [1 666 5]
       }
    
       package main
       import "fmt"
       func main() {
      var sce1 = []int{1, 3, 5}
      var sce2 = make([]int, 5)
      fmt.Printf("赋值前:%p\n", sce1) // 0xc0420600a0
      fmt.Printf("赋值前:%p\n", sce2) // 0xc042076060
      // 将sce1中的数据拷贝到sce2中,, 此时sce1和sce2底层指向不同数组
      copy(sce2, sce1)
      fmt.Printf("赋值后:%p\n", sce1) // 0xc0420600a0
      fmt.Printf("赋值后:%p\n", sce2) // 0xc042076060
      //copy(sce2, sce1)
      fmt.Println(sce1) // [1 3 5]
      fmt.Println(sce2) // [1 3 5 0 0]
      sce2[1] = 666
      fmt.Println(sce1) // [1 3 5]
      fmt.Println(sce2) // [1 666 5 0 0]
       }
    
    • copy函数在拷贝数据时永远以小容量为准
       package main
       import "fmt"
       func main() {
      // 容量为3
      var sce1 = []int{1, 3, 5}
      // 容量为5
      var sce2 = make([]int, 5)
      fmt.Println("拷贝前:", sce2) // [0 0 0 0 0]
      // sce2容量足够, 会将sce1所有内容拷贝到sce2
      copy(sce2, sce1)
      fmt.Println("拷贝后:", sce2) // [1 3 5 0 0]
       }
    
       package main
       import "fmt"
       func main() {
      // 容量为3
      var sce1 = []int{1, 3, 5}
      // 容量为2
      var sce2 = make([]int, 2)
      fmt.Println("拷贝前:", sce2) // [0 0]
      // sce2容量不够, 会将sce1前2个元素拷贝到sce2中
      copy(sce2, sce1)
      fmt.Println("拷贝后:", sce2) // [1 3]
       }
    

  • 切片的注意点
    • 可以通过切片再次生成新的切片, 两个切片底层指向同一数组
      package main
      import "fmt"
      func main() {
          arr := [5]int{1, 3, 5, 7, 9}
          sce1 := arr[0:4]
          sce2 := sce1[0:3]
          fmt.Println(sce1) // [1 3 5 7]
          fmt.Println(sce2) // [1 3 5]
          // 由于底层指向同一数组, 所以修改sce2会影响sce1
          sce2[1] = 666
          fmt.Println(sce1) // [1 666 5 7]
          fmt.Println(sce2) // [1 666 5]
       }
    
    • 和数组不同, 切片只支持判断是否为nil, 不支持==、!=判断
    package main
    import "fmt"
    func main() {
        var arr1 [3]int = [3]int{1, 3, 5}
        var arr2 [3]int = [3]int{1, 3, 5}
        var arr3 [3]int = [3]int{2, 4, 6}
        // 首先会判断`数据类型`是否相同,如果相同会依次取出数组中`对应索引的元素`进行比较, 
        // 如果所有元素都相同返回true,否则返回false
        fmt.Println(arr1 == arr2) // true
        fmt.Println(arr1 == arr3) // false
    
        sce1 := []int{1, 3, 5}
        sce2 := []int{1, 3, 5}
        //fmt.Println(sce1 == sce2) // 编译报错
        fmt.Println(sce1 != nil) // true
        fmt.Println(sce2 == nil) // false
     }
    
    • 只声明当没有被创建的切片是不能使用的
    package main
    import "fmt"
    func main() {
      // 数组声明后就可以直接使用, 声明时就会开辟存储空间
      var arr [3]int
      arr[0] = 2
      arr[1] = 4
      arr[2] = 6
      fmt.Println(arr) // [2 4 6]
    
      // 切片声明后不能直接使用, 只有通过make或语法糖创建之后才会开辟空间,才能使用
      var sce []int
      sce[0] = 2 // 编译报错
      sce[1] = 4
      sce[2] = 6
      fmt.Println(sce)
     }
    
    • 字符串的底层是[]byte数组, 所以字符也支持切片相关操作
    package main
    import "fmt"
    func main() {
      str := "abcdefg"
      // 通过字符串生成切片
      sce1 := str[3:]
      fmt.Println(sce1) // defg
    
      sce2 := make([]byte, 10)
      // 第二个参数只能是slice或者是数组
      // 将字符串拷贝到切片中
      copy(sce2, str)
      fmt.Println(sce2) //[97 98 99 100 101 102 103 0 0 0]
     }
    
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 156,265评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,274评论 1 288
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,087评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,479评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,782评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,218评论 1 207
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,594评论 2 309
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,316评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,955评论 1 237
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,274评论 2 240
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,803评论 1 255
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,177评论 2 250
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,732评论 3 229
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,953评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,687评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,263评论 2 267
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,189评论 2 258

推荐阅读更多精彩内容

  • 第四天 数组【悟空教程】 第04天 Java基础 第1章数组 1.1数组概念 软件的基本功能是处理数据,而在处理数...
    Java帮帮阅读 1,528评论 0 9
  • SwiftDay011.MySwiftimport UIKitprintln("Hello Swift!")var...
    smile丽语阅读 3,805评论 0 6
  • 模式与用户的区别:比喻为一个人(用户)与他所拥有的所有的钱(模式),他的钱是由那些一块的,十块的,100块的之类的...
    我是一个好人吗阅读 238评论 0 0
  • 一、我们打完了吧 还有两天假期就要结束了,米妈妈要带着外公外婆回家,我也要带着大米回家了,本以为会有个快乐的结局,...
    燕山书童阅读 288评论 0 0
  • 11点半已经到了饭点,我们都打算去吃饭了,看霍琪准备好装备要出发的样子,“要吃饭了,这是去哪啊”?“3#加氢刚才打...
    miss敏敏阅读 131评论 0 0