go圣经复合数据结构 (个人纪录)
func complexstruct() {
//数组
//因为数组的长度是固定的,因此在Go语言中很少直接使用数组
//默认情况下,数组的每个元素都被初始化为元素类型对应的零值
var a [3]int = [3]int{1, 2, 3} //[3]int类型?
fmt.Println(a)
fmt.Println(len(a) - 1)
fmt.Println(a[1])
for index, value := range a {
fmt.Println(index, value)
}
//在数组字面值中,如果在数组的长度位置出现的是“...”省略号,则表示数组的长度是根据初始化值的个数来计算
b := [...]int{1, 2, 3, 4, 5, 6}
for i, v := range b {
fmt.Println(i, v)
}
//数组的长度是数组类型的一个组成部分,因此[3]int和[4]int是两种不同的数组类型
//数组的长度必须是常量表达式,因为数组的长度需要在编译阶段确定
//指定一个索引和对应值列表的方式初始化
//初始化索引的顺序是无关紧要的,而且没用到的索引可以省略,未指定初始值的元素将用零值初始化
d := [...]int{1: 1, 2: 2, 3: 3}
fmt.Println(d) //0处用默认0代替
//数组作为参数
//因为函数参数传递的机制导致传递大的数组类型将是低效的,并且对数组参数的任何的修改都是发生在复制的数组上,并不能直接修改调用时原始的数组变量
//不同于c,c++作为指针
//数组指针:ptr [32]byte 常规数组类型后面加
e := [...]byte{1, 2, 3, 5}
fmt.Println(e)
//虽然通过指针来传递数组参数是高效的,而且也允许在函数内部修改数组的值,但是数组依然是僵化的类型,因为数组的类型包含了僵化的长度信息。
//切片 slice
//它是可以增长和收缩的动态序列
//代表变长的序列,序列中每个元素都有相同的类型
//一个slice类型一般写作[]T,其中T代表slice中元素的类型
//一个slice由三个部分构成:指针、长度和容量
//指针指向第一个slice元素对应的底层数组元素的地址
//长度对应slice中元素的数目
//容量一般是从slice的开始位置到底层数据的结尾位置
//内置的len和cap函数分别返回slice的长度和容量
//多个slice之间可以共享底层的数据,并且引用的数组部分区间可能重叠
//slice的切片操作s[ i=0 : j=len(s) ],用于创建一个新的slice,引用s的从第i个元素开始到第j-1个元素的子序列
//范围:[i,j)
f := [13]int{}
for i := 0; i < 13; i++ {
f[i] = i
}
f1 := f[1:] //切片
fmt.Println(f1)
//如果切片操作超出cap(s)的上限将导致一个panic异常,但是超出len(s)则是意味着扩展了slice,因为新slice的长度会变大
//字符串的切片操作和[]byte字节类型切片的切片操作是类似的,都写作x[m:n]
//因为slice值包含指向第一个slice元素的指针,因此向函数传递slice将允许在函数内部修改底层数组的元素
//复制一个slice只是对底层的数组创建了一个新的slice别名
//slice类型的变量s和数组类型的变量a的初始化语法的差异
//但是对于slice并没有指明序列的长度,这会隐式地创建一个合适大小的数组,然后slice的指针指向底层的数组
g := []int{1, 2, 3, 4, 5} //slice 无需指定长度
h := [...]int{1, 2, 3, 4, 5} //array 需要指定长度
fmt.Println(g, "\n", h)
//slice之间不能比较
//必须自己展开每个元素进行比较
//原因:
//1.一个slice的元素是间接引用的,一个slice甚至可以包含自身,虽然有很多办法处理这种情形,但是没有一个是简单有效的。
//2.因为slice的元素是间接引用的,一个固定的slice值在不同的时刻可能包含不同的元素
//slice唯一合法的比较操作是和nil比较
//一个零值的slice等于nil,一个nil值的slice并没有底层数组,一个nil值的slice的长度和容量都是0
//但是也有非nil值的slice的长度和容量也是0的,例如[]int{}或make([]int, 3)[3:]
//可以用[]int(nil)类型转换表达式来生成一个对应类型slice的nil值, 这个值类型为[]int 类型的slice
if g == nil {
fmt.Println("compare with nil is ok ")
}
//需要测试一个slice是否是空的,使用len(s) == 0来判断,而不应该用s == nil来判断
if len(g) == 0 {
fmt.Println("g is empty")
}
//除了和nil相等比较外,一个nil值的slice的行为和其它任意0长度的slice一样;例如reverse(nil)也是安全的。
//除了文档已经明确说明的地方,所有的Go语言函数应该以相同的方式对待nil值的slice和0长度的slice
//make
//内置的make函数创建一个指定元素类型、长度和容量的slice。容量部分可以省略,在这种情况下,容量将等于长度
//语法 make([]T, len, cap=len) slice只引用了底层数组的前len个元素,但是容量将包含整个的数组。额外的元素是留给未来的增长用的
// make([]T, len) slice是整个数组的view
//在底层,make创建了一个匿名的数组变量,然后返回一个slice;只有通过返回的slice才能引用底层匿名的数组变量。
//append 用于向slice追加元素
//append函数则可以追加多个元素,甚至追加一个slice
fmt.Println(g)
g = append(g, 1) //追加元素
fmt.Println(g)
//copy 复制slice
//copy( new , old )
i := []int{}
fmt.Println(len(i), cap(i))
for j := 0; j < 10; j++ {
i = append(i, j)
fmt.Println(len(i), cap(i)) //容量不够翻倍
}
fmt.Println(i)
reverse(i)
fmt.Println(i)
//通常我们并不知道append调用是否导致了内存的重新分配
//因此我们也不能确认新的slice和原始的slice是否引用的是相同的底层数组空间
//同样,我们不能确认在原先的slice上的操作是否会影响到新的slice
//因此,通常是将append返回的结果直接赋值给输入的slice变量:
//更新slice变量不仅对调用append函数是必要的,实际上对应任何可能导致长度、容量或底层数组变化的操作都是必要的
//map 哈希表
//无序的key/value对
//map中所有的key都有相同的类型,所有的value也有着相同的类型,但是key和value之间可以是不同的数据类型。
//K对应的key必须是支持==比较运算符的数据类型,所以map可以通过测试key是否相等来判断是否已经存在
//V对应的value数据类型则没有任何的限制
//make函数可以创建一个map
ages1 := make(map[string]int)
//用map字面值的语法创建map,同时还可以指定一些最初的key/value
ages2 := map[string]int{
"alice": 31,
"charlie": 34,
"elysia": 18,
"hua": 55000,
}
//创建无元素map
ages3 := map[string]int{}
//创建空map
var ages4 map[string]int
fmt.Println(ages1, ages2, ages3)
//delete函数可以删除元素
delete(ages2, "alice")
//map中的元素并不是一个变量,因此我们不能对map的元素进行取址操作
//禁止对map元素取址的原因是map可能随着元素数量的增长而重新分配更大的内存空间,从而可能导致之前的地址无效
//遍历map 使用range风格的for循环实现
for k, v := range ages2 {
fmt.Println(k, v)
}
//Map的迭代顺序是不确定的,并且不同的哈希函数实现可能导致不同的遍历顺序
for name := range ages2 {
fmt.Println(name) //只拿到key
}
//空白标识符_来忽略第一个循环变量
for _, age := range ages2 {
fmt.Println(age) //只拿到value
}
//在实践中,遍历的顺序是随机的,每一次遍历的顺序都不相同
//可以通过对key排序,间接达到对value排序目的
//map类型的零值是nil,也就是没有引用任何哈希表
if ages1 == nil {
fmt.Println("ages1==nil")
} else {
fmt.Println("age1!=nil")
fmt.Println(len(ages1))
}
if ages4 == nil {
fmt.Println("ages4==nil")
} else {
fmt.Println("age4!=nil")
fmt.Println(len(ages4))
}
//map上的大部分操作,包括查找、删除、len和range循环都可以安全工作在nil值的map上,它们的行为和一个空的map类似。但是向一个nil值的map存入元素将导致一个panic异常
//查找
//通过key作为索引下标来访问map将产生一个value。如果key在map中是存在的,那么将得到与key对应的value;如果key不存在,那么将得到value对应类型的零值
//区分一个已经存在的0,和不存在而返回零值的0
//接受两个值,ok用于查看这个map索引是否存在
age, ok := ages4["bo"]
if ok {
fmt.Println("ok is true , val =", age)
} else {
fmt.Println("ok is false and val =", age, " , that is the 0 of this type")
}
//map之间也不能进行相等比较,唯一的例外是和nil进行比较
//要判断两个map是否包含相同的key和value,我们必须通过一个循环实现
//结构体
//type Employee struct {
// ID int
// Name string
// Address string
// DoB time.Time
// Position string
// Salary int
// ManagerID int
//}
//通常一行对应一个结构体成员,成员的名字在前类型在后,不过如果相邻的成员类型如果相同的话可以被合并到一行
//结构体成员的输入顺序不同,定义不同结构体
//如果结构体 成员 名字是以大写字母开头的,那么该成员就是导出的
//一个结构体可能同时包含导出和未导出的成员
//一个命名为S的结构体类型将不能再包含S类型的成员:因为一个聚合的值不能包含它自身
//但是S类型的结构体可以包含*S指针类型的成员
var dilbert Employee
var employeeOfTheMonth *Employee = &dilbert
employeeOfTheMonth.ID = 1 //注意此处指针不用->
k := EmployId(123)
fmt.Println(k)
//如果结构体没有任何成员的话就是空结构体,写作struct{}
//它的大小为0,也不包含任何信息,但是有时候依然是有价值的
//结构体值也可以用结构体字面值表示,结构体字面值可以指定每个成员的值。
//另一种初始化方式
l := Employee{ID: 1, Name: "el"} //按名字走,可以包含部分或者全部成员
//未初始化默认0
m := Employee{2, "ke", 3} //按顺序走,这种必须全部初始化
//不能企图在外部包中用第一种顺序赋值的技巧来偷偷地初始化结构体中未导出的成员
//大写
//如果考虑效率的话,较大的结构体通常会用指针的方式传入和返回
// a *employee
//结构体比较
//如果结构体的全部成员都是可以比较的,那么结构体也是可以比较的,那样的话两个结构体将可以使用==或!=运算符进行比较
//可比较的结构体类型和其他可比较的类型一样,可以用于map的key类型。
fmt.Println(l, m)
////结构体字面值必须遵循形状类型声明时的结构
//只有两种字面值初始方法
var n Whell = Whell{Circle: Circle{1, 1}, Spokes: 1}
var o Whell = Whell{Circle{1, 2}, 3}
fmt.Printf("%d %d %d", n.x, n.y, n.Spokes) //不用两次查找
//在访问子成员的时候可以忽略任何匿名成员部分
//结构体字面值并没有简短表示匿名成员的语法
fmt.Printf("%#f,%#f", o, n) //#打印每个成员名字
//因为匿名成员也有一个隐式的名字,因此不能同时包含两个类型相同的匿名成员,这会导致名字冲突
//因为成员的名字是由其类型隐式地决定的,所以匿名成员也有可见性的规则约束。
//匿名成员并不要求是结构体类型;其实任何命名的类型都可以作为结构体的匿名成员
//为什么要嵌入一个没有任何子成员类型的匿名成员类型呢
//匿名类型的方法集,简短的点运算符语法可以用于选择匿名成员嵌套的成员,也可以用于访问它们的方法
//外层的结构体不仅仅是获得了匿名成员类型的所有成员,而且也获得了该类型导出的全部的方法
}