Go圣经-学习笔记之复合类型

上一篇 Go圣经-临时插入ORM的小trickgolang

下一篇 Go圣经-学习笔记之复合类型(二)数组

数组

数组是一个固定长度类型的序列,由零个或者多个同构元素组成。Go语言不多直接使用数组,通常使用Slice多。数组的表现形式:安全

var q [3]int =[3]int{1,2,3}
var r [3]int =[3]int{1,2}
var s =[...]int{1,2,3}

数组类型是由类型和长度组成的。所以[3]int和[5]int是两个不一样的数据类型app

若是相同数组的元素类型是能够比较的,则这个数组是能够比较的。咱们能够经过==进行数组比较。注意:这里不是C++,a==b,不是指针比较,而是数组全部元素的比较函数

咱们如何比较两个动态数组是否相等呢?有两种方法:学习

  • 第一种,比较slice的len、cap和底层数组元素
  • 第二种,咱们能够对slice序列化成byte流,而后利用sha256生成消息摘要, 摘要是32个字节的数组类型。而后直接比较便可。
func compareSlice(a []int, b []int) bool{
    if len(a) != len(b) {
        return false
    }
    for i:=0 ;i<len(a); i++{
        if a[i]!=b[i]{
            return false
        }
    }
    return true
}

这里有必要提一下,为啥判断a和b相等, 不比较cap(a)与cap(b)的大小关系呢?是否是比较时,只比较数据呢?优化

注意:不用比较slice的底层数组指针所指向的内存地址, 数据存放确定不在一块儿的。ui

package main

import (
	"crypto/sha256"
	"fmt"
)

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
    // 2d711642b726b04401627ca9fbac32f5c8530fb1903cc4db02258717921a4881
    // true
    // [32]uint8
}

当调用函数时,调用参数将会被赋值给函数内部的参数变量,因此函数参数接收是一个变量的副本,并非原始的变量。Go语言的数据传递所有是值类型。因此数组传递是低效的,而且对数组的修改都是发生在拷贝的数组上,对传入的实参没有任何影响。如果C++等其余语言,则数组传递时,是传递的指针。同时附带数组的长度信息。若是必定要传数组,请传数组指针,例如func(aptr *[32]byte)。.net

Slice动态数组

一个很形象的例子指针

months := []string{1: "January", /* ... */, 12: "December"}

月份动态数组

这里有个我很懵逼的问题:

var months = []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}

// 夏天
summer:=months[6:9]

fmt.Println(summer[:20]) // slice超出了cap(summer),直接panic。  明白,由于summer容量是12-6=6

fmt.Println(summer[:6]) // [ 7,8,9,10,11,12 ] ,这里有点懵逼,其实summer[:6]已经超出len(summer)了,我以为应该是有越界控制的,可是没有

var months = make([]int, 0, 12)
fmt.Println(months[:10]) // [ 0,0,0,0,0,0,0,0,0,0 ]
months[0] = 1 // panic: index out of range

主要懵逼的问题:

  • 当读取slice动态数组时,只要读取范围不超过cap容量就ok了
  • 当写入slice动态数组时,只要超出len长度了,就报"index out of range"的panic

个人问题是,为何不作一致性呢?读写都控制在len范围内呢?

由于上面这个问题,咱们要十分当心在slice动态数组读取的时候,克制len的范围,否则就会出现下面这种状况

// nonempty returns a slice holding only the non-empty strings.
// The underlying array is modified during the call.
func nonempty(strings []string) []string {
    i := 0
    for _, s := range strings {
        if s != "" {
            strings[i] = s
            i++
        }
    }
    return strings[:i]
}

func main() {
    data := []string{"one", "", "three"}
    data = nonempty(data)

    fmt.Println(data[:3]) // [one three three], 这个是不想看到的
    fmt.Println(data) // [one three]
}

利用slice动态数组,左旋转slice的前N个元素

package main

import "fmt"

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]
    }
}

func reverseN(s []int, n int) {
    reverse(s[:n]) // 反转前N个数
    reverse(s[n:]) // 反转后len(s)-n-1
    reverse(s)
}

func main() {
    var a = []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}
    var n = 5 // 指定旋转前N个数
    reverseN(a, n)
    fmt.Println(a)  // [5 6 7 8 9 10 11 12 0 1 2 3 4]
}

slice的比较问题

和数组不一样的是,slice动态数组不能直接比较,不过标准库提供了bytes.Equal方法判断两个字节型slice是否相等。对于其余咱们须要比较slice的元素个数和元素值。同时咱们又引出struct类型的比较。

type Person struct {
    Cap int
    Len int
    Bt  *byte
}

func main() {
    p1 := Person{
        Cap: 28,
        Len: 28,
        Bt:  new(byte),
    }
    p2 := Person{
        Cap: 28,
        Len: 28,
        Bt:  new(byte),
    }
    fmt.Println(p1 == p2)
}

上面这个DEMO是能够编译经过,且运行没有问题的,返回false。

可是slice的底层结构和这个是相似的。可是它不能比较,slice是一个特殊类型。尽可能少的考虑它是一个struct类型。同时struct可以进行比较,取决于内部的各个元素是否可以比较。

那为何不直接slice动态数组比较呢?只须要比较元素个数和每一个元素比较。咱们从《Go圣经-学习笔记入门bufio》, 的bytes.ReadSlice方法就能够知道line多是间接引用的,它底层的数据可能会随时发生变化。这样比较的话是没有意义的,因此安全的作法是直接禁止slice比较。

append函数

在使用slice动态数组的扩容时,咱们常常会用到append函数。它的原型:

func append(src []T, elem ...T) []T

由于cap容量有界的,因此当slice静态增加到达cap容量后,就须要从新分配必定大小的内存空间,把老的slice动态数组复制到新的slice中,而后再静态增加。整个流程就是这样子的。

那么,append函数就是这个做用,分两种状况讨论:

  • 当 len(elem)+len(src)>cap(src)时,就须要动态扩容了,扩容后的cap容量=2*(len(elem)+len(src)), 增长后长度的两倍。同时返回的slice是新的动态数组,与src无关。因此须要返回类型[]T。
  • 当 len(elem)+len(src) <= cap(src)时,知足静态增加的须要。src和返回slice类型数据共用底层数组。

内存的动态增加,会产生暂时的内存浪费、以及屡次内存分配致使时效低下。若是咱们一开始分配合理的内存空间大小,既能够节约内存空间,又能够优化时间。

相关文章
相关标签/搜索