Go语言入门 - Go语言的基本数据结构 | 青训营

59 阅读12分钟

Go语言中有几种常用的数据结构,熟练掌握它们之后才可以在开发过程中灵活自如地运用它们。本文主要讲解了4种go语言中的基本数据类型:数组array、切片slice、映射map和结构体struct,包括了如何定义属于这些数据类型的变量以及访问这些数据类型的基本操作。

目录

  1. 数组 array
    1.1 如何定义一个数组
    1.2 如何在定义数组的时候给数组提供初始值
    1.3 如何获取数组的长度
    1.4 如何访问数组的元素
    1.5 如何截取数组的一部分
  2. 切片 slice
    2.1 如何声明一个切片
    2.2 如何在声明的时候初始化切片
    2.3 如何截取切片的单个元素
    2.4 如何截取切片的一部分
    2.5 如何获取切片的长度容量
    2.6 如何向切片中添加元素
  3. 映射 map
    3.1 如何定义一个映射
    3.2 如何遍历一个映射的键值
    3.3 通过键查找映射中对应的值
    3.4 如何从映射中删除一对键值
  4. 结构体 struct
    4.1 如何定义一个结构体
    4.2 如何访问一个结构体的成员

参考文档

1 数组 array

数组是用来存储具一定数目的同类型元素的数据结构。这里的元素类型可以是go语言中的原始类型(int, string, bool等)或者是其他数据结构(包括数组本身)。

1.1 如何定义一个数组

数组的定义包括数组的元素类型以及数组的长度。语法是var <name> [<length>]<type>。其中name是作为数组的变量名称,length是数组的长度,即期望最多能存储的元素的个数,type是元素的数据类型。下面这个例子定义了一个长度为10的、存储元素是64位整数的数组,且数组名称为arr

var arr [10]int64

1.2 如何在定义数组的时候给数组提供初始值

  • 如果像上面那行代码,在定义数组的时候不提供初始值,go语言会给每个元素赋一个默认值。数值型(int, float等)的数组默认值是0,布尔值的数组默认值是false,字符串数组默认值是空字符串""

  • 如果在定义数组时已经想确定其中一个或多个元素的值,则可以用以下方法对数组初始化。

var arr1 = [10]int64{1,2,3,4,5,6,7,8,9,10} // 完整提供每一个元素的值
fmt.Println(arr1) //[1 2 3 4 5 6 7 8 9 10]
var arr2 = [10]int64{1,2,3,4,5} //只提供开头部分元素的值,其余的元素赋默认值0
fmt.Println(arr2) //[1 2 3 4 5 0 0 0 0 0]
var arr3 = [10]int64{1:10,2:9,4:8,8:7} //为指定下标的元素赋值,其余的元素赋默认值0
fmt.Println(arr3) //[0 10 9 0 8 0 0 0 7 0]
arr4 := [5]int64{10,9,8,7,6} //使用简短形式的赋值操作符 :=
fmt.Println(arr4) //[10 9 8 7 6]
var arr5 = [...]int64{10,9,8,7,6} //省略数组长度,让编译器通过提供的元素个数确定长度
fmt.Println(arr5) //[10 9 8 7 6]

1.3 如何获取数组的长度

如果已经有一个数组,则可以用go语言原生的len函数获取这个数组的长度。如len(arr)将返回数组arr的长度。

1.4 如何访问数组的元素

通过方括号语法可以访问具有指定下标的元素,数组的下标从0开始,例如arr[0]访问的是arr数组中第1个元素,arr[1]访问的是arr数组中第2个元素,以此类推。

var arr = [10]int64{1,2,3,4,5,6,7,8,9,10}
fmt.Println(arr[0]) //1
fmt.Println(arr[1]) //2

1.5 如何截取数组的一部分

已有一个数组,截取它的一部分使用的语法是方括号内标明上界和下界,上下界之间用一个冒号分隔,即[lower-bound:upper-bound],截取的部分是包含第lower-bound + 1个元素到第upper-bound个元素的子序列。

在使用方括号下标这种语法时,也有一些简写形式,常见情形是 (1)只写出上界,省略下界,则默认上界取到数组中的最后一个元素 (2)只写出下界,省略上界,则默认上界是0,即从数组的第一个元素开始取。

var nums = [6]int{1,2,3,4,5,6}
fmt.Println(nums[0:3]) // [1 2 3]
fmt.Println(nums[3:]) // [4 5 6]
fmt.Println(nums[:2]) // [1 2]

2 切片 slice

切片是类似数组的序列化数据结构,但是它在定义的时候不一定需要指定长度(=容量),相反,一个切片可以在元素不断加入的过程中扩张自己的容量。

2.1 如何声明一个切片

声明一个切片时使用var关键字,且只需指定切片的类型。切片的类型通常表示为一个空的方括号对[]和一个基本类型复合而成的形式,如[]int表示一个内部元素是整数的切片。

var strSlice []string
var intSlice []int

注意:仅仅声明一个切片只会创建一个长度和容量均为0的空切片,此时其中没有元素,还需要使用make函数或append函数(马上将会提到)向切片中加入元素,例:

var intSlice []int
intSlice = make([]int, 3, 6)
fmt.Println(intSlice) // [0 0 0]

2.2 如何在声明的时候初始化切片

有三种方式:

  1. 使用make函数为一个切片变量分配内存空间,make函数的使用方法为:make(<type>,[length, capacity])。其中type是切片的类型;length是切片长度,即被初始化后的元素个数;capacity是切片的容量,即被初始化后在不扩容的情况下能存储的最大元素个数。
var nums []int = make([]int, 3, 6) //初始化3个元素赋默认值0,在不扩容的情况下最大能存储6个元素
fmt.Println(nums) // [0 0 0]
  1. 使用花括号{}语法在声明切片的时候提供切片元素的字面值。如下例所示。
var vegetable = []string{"tomatoes", "carrots", "potatoes"}
  1. 使用简短形式的赋值操作符:=,可以在第2种方式的基础上省略var关键字。
vegetable := []string{"tomatoes", "carrots", "potatoes"}

2.3 如何截取切片的单个元素

获取切片的单个元素的语法类似第一小节的数组,例如:

var vegetable = []string{"tomatoes", "carrots", "potatoes", "onions", "cabbage"}
fmt.Println(vegetable[1]) // carrots

2.4 如何截取切片的一部分

截取子切片的语法与截取子数组的语法完全一致,例如:

var nums = []int{1,2,3,4,5,6}
fmt.Println(nums[0:3]) // [1 2 3]
fmt.Println(nums[3:]) // [4 5 6]
fmt.Println(nums[:2]) // [1 2]

2.5 如何获取切片的长度、容量

像数组那样,我们可以使用len函数来获取切片的长度。除此之外,使用cap函数可以获取切片的容量。

var nums []int = make([]int, 3, 6)
fmt.Println(len(nums)) // 3
fmt.Println(cap(nums)) // 6

2.6 如何向切片中添加元素

  • append函数。 使用append函数可以向已有的切片中添加元素,用法是<slice> = append(<slice>, element1, element2, ...)。其中<slice>是切片变量名,element1, element2, ...可以是一个或任意多个元素值。
var nums []int = make([]int, 3, 6)
nums = append(nums, 4, 5, 6)
fmt.Printf("Slice: %v; length: %d; capacity: %d\n", nums, len(nums), cap(nums)) // Slice: [0 0 0 4 5 6]; length: 6; capacity: 6
nums = append(nums, 100)
fmt.Printf("Slice: %v; length: %d; capacity: %d\n", nums, len(nums), cap(nums)) // Slice: [0 0 0 4 5 6 100]; length: 7; capacity: 12
  • copy函数。用于将一个切片的所有元素完整复制到另一个切片里。注意语法是copy(<目标切片>, <源切片>)
var nums = []int{1,2,3,4,5,6,100}
var nums2 []int = make([]int, 7, 7)
copy(nums2, nums)
fmt.Println(nums2) //[1 2 3 4 5 6 100]

需要注意的是,当目标切片的长度(假设为N)小于源切片的长度时,只有源切片的最前面的N个元素会被复制到目标切片中。

var numbers = []int{1,2,3,4,5,6}
fmt.Printf("slice=%v; len=%d; cap=%d\n",numbers,len(numbers),cap(numbers)) //slice=[1 2 3 4 5 6]; len=6; cap=6
numbers1 := make([]int, 3, 3) //numbers1的长度小于numbers
copy(numbers1, numbers)
fmt.Printf("slice=%v; len=%d; cap=%d\n",numbers1, len(numbers1),cap(numbers1)) //slice=[1 2 3]; len=3; cap=3
//因为numbers1的长度是3,所以只复制了numbers的前3个元素

3. 映射 map

go语言中的映射是一个类似哈希表的结构。它的基本组成单元是一个索引键和一个值,通常称为一个键值对。由于go语言的映射对索引键实行无序存储,多次遍历同一个映射对象返回的键值对的顺序有可能会不一样。

3.1 如何定义一个映射

有两种方式:

  1. 使用make函数来开辟一段内存空间,并分配给一个映射变量。语法是make(map[<Key-Type>]<Value-Type>, [Capacity])。其中<Key-Type>是索引键的数据类型,<Value-Type>是索引键对应的值的数据类型,Capacity是映射的初始容量,即可以存储的键值对的数量。Capacity属于一个可选参数,当省略它的时候,make函数返回一个空的映射对象。
var capitals = make(map[string]string) //省略Capacity参数,创建一个空的映射
fmt.Printf("map=%v; len=%d; cap=%d\n", capitals, len(capitals), len(capitals)) //map=map[]; len=0; cap=0
capitals["China"] = "Beijing"
capitals["Japan"] = "Tokyo"
capitals["Russia"] = "Moscow"
fmt.Printf("map=%v; len=%d; cap=%d\n", capitals, len(capitals), len(capitals)) //map=map[China:Beijing Japan:Tokyo Russia:Moscow]; len=3; cap=3
  1. 使用一个映射字面值来给映射变量赋值。字面值以该映射的类型开头(如map[string][string]),并在花括号中指定所有的键值对。需留意的地方是最后一对键值对的末尾需要有一个逗号,或者你需要把右花括号与最后一个键值对放在同一行上
capitals := map[string]string{
  "China": "Beijing",
  "Japan": "Tokyo",
  "Russia": "Moscow", //此行末必须要有一个逗号
}
/* 另一种字面值写法,唯一不同的是最后一行 
capitals := map[string]string{
  "China": "Beijing",
  "Japan": "Tokyo",
  "Russia": "Moscow"}
*/
fmt.Printf("map=%v; len=%d; cap=%d\n", capitals, len(capitals), len(capitals)) //map=map[China:Beijing Japan:Tokyo Russia:Moscow]; len=3; cap=3

3.2 如何遍历一个映射的键、值

使用for循环和range关键字可以遍历一个映射的键值对。语法是key [,value] := range <map-variable> ,其中<map-variable>是要遍历的映射。当value变量省略时,range仅返回映射中的键;当提供value变量时,range会返回映射中的键、值对。

capitals := map[string]string{
  "China": "Beijing",
  "Japan": "Tokyo",
  "Russia": "Moscow",
}
/* 仅遍历键 */
for key:= range capitals{
  fmt.Println(key)
}
/*输出:
China
Japan
Russia
*/
/* 同时遍历键值对 */
for key, value := range capitals{ 
  fmt.Printf("key=%s, value=%s\n", key, value)
}
/*输出: 
key=China, value=Beijing
key=Japan, value=Tokyo
key=Russia, value=Moscow
*/

3.3 通过键查找映射中对应的值

可以使用方括号语法访问映射中对应一个键的值,如:

capitals := map[string]string{
  "China": "Beijing",
  "Japan": "Tokyo",
  "Russia": "Moscow",
}
fmt.Println(capitals["China"]) //Beijing

注意:当一个键在映射中不存在时,通过map[key]的方式仍然可以返回一个值,但是这是一个由映射值数据类型决定的默认零值。如映射值数据类型是string,则默认零值是空字符串""

capitals := map[string]string{
  "China": "Beijing",
  "Japan": "Tokyo",
  "Russia": "Moscow",
}
fmt.Println(capitals["India"] == "") //true

3.4 如何从映射中删除一对键、值

使用delete函数可以做到这一点。语法是delete(<map-variable>, <key>)。其中<map-variable>是要遍历的映射,key是要删除的键值对中的键。

capitals := map[string]string{
  "China": "Beijing",
  "Japan": "Tokyo",
  "Russia": "Moscow",
  "India": "New Delhi",
}
capitals = delete(capitals, "India")
fmt.Println(capitals) //map[China:Beijing Japan:Tokyo Russia:Moscow]

4. 结构体 struct

go语言中的结构体类似于面向对象编程语言中的“类”。s结构体可以用作一系列相同或不同类型数据的集合。

4.1 如何定义一个结构体

使用struct关键字可以定义一个新的结构体,语法是type <Name> struct {},其中<Name>是结构体的名称。花括号中包含结构体的成员名称及类型,例如

type Book struct {
    id int
    title string
    author string
    subject string
}

定义了一个名为Book的结构体,包含4个成员,分别是整数型的id和字符型的title,author,subject

4.2 如何访问一个结构体的成员

可以使用点号的方法,形式如结构体.成员名。如在上例中,如果定义了一个Book类型的结构体变量,访问它的title成员并给它写入值:

var book1 Book
fmt.Println(book1.title) //输出空字符串""
book1.title = "The Go Programming Language"
fmt.Println(book1.title) //输出"The Go Programming Language"

除直接使用结构体.成员名可以访问一个结构体内的成员外,我们也可以使用一个指向一个结构体变量的指针按照指针变量.成员名的形式访问结构体成员。

type Book struct {
    id int
    title string
    author string
    subject string
}
var book2 *Book = &Book{}
fmt.Println(book2.title) //输出空字符串""
book2.title = "The Go Programming Language"
fmt.Println(book2.title) //输出"The Go Programming Language"

参考文档

  1. "Go 语言数组",菜鸟教程。www.runoob.com/go/go-array…
  2. "Go 语言切片(Slice)",菜鸟教程。www.runoob.com/go/go-slice…
  3. "Go 语言Map(集合)",菜鸟教程。www.runoob.com/go/go-map.h…
  4. "Go语言结构体",菜鸟教程。www.runoob.com/go/go-struc…