美文网首页Golang 开发者Go语言程序员
Go语言中的Array、Slice、Map、Set和Struct

Go语言中的Array、Slice、Map、Set和Struct

作者: Solonk8 | 来源:发表于2017-02-04 00:55 被阅读1444次

    Go语言的数据类型和其他语言诸如Java,Python有相似之处,也有自己独特的地方。这篇文章主要讨论了几种数据结构类型(Composite Types)的初始化以及基本使用方法。

    Array

    Go中Array是固定长度的数组,因为其长度固定,所以在实际编程中Array很少被直接使用,动态数组Slice更为通用。

    初始化

    Array的初始化方式如下:

    var a [3]int
    var a = [3]int{1, 2, 3}
    
    var a = [3]int{1, 2, 3}
    fmt.Println(a[2]) // "3"
    
    var b = [...]int{1, 2, 3}
    fmt.Printf("%T\n", b) // "[3]int"
    

    遍历

    Array的遍历方式很有Python风格:

    for idx, v := range a {
        fmt.Printf("d% %d\n", idx, v)
    }
    

    要注意的是,迭代过程中v是索引位置值的拷贝,因此变量v的地址每次循环都是相同的。如果要得到每个元素的真实地址可以用&a[idx]

    Array还可以直接给指定index元素赋值:

    r := [...]int{99:-1} // 前99个均为0,第100个元素为-1
    

    多维数组

    // 声明一个二维数组
    var array [4][2]int
    // 使用数组字面值声明并初始化
    array := [4][2]int{{10, 11}, {20, 21}, {30, 31}, {40, 41}}
    // 指定外部数组索引位置初始化
    array := [4][2]int{1: {20, 21}, 3: {40, 41}�}
    // 同时指定内外部数组索引位置初始化
    array := [4][2]int{1: {0: 20}, 3: {1: 41}}
    

    Slice

    Slice(切片)是Go中所谓的动态数组,但与动态数组也有一些区别。Go中slice的创建方法有很多种,我们一个一个来看。

    初始化

    首先是最直接的make方式创建slice:

    s1 := make([]string, 5) // 只指定slice长度
    s2 := make([]string, 5, 7) //第二个参数指slice的容量
    

    slice的实现建立在底层数组之上,容量指的是底层数组的长度。slice也可以直接以赋值的方式创建:

    // 创建一个字符串 slice
    // 长度和容量都是 5
    slice := []string{"Red", "Blue", "Green", "Yellow", "Pink"}
    
    // 创建一个字符串 slice
    // 初始化一个有100个元素的空的字符串 slice
    slice := []string{99: ""}
    

    除此之外,slice也可以在array的基础上创建:

    months := [...]string{1: "January", /* ... */, 12:"December"}
    Q2 := months[4:7] // ["April", "May", "June"], len=3, cap=9
    summer := months[6:9] // ["June", "July", "August"], len=3, cap=7
    

    Empty slice和nil slice

    在Go中empty slice和nil slice是一对非常容易混淆的概念。他们两者表面上很相似:长度和容量都为0。但是区别在于nil slice是没有底层数组的,nil slice可以被看作是未初始化的slice。

    对于nil slice,当我们想要表示一个并不存在的slice时它变得非常有用,比如一个返回slice的函数中发生异常的时候。

    empty slice包含0个元素并且底层数组没有分配存储空间。当我们想要表示一个空集合时它很有用处,比如一个数据库查询返回0个结果。

    var s []int
    s = nil // len(s) == 0, s == nil
    s = []int(nil) // len(s) = 0, s == nil
    s = []int{} // len(s) = 0, s != nil
    

    值得注意的一点是,在判断slice是否为空时,我们应该用len(s) == 0而非s == nil

    Slice 增长

    // 创建一个长度和容量都为5的 slice
    slice := []int{10, 20, 30, 40, 50}
    // 创建一个新的 slice
    newSlice := slice[1:3]
    // 为新的 slice append 一个值
    newSlice = append(newSlice, 60)
    

    前面提到过,重叠的slice会共享底层数组,所以在未超出容量的条件下,append的新值也会同时改变slice对应位置的值。如果newSlice增加值后超过slice原容量长度:

    // 创建一个长度和容量都为5的 slice
    slice := []int{10, 20, 30, 40, 50}
    // 创建一个新的 slice
    newSlice := slice[1:]
    // 为新的 slice append 一个值
    newSlice = append(newSlice, 60)
    

    slice不会改变,cap仍旧是5。

    对于容量不足的slice,如果append则会创建新的底层数组,拷贝已存在的值和将要被附加的新值——容量会是现有元素的两倍(前提是元素个数小于1000),如果元素个数超过1000,那么容量会以 1.25 倍来增长。

    // 创建长度和容量都为4的 slice
    slice := []int{10, 20, 30, 40}
    // 附加一个新值到 slice,因为超出了容量,所以会创建新的底层数组
    newSlice := append(slice, 50)
    

    多维slice

    初始化同理:

    slice := [][]int{{10}, {20, 30}}
    

    需要注意的是使用 append 方法时的行为,比如我们现在对 slice[0] 增加一个元素:

    slice := [][]int{{10}, {20, 30}}
    slice[0] = append(slice[0], 20)
    

    那么只有 slice[0] 会重新创建底层数组,slice[1] 则不会。

    函数间传递

    由于slice相当于是指向底层数组的指针,所以slice的传递非常廉价方便:

    slice := make([]int, 1e6)
    slice = foo(slice)
    func foo(slice []int) []int {
        ...
        return slice
    }
    

    基于slice的stack实现

    首先给出empty slice,然后利用append实现:

    stack := []int{}
    
    stack = append(stack, 1) // push
    stack = stack[:len(stack) - 1] // pop
    top := stack[len(stack) - 1] // peek method
    

    Map

    初始化

    总的来说map是键值对的无序组合。Map的初始化如下:

    m := make(map[int]string)
    m := map[string]int {
        "alice": 31,
        "bob": 34,
    }
    

    map 的键可以是任意内建类型或者是 struct 类型,map 的值可以是使用 ==操作符的表达式。slice,function 和 包含 slice 的 struct 类型不可以作为 map 的键,否则会编译错误:

    dict := map[[]string]int{}
    Compiler Exception:
    invalid map key type []string
    

    同样,nil map不能存放键值对:

    var colors map[string]string
    colors["Red"] = "#da1337"
    Runtime Error:
    panic: runtime error: assignment to entry in nil map
    

    删除

    map的删除操作很简单:

    delete(m, "alice") // remove element m["alice"]
    

    如果map中不存在某个键值对,对其进行操作也是安全的,map会在操作时给它附上默认值:

    m["charlie"]++ // m["charlie"] = 1
    

    检查是否存在该值:

    age, ok := m["bob"]
    

    ok作为flag,如果返回true则表明含有该key,如果返回false则该key不存在。

    函数间传递

    函数间传递的map不是map的拷贝,所以如果我们在函数中改变了 map,那么所有引用 map 的地方都会改变。

    Set

    Go中没有直接的set的实现,原因在于set可以十分简单地利用map实现:

    set := make(map(int)bool)
    

    struct

    初始化

    Struct是把零个或者多个变量组合在一起的整体。其初始化如下:

    type Employee struct {
        ID int
        Name string
        DoB time.Time
    }
    
    var e Employee
    em1 := Employee{1, "name", "time..."}
    em2 := Employee{ID: 1, Name: "name", DoB: "19930701"}
    
    em1.ID = 1 // 调用
    

    嵌套式初始化如下:

    Wheel {
        Circle: Circle {
            Point: Point {
                X: 8
                Y: 8
            },
            Radius: 5
        }
        Spokes: 20
    }
    

    函数间传递

    Struct在函数间传递如果直接传递则是copy,想要改变其中的值,可以传递指针。

    pp := &Employee{1, ...}
    
    // It is equivalent to...
    pp := new(Employee)
    *pp = Employee{1, ...}
    

    Embedding机制

    Struct的embedding机制允许我们把struct作为匿名域放到另一个struct里面,省去了很多书写的麻烦:

    type Point struct {
        X, Y int
    }
    
    type Circle struct {
        Point
        Radius int
    }
    
    type Wheel struct {
        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 = 8
    w.Spokes = 20
    

    References:
    [1] Donovan, Alan AA, and Brian W. Kernighan. The Go programming language. Addison-Wesley Professional, 2015.
    [2] http://www.jb51.net/article/56828.htm

    更多文章请见 http://davidcorn.github.io

    相关文章

      网友评论

        本文标题:Go语言中的Array、Slice、Map、Set和Struct

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