Go语言数组深度解析:从基础到高级应用全指南

53 阅读5分钟

Go语言数组深度解析:从基础到高级应用全指南

在Go语言中,数组是一个基本的数据结构,虽然它的定义和用法看似简单,但其应用却非常广泛。从初学者到资深开发者,掌握数组的各种技巧和细节无疑对提升编码能力至关重要。本文将全面解析Go语言数组的各个方面,从基础的声明和访问,到高级的数组操作、性能优化及实际应用。无论你是刚刚接触Go,还是希望深入了解数组在复杂场景下的使用方法,本指南都会为你提供清晰的思路和实用的技巧。

Go语言中的数组是一种固定长度、元素类型一致的集合。虽然数组的使用简单,但其在实际开发中的应用却常常被忽视。本文从Go数组的基础知识开始,逐步深入到数组的高级应用,包括如何访问、遍历、复制数组,以及如何避免数组越界错误等常见问题。同时,还探讨了如何使用复合字面值初始化数组,如何使用range进行高效遍历,如何优化数组传参性能,并通过实例演示数组在多维数据结构(如棋盘等)中的应用。通过本文,你将全面了解Go语言数组的强大功能,并能够灵活应用于不同的开发场景中。

数组

数组

  • 数组是一种固定长度且有序的元素集合。
var planets [8]string

访问数组元素

  • 数组中的每个元素都可以通过[ ]和一个从0开始的索引进行访问。
package main

import "fmt"

func main() {
 var planets [8]string

 planets[0] = "Mercury"
 planets[1] = "Venus"
 planets[2] = "Earth"

 earth := planets[2]
 fmt.Println(earth)
}
  • 数组的长度可由内置函数len来确定。
package main

import "fmt"

func main() {
 var planets [8]string

 planets[0] = "Mercury"
 planets[1] = "Venus"
 planets[2] = "Earth"

 earth := planets[2]
 fmt.Println(earth)
  
  fmt.Println(len(planets))
  fmt.Println(planets[3] == "")
}
  • 在声明数组时,未被赋值元素的值是对应类型的零值。

小测试

  1. 如何才能访问planets数组的第一个元素?
  2. 对新创建的整数数组来说,其元素的默认值是什么?

数组越界

  • Go编译器在检测到对越界元素的访问时会报错
package main

func main() {
  var plantes [8]string
  
  planets[8] = "Pluto" // 报错
  pluto := planets[8// 报错
}
  • 如果Go编译器在编译时未能发现越界错误,那么程序在运行时会出现panic
  • Panic会导致程序崩溃
package main

import "fmt"

func main() {
  var planets [8]string
  
  i := 8
  planets[i] = "Pluto"
  pluto := planets[i]
  
  fmt.Println(pluto)
}

小测试

  • 访问planets[11]会导致编译时错误还是运行时 panic?

使用复合字面值初始化数组

  • 复合字面值(composite literal)是一种给复合类型初始化的紧凑语法。

  • Go的复合字面值语法允许我们只用一步就完成数组声明和数组初始化两步操作:

    • dwarfs := [5]string{"Ceres", "Pluto", "Haumea", "Makemake", "Eris"}
  • 可以在复合字面值里使用 … 作为数组的长度,这样Go编译器会为你算出数组的元素数量

package main

import "fmt"

func main() {
  planets := [...]string{
    "Mercury",
    "Venus",
    "Earth",
    "Mars",
    "Jupiter",
    "Saturn",
    "Uranus",
    "Neptune",
  }
  
  fmt.Println(planets)
}
  • 无论哪种方式,数组的长度都是固定的。

遍历数组

  • for循环
package main

import "fmt"

func main() {
  dwarfs := [5]string{"Ceres""Pluto""Haumea""Makemake""Eris"}
  
  for i := 0; i < len(dwarfs); i++ {
    dwarf := dwarfs[i]
    fmt.Println(i, dwarf)
  }
}
  • 使用range
package main

import "fmt"

func main() {
  dwarfs := [5]string{"Ceres""Pluto""Haumea""Makemake""Eris"}
  
  for i, dwarf := range dwarfs {
    fmt.Println(i, dwarf)
  }
}

小测试

  1. 使用range关键字遍历数组可避免哪些错误?
  2. 什么情况下,使用for循环比range更合适?

数组的复制

  • 无论数组赋值给新的变量还是将它传递给函数,都会产生一个完整的数组副本。
package main

import "fmt"

func main() {
  planets := [...]string{
    "Mercury",
    "Venus",
    "Earth",
    "Mars",
    "Jupiter",
    "Saturn",
    "Uranus",
    "Neptune",
  }
  
  planetsMarkII := planets
  
  planets[2] = "whoops"
  
  fmt.Println(planets)
  fmt.Println(planetsMarkII)
}
  • 数组也是一种值,函数通过值传递来接受参数。所以数组作为函数的参数就非常低效。
package main

import "fmt"

// terraform accomplishes nothing
func terraform(planets [8]string) {
  for i := range planets {
    planets[i] = "New " + planets[i]
  }
}

func main() {
  planets := [...]string{
    "Mercury",
    "Venus",
    "Earth",
    "Mars",
    "Jupiter",
    "Saturn",
    "Uranus",
    "Neptune",
  }
 
  terraform(planets)
  fmt.Println(planets)
}
  • 数组的长度也是数组类型的一部分。

    • 尝试将长度不符的数组作为参数传递,将会报错。
  • 函数一般使用 slice 而不是数组作为参数。

数组的数组

package main

import "fmt"

func main() {
  var board [8][8]string
  
  board[0][0] = "r"
  board[0][7] = "r"
  
  for column := range board[1] {
    board[1][column] = "p"
  }
  
  fmt.Print(board)
}

小测试

  • 如果我们要设计一个数独游戏程序,那么该如何声明9 x 9的整数网格?

作业题

package main

import "fmt"

func main() {
  var board [8][8]string
  
  board[0][0] = "r"
  board[0][7] = "r"
  
  for column := range board[1] {
    board[1][column] = "p"
  }
  
  fmt.Print(board)
}
  1. 扩展上面例子的程序,使用字符kqrbnp表示上方的黑棋,字符KQRBNP表示下方的白棋,然后在棋子的起始位置是打印出所有的棋子。
  2. 编写一个能够美观的打印出整个棋盘的函数
  3. 使用[8][8]rune数组而不是字符串来表示棋

总结

Go语言的数组是一个非常强大的数据结构,理解它的工作原理并掌握各种操作技巧对于提升编码效率和代码质量至关重要。从基础的数组声明、元素访问到复杂的数组复制、越界检查和性能优化,掌握这些知识将帮助开发者编写更加高效、健壮的程序。尤其是在处理多维数组和更复杂的数据结构时,Go的数组提供了丰富的灵活性和强大的功能。希望通过本文的深入解析,能够帮助你在Go编程的道路上走得更远,真正发挥Go数组的强大优势。