Go数据类型有如下类型。

  • 基础类型
  • 复合类型
  • 引用类型
  • 接口类型 类型内容很多只能慢慢学习,今天学习复合类型。

复合类型

符合类型分类

  • 固定长度
    • 数组
    • 结构体
  • 动态长度
    • slice
    • map

      类型详解

  • 数组:数组是一个由固定长度的特定类型元素组成的序列,一个数组可以由零个或多个元素组成
    • 我们将会发现,数组、slice、map和结构体字面值的写法都很相似。是直接提供顺序初始化值序列,但是也可以指定一个索引和对应值列表的方式初始化,就像下面这样: ``` type Currency int

    const ( USD Currency = iota // 美元 EUR // 欧元 GBP // 英镑 RMB // 人民币 )

    symbol := […]string{USD: “$”, EUR: “€”, GBP: “£”, RMB: “¥”}

    fmt.Println(RMB, symbol[RMB]) // “3 ¥”

      * 数组是可以直接比较的
    

    import “crypto/sha256”

    func main() { c1 := sha256.Sum256([]byte(“x”)) c2 := sha256.Sum256([]byte(“X”)) fmt.Printf(“%x\n%x\n%t\n%T\n”, c1, c2, c1 == c2, c1) // Output:
    //2d711642b726b04401627ca9fbac32f5c8530fb1903cc4db02258717921a4881 // 4b68ab3847feda7d6c62c1fbcbeebfa35eab7351ed5e78f4ddadea5df64b8015 // false // [32]uint8 } ```

    • Go语言对待数组的方式和其它很多编程语言不同,其它编程语言可能会隐式地将数组作为引用或指针对象传入被调用的函数。

      因为函数对待参数是值传递,需要复制参数变量,有些变成语言回见数据组隐式的转换为引用,以减少复制大型参数造成的额外开销。

    • 虽然通过指针来传递数组参数是高效的,而且也允许在函数内部修改数组的值,但是数组依然是僵化的类型,因为数组的类型包含了僵化的长度信息。由于这些原因,除了像SHA256这类需要处理特定大小数组的特例外,数组依然很少用作函数参数;相反,我们一般使用slice来替代数组。
  • slice:底层是用数组实现的,支持动态的扩容
    • 组成
      • 指针:指针指向第一个slice元素对应的底层数组元素的地址,要注意的是slice的第一个元素并不一定就是数组的第一个元素
      • 长度:长度对应slice中元素的数目,长度不能超过容量
      • 容量:容量一般是从slice的开始位置到底层数据的结尾位置。内置的len和cap函数分别返回slice的长度和容量。
    • 多个slice之间可以共享底层的数据,并且引用的数组部分区间可能重叠。图4.1显示了表示一年中每个月份名字的字符串数组,还有重叠引用了该数组的两个slice。 9ca9eaf81a29acc6f76294e5cf97a179.png
    • 因为slice值包含指向第一个slice元素的指针,因此向函数传递slice将允许在函数内部修改底层数组的元素。换句话说,复制一个slice只是对底层的数组创建了一个新的slice别名。下面的reverse函数在原内存空间将[]int类型的slice反转,而且它可以用于任意长度的slice。 ``` func reverse(s []int) { for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 { s[i], s[j] = s[j], s[i] } }

    a := […]int{0, 1, 2, 3, 4, 5} reverse(a[:]) fmt.Println(a) // “[5 4 3 2 1 0]” ```

    • slice底层是数组但并不能像数组一样比较。
      • 一个原因slice的元素是间接引用的,一个slice甚至可以包含自身
      • 第二个原因,因为slice的元素是间接引用的,一个固定的slice值(译注:指slice本身的值,不是元素的值)在不同的时刻可能包含不同的元素,因为底层数组的元素可能会被修改。slice扩容是地址也会发生改变。鉴于安全考虑直接禁止slice之间的比较操作,简化复杂度。
    • 判断空slice
      • 如果你需要测试一个slice是否是空的,使用len(s) == 0来判断,而不应该用s == nil来判断。
  • 结构体:相当于类
    • 一个命名为S的结构体类型将不能再包含S类型的成员:因为一个聚合的值不能包含它自身。(该限制同样适应于数组。)但是S类型的结构体可以包含*S指针类型的成员,这可以让我们创建递归的数据结构,比如链表和树结构等。
    • Go语言有一个特性让我们只声明一个成员对应的数据类型而不指名成员的名字;这类成员就叫匿名成员。匿名成员的数据类型必须是命名的类型或指向一个命名的类型的指针。下面的代码中,Circle和Wheel各自都有一个匿名成员。我们可以说Point类型被嵌入到了Circle结构体,同时Circle类型被嵌入到了Wheel结构体。 ``` type Point struct { X, Y int }

    type Circle struct { Center Point Radius int }

    type Wheel struct { Circle Circle Spokes int }

    var w Wheel w.X = 8 // equivalent to w.Circle.Point.X = 8 w.Y = 8 // equivalent to w.Circle.Point.Y = 8 w.Radius = 5 // equivalent to w.Circle.Radius = 5 w.Spokes = 20 ```

    • 在右边的注释中给出的显式形式访问这些叶子成员的语法依然有效,因此匿名成员并不是真的无法访问了。其中匿名成员Circle和Point都有自己的名字——就是命名的类型名字——但是这些名字在点操作符中是可选的。我们在访问子成员的时候可以忽略任何匿名成员部分。
  • map
    • map 底层也是固定数组,通过哈希表映射
    • map中的元素并不是一个变量,因此我们不能对map的元素进行取址操作,禁止对map元素取址的原因是map可能随着元素数量的增长而重新分配更大的内存空间,从而可能导致之前的地址无效。
    • Map的迭代顺序是不确定的,并且不同的哈希函数实现可能导致不同的遍历顺序。在实践中,遍历的顺序是随机的,每一次遍历的顺序都不相同。这是故意的,每次都使用随机的遍历顺序可以强制要求程序不会依赖具体的哈希函数实现。

总结

类型就是数据结构,写代码先定义数据结构,数据结构决定了代码的走向决定了用什么算法。学海无涯苦作舟,千帆过尽还复来。继续努力吧。