阅读本文前,建议已掌握基本C语言语法及规范,本文将更加侧重Go语言和C系列语言的区别以达到快速上手的目的
本文主要参考《Go语言圣经(中文版)》及个人实践撰写,具体可参考: Go语言圣经
本文作为该系列第四篇,将重点介绍数据类型
,上一篇介绍了基础数据类型
,本篇将介绍复合数据类型
。
复合数据类型的“复合”之处在于其由基础数据类型的元素组成,C语言中聚合类型(如结构体、枚举、共用体等)就是复合数据类型的一种。Go语言的复合数据类型具体分为数组
/切片(slice)
/字典(map)
/结构体
四种。
数组
数组由相同数据类型元素构成的聚合类型。Go语言中数组和C语言几乎相同,具有固定长度
、索引访问
的特性,其中索引也是从0开始。
可以用以下标准语句声明并初始化一个数组:
var 数组名 [数组大小] 元素数据类型 = [数组大小] 元素数据类型 {初始化值}
参照变量的极简声明语句,也可以写出数组的极简声明(即数组的第二种声明方式):
数组名 := [数组大小] 元素数据类型 {初始化值}
参考以下代码段,首先数组两种方式的声明及整体输出:
package main
import "fmt"
func main() {
var s1 [3]int = [3]int{1, 2, 3} //普通声明
s2 := [3]string{"1", "2", "3"} //极简声明
fmt.Println(s1)
fmt.Println(s2)
}
运行结果如下:
由于Go语言的零值初始化性质,如果在声明时不手动初始化,自动初始化为当前数据类型的零值,参考以下代码及运行结果:
package main
import "fmt"
func main() {
var s1 [3]int
var s2 [3]string
fmt.Println(s1)
fmt.Println(s2)
}
在极简声明中可以进一步简化——将元素个数省去,使用...
替代,编译器将根据初始化的元素个数,自动推得数组大小,参考以下代码及运行结果:
package main
import "fmt"
func main() {
s1 := [...]int{1, 3, 5, 7, 9}
fmt.Println(s1)
fmt.Printf("%T\n", s1)
}
上述代码中占位符&T
代表相应值的类型的Go语法表示
,可显示数组大小及类型,运行结果如下:
除了使用占位符展现数组信息,还可以用内置的len
函数求出数组长度。参考以下代码:
package main
import "fmt"
func main() {
s1 := [...]int{1, 3, 5, 7, 9}
fmt.Println(len(s1))
fmt.Println(s1[len(s1)-1]) //打印末尾元素
}
运行结果如下,可以看到可以利用len求得的长度信息对应到最后一个元素的索引:
切片(Slice)
切片和数组一样,是同种数据类型元素聚合而成的“聚合类型”,但是数组的不可变长度性既会导致某些情况下空间不足,也会导致某些情况下空间浪费。所以Go语言中内置了可变长度
的Slice类型。
定义切片
切片的定义有两种方式:
- 使用
未指定大小的数组
定义切片,标准语句如下:
var 切片名 [] 数据类型
- 使用内置的
make
函数创建切片,标准语句如下:
var 切片名 [] 数据类型 = make([]数据类型, 初始长度) //普通声明
切片名 := make([]数据类型, 初始长度) //极简声明
其中make函数通常使用两个参数type
和len
(如上),第三个参数capacity
(容量)可选。
容量问题(capacity)
虽然将切片类型区别于数组提出,但是每一个切片对象都有一个底层数组(或多个切片对象共享一个底层数组)。所以虽然切片类型是一个长度可变的数据类型,但是底层也有一定的容量限制。
取《Go语言圣经》中的例子:
数组months是底层数组,切片可以取其中部分,如切片Q2、summer等。
参考定义切片中的make函数,定义声明时可固定容量。当后续不断追加元素,超过容量时,容量会自动翻倍
。可以通过内置函数len
获取切片长度、函数cap
获取底层数组容量。
参考以下代码,实现简单的容量翻倍:
package main
import "fmt"
func main() {
sli := make([]int, 5, 5) //初始长度为5,容量为5
fmt.Println(sli)
fmt.Printf("length: %d\ncapacity: %d\n", len(sli), cap(sli))
sli = append(sli, 2333) //追加一个元素
fmt.Println(sli)
fmt.Printf("length: %d\ncapacity: %d\n", len(sli), cap(sli))
}
运行结果如下,可以看到容量cap由原来的5翻倍为10:
初始化
① 声明时直接初始化:
切片名 := [] 数据类型 {初始化内容}
//例如:
sli := [] int {1,2,3} // cap=len=3
使用该方式初始化的切片对象cap=len=初始化内容中的元素个数。
② 依托已有数组初始化:
由于切片的底层就是数组,所以可以直接从数组中取部分或全部初始化切片:
切片名 := 数组名[起始索引:终止索引]
//例如:
sli := array[0:2333] // 取array[0]~array[2332]组成切片
范围的选取原则可以参考字符串string取字串的操作,真实取到的范围是[起始索引,终止索引)
,前开后闭。同理省略起始索引表示从头开始、省略终止索引表示一直到结尾。
空切片(nil)
切片定义/声明但未初始化,默认为nil
,长度为0。
参考以下代码:
package main
import "fmt"
func main() {
var sli []int
fmt.Println(sli)
fmt.Printf("sli is nil? %t\n", sli == nil)
}
运行结果如下,确实为nil空切片,因此可以用s==nil
来判断是否为空切片:
追加(append)和复制(copy)
追加即添加新元素到切片中,使用内置函数append
,标准语句如下:
原切片名 = append(原切片名,追加元素1,追加元素2,...,追加元素n)
//例如:
sli = append(sli, 1, 2, 3)
注意一定要用原来的切片指针接收append结果,因为append底层操作实际上是重新分配空间并将新位置返回给原指针。
复制即将原切片的所有元素复制到目标切片中,更新目标切片的长度len值,使用内置函数copy
,标准语句如下:
copy(目标切片名, 源切片名)
//例如:
copy(slice_dest,slice_init)
参考以下示例代码:
package main
import "fmt"
func main() {
sli := []int{1, 2, 3, 4, 5} //初始长度为5,容量为5
sli_des := make([]int, 6, 6) //目标切片长度和容量定义更小
fmt.Println(sli)
fmt.Println(sli_des)
fmt.Printf("sli length: %d capacity: %d\n", len(sli), cap(sli))
fmt.Printf("sli_des length: %d capacity: %d\n", len(sli_des), cap(sli_des))
copy(sli_des, sli)
fmt.Println(sli_des)
fmt.Printf("sli_des length: %d capacity: %d\n", len(sli_des), cap(sli_des))
}
上述代码使用一个更大容量的切片来接收复制的切片内容,输出结果如下:
但是如果使用一个容量更小的切片接收较大的切片,则会出发生截断,代码如下(仅将slice_des容量和长度改为4):
package main
import "fmt"
func main() {
sli := []int{1, 2, 3, 4, 5} //初始长度为5,容量为5
sli_des := make([]int, 4, 4) //目标切片长度和容量定义更小
fmt.Println(sli)
fmt.Println(sli_des)
fmt.Printf("sli length: %d capacity: %d\n", len(sli), cap(sli))
fmt.Printf("sli_des length: %d capacity: %d\n", len(sli_des), cap(sli_des))
copy(sli_des, sli)
fmt.Println(sli_des)
fmt.Printf("sli_des length: %d capacity: %d\n", len(sli_des), cap(sli_des))
}
结果如下,可见原来的[1,2,3,4,5]被截断为[1,2,3,4]:
字典(map)
Go语言中的字典map和C++ STL标准库中的map是一样的,都是通过哈希
实现的无序键值对
集合。
map创建通常有两种方式:
- 内置
make
函数创建,标准语句如下:
字典名 := make(map[key]value)
//例如:
map0 := make(map[string]int) //map from string to int
字面值语法
创建方法,标准语句如下:
字典名 := map[key]value{
key1 : value1
key2 : value2
...
keyn : valuen
}
//例如:
wheel := map[string]int{
"car" : 4
"bicycle" : 2
"tricycle" : 3
"human" : 0
} //我也不知道我怎么想到这个例子的
对应创建空map即为map[string]int{}
,空map为nil
,不能用于存放键值对。
添加键值对需要先确保 map!=nil,再直接使用mapName[key]=value
的形式添加即可。
删除键值对使用内置的delete
函数,标准语句如下:
delete(key,value)
结构体
和数组/切片不同,结构体可以存放不同类型的数据
,可以将同一个研究对象不同类型的性质数据归类到一起。
结构体定义需要使用type
和struct
语句,标准语法如下:
type 结构体名 struct{
数据成员定义
}
定义后可以按照如下格式对结构体变量进行声明:
变量名 := 结构体名{ value1, value2,..., valuen}
变量名 := 结构体名{ key1:value1, key2:value2,..., key3:value3}
和C语言相同,Go语言的结构体可以使用.
访问元素,同时可以定义结构体指针。
本篇主要介绍数据类型中的复合数据类型,包括数组、切片、字典、结构体。