Go语言的复合数据类型
Go语言的复合数据类型是基础数据类型的组合,主要包括四个数组,切片(slice),map和结构体。
数组和结构体的大小是固定大小的,数组的元素类型是固定的,结构体的元素类型是不固定。
map和slice是动态的数据结构,它们将根据需求动态的增长。
##数组
数组顾名思义就是同一类资源或者数据的集合。下面主要介绍对数组的操作:
数组的初始化
var arr [3]int //默认初始化0
var q [3]int = [3]int{1,2,3}
q := [...]int{1,2,3}
q := [...]int{90:-1}//key和value的赋值方式,下表90的值为-1,数组长度为91
数组的访问
可以使用数组下标来访问数组中的元素。与C语言相同,数组下标从0开始,len(array)-1
则表示最后一个元素的下标。下面的示例遍历整型数组并逐个打印元素内容:
q := [...]int{1, 2, 3, 4}
for i, value := range q {
fmt.Println(i, value)
}
数组可以直接进行比较,当数组内的元素都一样的时候表示两个数组相等。
arr1 := [3]int{1, 2, 3}
arr2 := [3]int{1, 2, 3}
arr3 := [3]int{1, 2, 4}
fmt.Println(arr1 == arr2, arr1 == arr3) //true,false
数组可以作为函数的参数传入,但由于数组在作为参数的时候,其实是进行了拷贝,这样在函数内部改变数组的值,是不影响到外面的数组的值得。
func ArrIsArgs(arr [4]int) {
arr[0] = 100
}
q := [...]int{1, 2, 3, 4}
ArrIsArgs(q)
如果想要改变:
func ArrIsArgs(arr *[4]int) {
arr[0] = 100
}
q := [...]int{1, 2, 3, 4}
ArrIsArgs(&q)
但一般都是用切片来解决这个问题,而不是用数组。
切片
切片和数组很相似,只是它的长度并不是固定的,也就是定义的时候[ ]T内不需要指定长度。
数组和slice关系非常密切,一个slice可以访问数组的部分或者全部数据,而且slice的底层本身就是对数组的引用。
一个Slice由三部分组成:指针,长度和容量。内置的len和cap函数可以分别返回slice的长度和容量
首先我们来看slice的创建方式:
创建slice主要两种:1.基于数组创建。2.直接创建
-
基础数组创建:
arrVar := [4]int{1, 2, 3,4} sliceVar := arrVar[1:3]
数组arrVar和sliceVar里面的地址其实是一样的,也就是说如果你改变sliceVar里面的变量,那么arrVar里面的变量也会随之改变。
2.直接创建
可以使用内置的make()函数来创建。事实上还是会创建一个匿名的数组,只是不需要我们来定义。
myslice1 := make([ ]int,5)//创建一个元素个数5的slice,cap也是5
myslice2 := make([ ]int,5,10)//创建一个元素个数5的slice,cap是10
myslice3 := [ ]int{1,2,3,4}//创建一个元素个数为4的slice,cap是4
var slice []int //创建一个空的slice,cap和len都是0
对于为什么说slice其实和数组是一个地址那,看下面这张图:
3.动态的增减元素
前面说过,slice是可以动态扩展的。但slice的动态扩展是有代价的,也就是说如果在确定大小的前提下,最好是设置好slice的cap大小,看个经典的例子:
func TestAppend() {
var slice []int
for i := 0; i < 10; i++ {
slice = append(slice, i)
fmt.Printf("%d cap = %d t%v\n", i, cap(slice), slice)
}
}
output:
0 cap = 1 t[0]
1 cap = 2 t[0 1]
2 cap = 4 t[0 1 2]
3 cap = 4 t[0 1 2 3]
4 cap = 8 t[0 1 2 3 4]
5 cap = 8 t[0 1 2 3 4 5]
6 cap = 8 t[0 1 2 3 4 5 6]
7 cap = 8 t[0 1 2 3 4 5 6 7]
8 cap = 16 t[0 1 2 3 4 5 6 7 8]
9 cap = 16 t[0 1 2 3 4 5 6 7 8 9]
可以看到,当slice的的容量等于len的时候,cap是翻倍了。append的底层原理就是当slice的容量满了的时候,重新建立一块内存,然后将原来的数据拷贝到新建的内存。所以说容量的扩充是存在内存的建立和复制的。该过程将会影响到系统的运行速度。
append 也可以将两个slice拼接起来,但格式有所不同:
oldSlice := []int{1, 2, 3, 4, 5}
oldSlice2 := []int{6, 7, 8, 9, 10}
oldSlice = append(oldSlice, oldSlice2...) //必须加三个...
两个slice也可以直接进行内容的复制,copy函数:
slice1 := []int{1, 2, 3, 4, 5}
slice2 := []int{5, 4, 3}
copy(slice2, slice1) // 只会复制slice1的前3个元素到slice2中
copy(slice1, slice2) // 只会复制slice2的3个元素到slice1的前3个位置
4.作为形参使用
要想搞清楚slice我们需要看一下slice作为函数参数的使用方式,看一下下面的几个例子:
func TestSLice(myslice []int) {
for i := 0; i < 8; i++ {
myslice = append(myslice, i)
}
}
eg1:
myslice1 := make([]int, 8, 8)
TestSLice(myslice1[:0])
fmt.Println(myslice1) //结果是什么?
eg2:
myslice1 := make([]int, 8, 8)
TestSLice(myslice1 )
fmt.Println(myslice1) //结果是什么哪?
eg1 的结果是[0 1 2 3 4 5 6 7],eg2的结果是[0 0 0 0 0 0 0 0]。来分析两个实例的不同,如果和你想的不一样,那么就证明你对slice的理解是有问题的(我也纠结了比较久o(╯□╰)o)
首先分析一下第一个用例TestSLice(myslice1[:0]),这个的意思其实就是将一个新的slice作为参数传给函数,新的slice指向的是myslice1的第0个元素的地址,这时候你可以测试一下myslice1[:0]这个东西的cap和len,数值分别为8和0,然后你在函数里面append的时候实际上是对新的slice进行了操作,新的slice和旧的指向的底层数组的地址是一样。所以外面的数值是被改变了的。
再来分析第二个实例,第二个实例直接把myslice1传给了函数,这其实是复制了一个slice的,在函数里面append的时候当你的len大于cap值得时候,这时候返回的是一个新的地址。所以外面的变量是不会变化的。全是默认值0。但如果你在append之前改变slice的值比如slice[0] = 100,main函数里面的slice[0]也会变为100,因为append在自动扩展之前是共享底层内存数组的。
那么我们想直接改变外面的slice怎么办?答案就是指针,在GO语言里面你想修改什么就传什么的指针。
func TestSLice3(myslice *[]int) {
for i := 0; i < 8; i++ {
*myslice = append(*myslice, i)
}
}
myslice1 := make([]int, 0, 8)
TestSLice3(&myslice1)
对于SLICE的应用基本就可以到这里结束了,大家可以多测试一下Slice的使用。
在Go语言中,所有的函数参数都是值拷贝传入的,函数参数将不再是函数调用时的原始变量
##map
在C++/java中,map 一般都是封装在库里面的,但在GO语言中map可以直接使用。作为Key-value的数据结构,map就是对哈希表的引用。
1.声明
var myMap map[string] PersonInfo
myMap是声明的变量名,sting是对应的Key的类型,PeesonInfo是value的类型。
2.创建
创建map使用的是GO语言内置的make()来创建的。
myMap = make(map[string] Personinfo)
创建一个容量固定的MAP
myMap = make(map[string] Personinfo,100)
创建初始化MAP
mymap1 := map[string]int{"hello": 1, "world": 2}
3.元素的删除
对于map的元素的删除,可以采用内置的delete函数
delete(myMap,"123")
如果你后面传入的key不存在,那么调用不会产生什么错误,但如果myMap是nil那么就会抛出异常了。
4.元素的查找
在map中传统的做法是:
1.声明一个变量为空
2.将map中获得的值保存到变量中
3.判断是否为空。
但这种做法过于复杂,可以采用下面的方式来实现:
value,ok := myM ap["1234"]
if ok{//代表找到了
//代码处理模块
}
##结构体
结构体是一种聚合的数据类型,是由零个或多个任意类型的值聚合成的实体。所以结构体里面不可以包含结构体类型的数据,但是可以包含该结构体类型的指针。
1.结构体的定义:
type MyStruct struct{
name string
age int
male string
}
var mystruct1 = myStruct{"wenxuwan", 12, "man"}
var mystruct2 = myStruct{name: "mashijie", age: 12, male: "man"}
结构体字面值并没有简短表示匿名成员的语法
2.结构体的操作
type myStruct struct {
name string
age int
male string
}
var mystest myStruct //定义struct
mystest.name = "wenxuwan" //通过.操作赋值
mystest.age = 28
mystest.male = "man"
var myStructPoint *myStruct = &mystest //通过指针操作结构体
memberPoint := &mystest.age //可以对里面的成员变量取值,然后用指针进行操作
*memberPoint = 200
fmt.Println(myStructPoint.name, myStructPoint.age, myStructPoint.male)
3.结构体的嵌入和匿名函数
首先我们需要来看为啥结构体是需要嵌套,看下面的例子
type Bird struct {
method string
species string
male string
}
type tiger struct {
method string
species string
male string
yanchi int //牙齿独有假设
}
上面的两个结构体,都包含了三个相同的变量,当然也有不同的,如果再加上其它的动物,那么我们需要写很多重复的代码,这样费时费力。所以我们可以写成以下方式:
type Animal struct {
method string
species string
male string
}
type Bird struct {
animal Animal
fly bool
}
type Tiger struct {
animal Animal
yanchi int
}
var tigers Tiger
tigers.animal.male = "gong"
tigers.animal.species = "maoke"
tigers.animal.method = "run"
这种方式是不是和当年我们刚开始学面向对象的继承的时候一样的感觉。回归到这个问题,改动之后结构体类型变的清晰了,但是这种修改同时也导致了访问每个成员变得繁琐。
Go语言有一个特性让我们只声明一个成员对应的数据类型而不指名成员的名字;这类成员就叫匿名成员。匿名成员的数据类型必须是命名的类型或指向一个命名的类型的指针
type Bird struct {
Animal
fly bool
}
type Tiger struct {
Animal
yanchi int
}
var tigers Tiger
tigers.male = "nan"
tigers.method = "pao"
如果你的Tiger里面也有Animal里面的变量,那么此处更改的是tiger的变量而不是Animal里面的。