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
网友评论