美文网首页
五、复杂类型

五、复杂类型

作者: UUID | 来源:发表于2018-04-23 18:46 被阅读8次

    在第三节我们简单的介绍了下数值类型、字符串类型和基本操作。现在介绍一下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}
    

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

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

    相关文章

      网友评论

          本文标题:五、复杂类型

          本文链接:https://www.haomeiwen.com/subject/gzbwkftx.html