顾名思义,复合数据类型就是由其他类型组合而成的类型。Go语言基本的复合数据类型有指针、数组、切片、字典(map)、通道、结构和接口,它们的字面量格式如下:
* pointerType //指针类型使用*后面跟其指向的类型名
[n]elementType //数组类型使用[n]后面跟数组元素类型来表示,n表示该数组的长度
[]elementType //切片类型使用[]后面跟切片元素类型来表示
map[keyType]valueType //map类型使用map[键类型]值类型来表示
chan valueType //通道使用chan后面跟通道元素类型来表示
struct { //结构类型使用st上uct{}将各个结构字段扩起来表示
feildType feildType
feildType feildType
}
interface { //接口类型使用interface{}将各个方法括起来表示
method1 (inputParams)(returnParams)
method2 (inputParams)(returnParams)
}
Go语言支持指针,指针的声明类型为*T,Go同样支持多级指针**T。通过在变量名前加&来获取变量的地址。指针的特点如下:
var a 11
p := &a //*p和a的值都是11
type User struct {
name string
age int
}
andes := User{
name: "andes",
age: 18,
}
ptr := &andes
fmt.Println(ptr.name, "+", ptr.age)
a := 1234
p := &a
p++ //不允许,报non-numeric type*int错误
func sum(a, b int) *int {
sum := a + b
return &sum //允许,sum会分配在heap上
}
数组的类型名是[n]elemetType,其中n是数组长度,elementType是数组元素类型。比如一个包含2个int类型元素的数组类型可表示为[2]it。数组一般在创建时通过字面量初始化,单独声明一个数组类型变量而不进行初始化是没有意义的。
var arr [2]int //声明一个有两个整型的数组,但元素默认值都是0,一般很少这样使用
array := [...]float64{7.0, 8.5, 9.1} //[...]后面跟字面量初始化列表
数组初始化:
w := [3]int{1, 2, 3} //指定长度和初始化字面量
x := [...]int{1, 2, 3} //不指定长度,但是由后面的初始化列表数量来确定其长度
y := [3]int{1: 1, 2: 3} //指定总长度,并通过索引值进行初始化,没有初始化元素时使用类型默认值
z := [...]int{1: 1, 2: 3} //不指定总长度,通过索引值初始化,数组长度由最后一个索引值确定,没有指定索引的元素被初始化为类型的零值
fmt.Println(w)//[1 2 3]
fmt.Println(x)//[1 2 3]
fmt.Println(y)//[0 1 3]
fmt.Println(z)//[0 1 3]
数组的特点
数组相关操作
aa := [...]int{1, 2, 3}
bb := aa[0]
for i, v := range aa {
fmt.Println(i, ",", v)
}
cc := [...]int{1, 2, 3}
alengh := len(cc)
for i := 0; i < alengh; i++ {
fmt.Println(cc[i])
}
Go语言的数组的定长性和值拷贝限制了其使用场景,Go提供了另一种数据类型slice(中文为切片),这是一种变长数组,其数据结构中有指向数组的指针,所以是一种引用类型。例如:
// src/runtime/slice.go (go.19.1)
11 type slice struct {
12 array unsafe.Pointer
13 len int
14 cap int
15 }
Go为切片维护三个元素——指向底层数组的指针、切片的元素数量和底层数组订的容量。具体结构如下图:
切片的创建:
var array = [...]int{0, 1, 2, 3, 4, 5, 6} //创建有7个int类型元素的数组
s1 := array[0:4]
s2 := array[:4]
s3 := array[2:]
fmt.Println(s1) //[0 1 2 3]
fmt.Println(s2) //[0 1 2 3]
fmt.Println(s3) //[2 3 4 5 6]
//len=10,cap=10
a := make([]int, 10)
//len=10,cap=15
b := make([]int, 15)
fmt.Printf("%v\n", a)
fmt.Printf("%v\n", b)
这里要注意:直接声明切片类型变量是没有意义的。例如:
var a []int
fmt.Printf("%v\n", a) //结果为[]
切片支持的操作:
x := [...]int{0, 1, 2, 3, 4, 5, 6}
y := make([]int, 2, 4)
z := x[0:3]
fmt.Println(len(y)) //2
fmt.Println(cap(y)) //4
y = append(y, 1)
fmt.Println(y) // [0 0 1]
fmt.Println(len(y)) //3
fmt.Println(cap(y)) //4
y = append(y, z...)
fmt.Println(y) // [0 0 1 0 1 2]
fmt.Println(len(y)) // 6
fmt.Println(cap(y)) // cap(y)=8,底层数组发生扩展
o := make([]int, 2, 2)
copy(o, z) //copy只会复制o和z中长度最小的
fmt.Println(o) // [0 1]
fmt.Println(len(o)) // 2
fmt.Println(cap(o)) // 2
字符串和切片的相互转换:
str := "hello,世界!" //通过字符串字面量初始化一个字符串str
aa := []byte(str) //转为[]byte类型切片
bb := []rune(str) //转为[]rune类型切片
Go语言内置的字典类型叫map。map的类型格式是:map[K]T,其中K可以是任意可以进行比较的类型,T是值类型。map也是一种引用类型。
map的创建:
ma := map[string]int{"a": 1, "b": 2}
fmt.Println(ma["a"])
fmt.Println(ma["b"])
mp1 := make(map[int]string)
mp2 := make(map[int]string, 10)
mp1[1] = "tom"
mp2[1] = "pony"
fmt.Println(mp1[1]) //tom
fmt.Println(mp2[1]) //pony
map支持的操作:
mmp := make(map[int]string)
mmp[1] = "tom"
mmp[2] = "pony"
mmp[3] = "jaky"
mmp[4] = "andes"
delete(mmp, 3)
fmt.Println(mmp[1])
fmt.Println(len(mmp)) //len函数返回map中的键值对的数量
for k, v := range mmp { //range支持遍历map,但不保证每次遍历次序是一样的
fmt.Println("key=", k, "value=", v)
}
注意:
type User struct {
name string
age int
}
maa := make(map[int]User)
andes := User{
name: "andes",
age: 18,
}
maa[1] = andes
//ma[1].age = 19 //error,不能通过map引用直接修改
andes.age = 19
maa[1] = andes //必须整体赋值
fmt.Printf("%v\n", maa)
Go中的suct类型和C类似,中文翻译为结构,由多个不同类型元素组合而成。这里面有两层含义:第一,struct结构中的类型可以是任意类型;第二,struct的存储空间是连续的,其字段按照声明时的顺序存放(注意字段之间有对齐要求)。
struct有两种形式:一种是struct类型字面量,另一种是使用type声明的自定义struct类型。
struct {
FeildName FeildType
FeildName FeildType
FeildName FeildType
}
type TypeName struct {
FeildName FeildType
FeildName FeildType
FeildName FeildType
}
实际使用struct字面量的场景不多,更多的时候是捅咕otype自定义一个新的类型来实现的。type是自定义类型的关键字,不但支持struct类型的创建,还支持任意其他子定义类型的创建。
type Person struct {
Name string
Age int
}
type Student struct {
*Person
Number int
}
//按照类型声明顺序,逐个赋值
//不推荐这种初始化方式,一旦struct增加字段,则整个初始化语句会报错
a := Person{"tom", 21}
//土建这种使用Feild名字的初始化方式,没有指定的字段则默认初始化为类型的零值
p := &Person{
Name: "tata",
Age: 12,
}
s := Student{
Person: p,
Number: 110,
}