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

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 162,475评论 4 372
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,744评论 2 307
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 112,101评论 0 254
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,732评论 0 221
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 53,141评论 3 297
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 41,049评论 1 226
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,188评论 2 320
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,965评论 0 213
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,716评论 1 250
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,867评论 2 254
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,341评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,663评论 3 263
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,376评论 3 244
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,200评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,990评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,179评论 2 285
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,979评论 2 279

推荐阅读更多精彩内容