五、复杂类型

在第三节我们简单的介绍了下数值类型、字符串类型和基本操作。现在介绍一下golang内置的稍微复杂点的类型:数组和映射(map)以及自定义类型及其操作。

1、数组

数组和字符串一样,是使用连续的内存空间进行存储,而且都是不可变的。不可变是什么意思呢,就是存储数据的地址空间是不会改变的,对于字符串来说,字符串任何字符都不能被改变,对于数组来说,并不是说数组内容不能改变。而且因为使用连续的地址空间,所以存取的时间复杂度为o(1)。
声明和定义数组方法如下:

var typeArray [N]Type

这样我们就申明了一个类型为任意存在的类型Type,大小为N的数组变量typeArray。比如:

#容量为10的int类型数组
var intArray [10]int

#容量为10的string类型数组
var strArray [10]string

定义好后,编译器会自动给它们设置对应的零值,对于数值类型,就是对应的0,对于字符串类型,就是空字符串“”,对于bool类型,就是false。数组通过[]+下下标的方式访问,下标从0开始。验证一下:

intArray[0] = 10
fmt.Println(intArray) //[10 0 0 0 0 0 0 0 0 0]

当然,我们可以申明和初始化一起做

var intArray = [3]int{1,2,3} // intArray := [3]int{1,2,3}

如果申明和初始化一起做,也可以不用指定大小,比如复制了一个个数未知的字符串,我们不会要一个个数吧?那也太麻烦了。对于这种情况,用...代替,表明自动计算大小。

var intArray = [...]int{1,2,3,4,5,6.........}

当然,数组里面也可以保存数组,这样就可以组成二维或者多维数组了,比如一个二维数组:

var array [2][2]int
array[0][0] = 10
fmt.Println(array) //[[10 0] [0 0]]

数组在定义的时候,就需要指定容量大小。但有的时候,我们一开始并不知道具体需要多大的大小,如果小了,剩余的数据没地方存,如果太大了,内存空间利用率又太低。尤其是在云时代,任何的存储和带宽都需要成本。就没有其他语言中的动态增长的数组吗?go这么强大,当然也提供了,那就是slice(切片)。

2、slice 切片

看看slice如何申明:

var intSlice []int

仔细一看,申明其实就是少了数组的大小而已。再看看如何在申明时初始化:

var intSlice = []int{1,2,3}

有没有感觉,还是一样的,只是少了数组的大小。其实,切片是数组的一个片段,本身不保存数据,是数组片段的一个引用。那么如何从数组得到一个切片呢?看下面:

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

对数组使用[from:to]就能得到数组从[from,to)的一个片段引用(切片),如果是从from到数组最后一个元素,我们可以省略to

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

如果是从0,到to,我们可以省略from

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

如果from和to都省略的话,就是整个数组的切片,所以切片不可能比底层数组的容量还大。对切片的修改,就是对底层数组的修改:

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

既然切片不能超过底层数组的容量,那么,如何实现动态增长呢?通过内置函数 append就可以实现,当容量不够的时候,就会申请一个容量更大的新数组,然后把老数组的数据拷贝过去,然后销毁老数组。其实动态增长的数组实现方式都是一样,只不过是增长因子可能不同而已。

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

如果一个方法需要的是一个切片,但是我们只有一个数组,如何传进去呢?我们在传入数组参数后面加...就可以了。

3、 map 映射

映射是一个键值对,通过键值能获取到对应的值,就是值映射到一个键上。在其他语言中,可能叫做map,可能叫做字典,其实是一样的都是键值对。只不过比起java来说类型有点少,只有hashMap。看下定义和初始化方式:

//var name map[keyType] valueType
var dic map[string] string
dic = make(map[string] string)
dic["name"]="Jin"
dic["app"]="Golang"
fmt.Println(dic) //map[app:Golang name:Jin]
fmt.Println(dic["name"]) // "Jin"

map的初始化只能通过内置函数make初始化,返回的是指向map的指针
使用map需要注意的几点:
map是无序的
map的长度不固定(可用内置函数len()查看map包含键值对的个数)
map只能通过key值进行操作
map是线程不安全的

使用内置函数delete()删除一个键值对

delete(dic,"name")
fmt.Println(dic) // map[app:Golang]

4、自定义类型

除了之前说过的类型,我们还可以自定义类型。格式为 type typeName typeType
比如,int类型其实是uint32的别名,看看如何定义的:

type int uint32

我们也可以定义一个其他类型的别名

type jin int64

对于这样的定义,其实意义不是很大,我们可以定义更复杂的类型

type user struct {
  name string
  age uin8
}

我们定义了一个user类型,它有两个属性:name和age
如何初始化(实例化)呢?

var xiaoMing = user{name:"xiaoMing",age:12}

这样我们就初始化了一个user对象--xiaoMing。我们也可以用内置函数new来创建自定义对象

var xiaoMing = new(user)
user.name = "xiaoMing"
user.age = uint8(12)

属性分别为xiaoMing和12
如果我们按照定义的顺序设置属性的话,可以把属性名去掉:

var xiaoMing = user{"xiaoMing",12}

如果只是设置部分属性,或者设置的顺序不是定义的顺序,就会导致问题,所以建议都把属性名加上,这样很直观,而且便于维护。

好了,我们可以自定义类型了,但是如何操作这些类型的实例呢。请看下节,函数和方法。

你可能感兴趣的:(五、复杂类型)