参考书《Go程序设计语言》,Go学习路径的Go基础模块,整理了一些我掌握不太好的地方
数组和结构体都是聚合类型,他们的值由内存中的一组变量构成。数组元素相同,结构体元素可以不同,他们的长度都是固定的。slice和map都是动态数据结构,他们的长度随着元素的添加动态增长。
-
数组
-
在数组字面量中,如果省略号…出现在数组长度的位置,那么数组的长度由初始化的数组的元素个数决定。
q := [...]int{1,2,3} fmt.Println("%T\n",q) //"[3]int"
-
数组的长度是数组类型的一部分,[3]int和[4]int是两种不同的数组类型,数组的长度必须是常量表达式,在编译时就确定。
q := [3]int{1,2,3} q = [4]int{1,2,3,4} //编译错误
-
上面是按顺序给出一组值,也可以给出索引和索引对应的值,这样索引可以以任意顺序出现,还可以省略,省略的会被赋为零值。
type Currency int const ( USD Currency = iota RMb ) symbol := [...]string{USD:"$",RMB:"¥"} fmt.Println(RMB,symbol[RMB]) //"3 ¥" r := [...]int{99:-1} //第100个-1,其余0
-
如果一个数组的元素类型是可比较的,那么这个数组也是可比较的。
a := [2]int{1,2} b := [...]int{1,2} c := [2]int{1,3} fmt.Println(a==b,a==c,b==c) //"true false false" d := [3]int{1,2} fmt.Println(a==d) //编译错误
-
当调用函数时,传入的参数会创建一个副本,然后赋值给对应的函数变量,所以函数接受的是一个副本而不是原始的参数。这种方式在传递大数组的时候会很低效。这种情况下,go把数组和其他类型都看成值传递,而在其他语言中,数组是隐式的使用引用传递。
也可以显示传递一个数组的指针,这样在函数内部对数组的任何修改都会反映到原始数组上面。
func zero(ptr *[32]byte) { /*for i := range ptr { ptr[i] = 0 }*/ *ptr = [32]byte{} }
-
-
slice
-
slice表示拥有相同类型元素的可变长度序列,通常写成[]T,数组和slice是紧密关联的,slice可以访问数组的部分或全部的元素,而这个数组成为slice的底层数组。
-
slice有三个属性:指针,长度,容量。指针指向数组的第一个可以从slice访问的元素,长度指slice元素个数,它不能超过容量,容量的大小通常是从slice的起始元素到底层数组最后一个元素间的元素个数。
-
一个底层数组可以对应多个slice,这些slice可以引用数组任何位置,彼此还可重叠。
months := [...]string{1:"January",...,12:"December"} summer := months[6:9] "6,7,8" endlessSummer := summer[:5] "6,7,8,9,10"
-
初始化slice不指定长度,创建指向数组的slice。和数组不同,slice无法比较,但可以使用bytes.Equal来比较两个字节slice,但对于其他类型slice需要自己写函数比较。slice允许和nil比较,值为nil的slice没有对应的底层数组
s := []int{1,2,3,4,5}
-
内置函数make可以创建一个具有指定元素类型,长度,容量的slice,其中容量参数可以省略,这种情况下容量等于长度。
make([]T,len) make([]T,len,cap) //等同于make([]T,cap)[:len]
其实make是创建了一个无名数组并返回了它的一个slice,这个数组仅可以通过这个slice访问,容量就是数组的长度。
-
append函数
var x []int x = append(x,1) x = append(x,2,3) x = append(x,4,5,6) ... fmt.Println(x) //"[1,2,3,4,5,6]"
当容量不足时,会创建一个新的数组并指向它。
-
-
map
-
go语言中,map是散列表的引用。内置函数make创建一个map:
ages := make(map[string]int)
-
也可以使用map字面量。
ages := map[string]int { "alice":31, "charlie":34, } //等价于 ages := make(map[string]int) ages["alice"] = 31 ages["charlie"] = 34
-
可以使用内置函数delete移除一个元素。
delete(ages,"alice")
-
如果map中没有某个键,使用时返回类型零值。
ages["bob"] = ages["bob"] + 1 ages["bob"] += 1 ages["bob"]++ //三者等价
-
map元素不是一个变量,无法获取地址,因为map的可增长性导致每一个元素的地址是动态的,这样就可能使获得的地址无效。
-
map中元素迭代是无序的,如果需要按照某种顺序遍历map中的元素,必须显式的给键排序,也就是拿个切片存键,对切片排序,再通过切片遍历键值。
-
-
结构体
-
聚合类型不可以包含他自己,但是命名结构体中可以定义一个它的指针,这样我们可以创建一些递归数据结构,比如链表和树。
type tree struct { value int left,right *tree //二叉树 }
-
结构体类型的值可以通过结构体字面量设置,即通过设置结构体的成员变量来设置。
type Point struct {X,Y int} p := Point{1,2} q := Point{X:1,Y:2}
-
由于结构体都是通过指针的方式使用,因此可以使用一种简单的方式创建初始化一个struct类型的变量并获取他的地址。
pp := &Point{1,2} //等价于: pp := new(Point) *pp = Point{1,2}
-
如果结构体所有变量成员都可以比较,那么这个结构体也可以使用或者!=比较,其中按照顺序比较成员变量,可比较的结构体可以作为map的键类型。
p := Point{1,2} q := Point{2,1} fmt.Println(p.X == q.X && p.Y == q.Y) //"false" //等价于: fmt.Println(p == q) //"false"
-
Go允许定义不带名称的结构体成员,只需要指定类型即可,这种结构体成员称作匿名成员,匿名成员必须是一个命名类型或命名类型的指针。
type Point struct { X,Y int } type Circle struct { Point Radius int } type Wheel struct { Circle Spokes int } var w wheel w.X = 8 //等价于w.Circle.Point.X = 8 w.Y = 8 //等价于w.Circle.Point.Y = 8,如果是circle,point那么在包外不能这么用 w.Radius = 5 //等价于w.Circle.Radius = 5 w.Spokes = 20 //但是不能这么干: w = Wheel{8,8,5,20} //编译错误,未知成员变量 w = Wheel{X:8,Y:8,Radius:5,Spokes:20} //编译错误,未知成员变量 //可以这样: w = Wheel{Circle{Point{8,8},5},20} //或者这样 w = Wheel{ Circle:Circle{ Point:Point{X:8,Y:8}, Radius:5, //逗号必须有 }, Spokes:20, //逗号必须有 } fmt.Printf("%#v\n",w) //"Wheel{Circle:Circle{Point:Point{X:8,Y:8},Radius:5},Spokes:20}" //%#v格式带着X:,Y:这些东西
-