夜雪天狼
学习笔记
技术博文
转载备份
心灵鸡汤
目录
go核心技术
发布者:caijw
阅读量:64945
发布时间:2018-10-21 09:00:00
# 数组 -vertical- ## 定义数组 ```go var arr [n]type //在[n]type中,n表示数组的长度,type表示存储元素的类型。对数组的操作和其它语言类似,都是通过[]来进行读取或赋值: var arr [10]int // 声明了一个int类型的数组 arr[0] = 42 // 数组下标是从0开始的 arr[1] = 13 // 赋值操作 fmt.Printf("The first element is %d\n", arr[0]) // 获取数据,返回42 fmt.Printf("The last element is %d\n", arr[9]) //返回未赋值的最后一个元素,默认返回0 //定义并赋值 a := [3]int{1, 2, 3} // 声明了一个长度为3的int数组 b := [10]int{1, 2, 3} // 声明了一个长度为10的int数组,其中前三个元素初始化为1、2、3,其它默认为0 c := [...]int{4, 5, 6} // 可以省略长度而采用`...`的方式,Go会自动根据元素个数来计算长度 var x = [3]int{1,2} //注:不是 var x [3]int = {1,2} //定义了以后,只能通过[]的方式一个个赋值 ``` Comment: 1、由于长度也是数组类型的一部分,因此[3]int与[4]int是不同的类型,数组也就不能改变长度 2、数组之间的赋值是值的赋值,即当把一个数组作为参数传入函数的时候,传入的其实是该数组的副本,而不是指针(引用) -vertical- ## 多维数组 ```go // 声明了一个二维数组,该数组以两个数组作为元素,其中每个数组中又有4个int类型的元素 doubleArray := [2][4]int{[4]int{1, 2, 3, 4}, [4]int{5, 6, 7, 8}} // 上面的声明可以简化,直接忽略内部的类型 easyArray := [2][4]int{{1, 2, 3, 4}, {5, 6, 7, 8}} ``` Comment: 在很多应用场景中,数组并不能满足我们的需求。在初始定义数组时,我们并不知道需要多大的数组,因此我们就需要“动态数组”。在Go里面这种数据结构叫slice -separator- # slice slice并不是真正意义上的动态数组,而是一个引用类型。slice总是指向一个底层array,slice的声明也可以像array一样,只是不需要长度 -vertical- ```go // 和声明array一样,只是少了长度 var fslice []int slice := []byte {'a', 'b', 'c', 'd'} // 声明一个含有10个元素元素类型为byte的数组 var ar = [10]byte {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'} // 声明两个含有byte的slice var a, b []byte // a指向数组的第3个元素开始,并到第五个元素结束, a = ar[2:5] //[2,5) //现在a含有的元素: ar[2]、ar[3]和ar[4] // b是数组ar的另一个slice b = ar[3:5] // b的元素是:ar[3]和ar[4] ``` -vertical- ## 它们的数据结构如下所示  -vertical- ## slice有一些简便的操作 slice的默认开始位置是0,ar[:n]等价于ar[0:n] slice的第二个序列默认是数组的长度,ar[n:]等价于ar[n:len(ar) 如果从一个数组里面直接获取slice,可以这样ar[:],因为默认第一个序列是0,第二个是数组的长度,即等价于ar[0:len(ar)] -vertical- ```go // 声明一个数组 var array = [10]byte{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'} // 声明两个slice var aSlice, bSlice []byte // 演示一些简便操作 aSlice = array[:3] // 等价于aSlice = array[0:3] aSlice包含元素: a,b,c aSlice = array[5:] // 等价于aSlice = array[5:10] aSlice包含元素: f,g,h,i,j aSlice = array[:] // 等价于aSlice = array[0:10] 这样aSlice包含了全部的元素 // 从slice中获取slice aSlice = array[3:7] // aSlice包含元素: d,e,f,g,len=4,cap=7 bSlice = aSlice[1:3] // bSlice 包含aSlice[1], aSlice[2] 也就是含有: e,f bSlice = aSlice[:3] // bSlice 包含 aSlice[0], aSlice[1], aSlice[2] 也就是含有: d,e,f bSlice = aSlice[0:5] // 对slice的slice可以在cap范围内扩展,此时bSlice包含:d,e,f,g,h bSlice = aSlice[:] // bSlice包含所有aSlice的元素: d,e,f,g ``` Comment: slice是引用类型,所以当引用改变其中元素的值时,其它的所有引用都会改变该值,例如上面的aSlice和bSlice,如果修改了aSlice中元素的值,那么bSlice相对应的值也会改变 -vertical- ## 深入理解 从概念上面来说slice像一个结构体,这个结构体包含了三个元素: 1. 一个指针,指向数组中slice指定的开始位置 2. 长度,即slice的长度 3. 最大长度,也就是slice开始位置到数组的最后位置的长度 ```go Array_a := [10]byte{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'} Slice_a := Array_a[2:5] //上面代码的真正存储结构如下图所示 ```  -vertical- 对于slice有几个有用的内置函数: * len 获取slice的长度 * cap 获取slice的最大容量 * append 向slice里面追加一个或者多个元素,然后返回一个和slice一样类型的slice * copy 函数copy从源slice的src中复制元素到目标dst,并且返回复制的元素的个数 Comment: 注:append函数会改变slice所引用的数组的内容,从而影响到引用同一数组的其它slice。 但当slice中没有剩余空间(即(cap-len) == 0)时,此时将动态分配新的数组空间。返回的slice数组指针将指向这个空间,而原数组的内容将保持不变;其它引用此数组的slice则不受影响。 -separator- # 默认值 与php不同的是,变量未定义时不是null或其他的特殊值,而是该类型的"零值(初始值)" ```go int 0 int8 0 int32 0 int64 0 uint 0x0 rune 0 //rune的实际类型是 int32 byte 0x0 // byte的实际类型是 uint8 float32 0 //长度为 4 byte float64 0 //长度为 8 byte bool false string "" ``` Comment: 这个问题要特别注意,尤其是int的0和未定义 # 结构体 -vertical- ## 结构体的定义 Go语言结构体数据类是将各个类型的变量定义的集合,通常用来表示记录 ```go package main import "fmt" // 这个person结构体有name和age成员 type person struct { name string age int } func main() { // 这个语法创建一个新结构体变量 fmt.Println(person{"Bob", 20}) // 可以使用"成员:值"的方式来初始化结构体变量 fmt.Println(person{name: "Alice", age: 30}) // 未显式赋值的成员初始值为零值 fmt.Println(person{name: "Fred"}) // 可以使用&来获取结构体变量的地址 fmt.Println(&person{name: "Ann", age: 40}) // 使用点号(.)来访问结构体成员 s := person{name: "Sean", age: 50} fmt.Println(s.name) // 结构体指针也可以使用点号(.)来访问结构体成员 // Go语言会自动识别出来 sp := &s fmt.Println(sp.age) // 结构体成员变量的值是可以改变的 sp.age = 51 fmt.Println(sp.age) } ``` -vertical- ## 方法 一般的函数定义叫做函数,定义在结构体上面的函数叫做该结构体的方法 ```go package main import "fmt" type rect struct { width, height int } // 这个area方法有一个限定类型*rect, // 表示这个函数是定义在rect结构体上的方法 func (r *rect) area() int { return r.width * r.height } // 方法的定义限定类型可以为结构体类型 // 也可以是结构体指针类型 // 区别在于如果限定类型是结构体指针类型 // 那么在该方法内部可以修改结构体成员信息 func (r rect) perim() int { return 2*r.width + 2*r.height } func main() { r := rect{width: 10, height: 5} // 调用方法 fmt.Println("area: ", r.area()) fmt.Println("perim:", r.perim()) // Go语言会自动识别方法调用的参数是结构体变量还是 // 结构体指针,如果你要修改结构体内部成员值,那么使用 // 结构体指针作为函数限定类型,也就是说参数若是结构体 //变量,仅仅会发生值拷贝。 rp := &r fmt.Println("area: ", rp.area()) fmt.Println("perim:", rp.perim()) } ``` -vertical- ## struct的匿名字段 上面介绍了如何定义一个struct,定义的时候是字段名与其类型一一对应,实际上Go支持只提供类型,而不写字段名的方式,也就是匿名字段,也称为嵌入字段 当匿名字段是一个struct的时候,那么这个struct所拥有的全部字段都被隐式地引入了当前定义的这个struct ```go package main import "fmt" type Human struct { name string age int weight int } type Student struct { Human // 匿名字段,那么默认Student就包含了Human的所有字段 speciality string } func main() { // 我们初始化一个学生 mark := Student{Human{"Mark", 25, 120}, "Computer Science"} // 我们访问相应的字段 fmt.Println("His name is ", mark.name) fmt.Println("His age is ", mark.age) fmt.Println("His weight is ", mark.weight) fmt.Println("His speciality is ", mark.speciality) // 修改对应的备注信息 mark.speciality = "AI" fmt.Println("Mark changed his speciality") fmt.Println("His speciality is ", mark.speciality) // 修改他的年龄信息 fmt.Println("Mark become old") mark.age = 46 fmt.Println("His age is", mark.age) // 修改他的体重信息 fmt.Println("Mark is not an athlet anymore") mark.weight += 60 fmt.Println("His weight is", mark.weight) } ``` Comment: Student访问属性age和name的时候,就像访问自己所有用的字段一样,能够实现字段的继承 如果human里面有一个字段叫做phone,而student也有一个字段叫做phone,那么该怎么办呢? Go里面很简单的解决了这个问题,最外层的优先访问,也就是当你通过student.phone访问的时候,是访问student里面的字段,而不是human里面的字段 -vertical- ```go package main import "fmt" type Human struct { name string age int phone string // Human类型拥有的字段 } type Test struct { name string age int phone string // Human类型拥有的字段 } type Employee struct { Human // 匿名字段Human Test speciality string phone string // 雇员的phone字段 } func main() { Bob := Employee{Human{"Bob", 34, "777-444-XXXX"},Test{"Bob", 34, "777-444-XXXX"}, "Designer", "333-222"} fmt.Println("Bob's work phone is:", Bob.phone) // 如果我们要访问Human的phone字段 fmt.Println("Bob's personal phone is:", Bob.Human.phone) // 如果我们要访问Test的phone字段 fmt.Println("Bob's personal phone is:", Bob.Test.phone) } ``` Comment: 如果删除Employee的phone,则不能通过Bob.phone获取,因为不知道获取哪一个 -vertical-  -separator-