Slice切片
slice是这样的结构:先创建一个有特定长度和数据类型的底层数组,然后从这个底层数组中选取一部分元素,返回这些元素组成的集合(或容器),并将slice指向集合中的第一个元素。换句话说,slice自身维护了一个指针属性,指向它底层数组中的某些元素的集合。
声明和初始化
声明:
var a []int
初始化:
a := new([]int)
fmt.Println(*a)
b := make([]int, 0, 5)
fmt.Println(b)
当声明一个slice,但不做初始化的时候,这个slice就是一个nil slice,这个slice不会指向哪个底层数组。初始化后的slice有指向的底层数组,只不过指向的底层数组暂时是长度为0的空数组。
make()比new()函数多一些操作,new()函数只会进行内存分配并做默认的赋0初始化,而make()可以先为底层数组分配好内存,然后从这个底层数组中再额外生成一个slice并初始化。另外,make只能构建slice、map和channel这3种结构的数据对象,因为它们都指向底层数据结构,都需要先为底层数据结构分配好内存并初始化。
初始化时直接赋初值:
// 创建长度和容量都为4的slice,并初始化赋值
color_slice := []string{"red","blue","black","green"}
// 创建长度和容量为100的slice,并为第100个元素赋值为3
slice := []int{99:3}
slice能被访问的元素只有length范围内的元素,那些在length之外,但在capacity之内的元素暂时还不属于slice,只有在slice被扩展时(见下文append),capacity中的元素才被纳入length,才能被访问。
可以通过len()函数获取slice的长度,通过cap()函数获取slice的Capacity。
my_slice := make([]int,3,5)
fmt.Println(len(my_slice)) // 3
fmt.Println(cap(my_slice)) // 5
对slice切片
可以从slice中继续切片生成一个新的slice,这样能实现slice的缩减。
对slice切片的语法为:
SLICE[A:B]
SLICE[A:B:C]
其中A表示从SLICE的第几个元素开始切,B控制切片的长度(B-A),C控制切片的容量(C-A),如果没有给定C,则表示切到底层数组的最尾部。
还有几种简化形式:
SLICE[A:] // 从A切到最尾部
SLICE[:B] // 从最开头切到B(不包含B)
SLICE[:] // 从头切到尾,等价于复制整个SLICE
单元测试
打桩
mock一个函数
// json.NetworkCompute 为方法名
patches := gomonkey.ApplyFunc(json.NetworkCompute, func(a, b int) (int,error){
return 2, nil
})
defer patches.Reset()
mock 一个方法
// CanalNetConfEx 是方法所在的结构体
// getPodID 是方法名
patches := gomonkey.ApplyFunc((*CanalNetConfEx).getPodID, func(_ *CanalNetConfEx) (string, error) {
return "123", nil
})
defer patches.Reset()
数组
数组长度固定,所以数组很少被使用。
初始化:
// 数组的长度必须是常量表达式
// 数组的长度是数组类型的一部分,所以 [3]int 和 [4]int 是两种数据类型
q := [3]int{1, 2, 3}
// 如果省略号出现在数组长度的位置,那么数组的长度由初始化数组的元素个数决定
p := [...]int{1, 2, 3, 4}
// 定义了一个长度为100的数组,q[99]为1,其余为int的默认值0
q := [...]int{99: 1}
如果一个数组的元素类型可比较,那么这个数组就可以用 == 和 != 比较,当两边元素完全相同时返回true。
数组在传入一个函数时,函数接受的是一个副本,而不是地址(与java不同)。
slice
slice是可变长度的序列,slice包含三个属性:指针、长度和容量,len和cap方法可以返回slice的长度和容量,指针直接指向底层数组,所以对slice中的元素修改可以改变底层数组的数据。
slice底层数组的元素是间接引用的,但是指针、长度和容量不是。
初始化:
p := [...]int{1, 2, 3, 4}
// s1指向 p[1]~p[2]
s1 := p[1:3]
s2 := p[1:]
s3 := p[:3]
s4 := p[:]
// 指针为nil,实际上没有初始化
var s []int
// 指针不为nil
s := []int{}
//创建一个长度和容量都为3的slice
p := make([]int, 3)
//创建一个长度为3容量为6的slice
p := make([]int, 3, 6)
slice无法直接用 == 和 != 做比较。slice唯一允许的比较操作是和nil做比较。
想检查一个slice是否为空,使用 len(s) == 0 ,而不是 s == nil ,因为 s != nil 的情况下,slice也有可能是空(指针指向了空数组)。
append函数可以用来将元素追加到slice的后面。
s = append(s, 1)
append时,如slice长度不够,但容量足够,则不会改变指针地址,而是在底层数组基础上做更改,所以会改变原数组的值。
如果容量不足,则新建一个底层数组,将元素复制到这个数组,并将指针指向新地址,然后就和原来的底层数组没关系了。
每次容量扩展时,默认扩展一倍。
为了从slice的中间移除一个元素,并保留剩余元素的顺序,可以使用函数copy来将高位索引的元素向前移动来覆盖被移除元素所在的位置:
copy(slice[i:], slice[i+1:])
map
键的类型,必须是可以通过 == 来进行比较的数据类型
初始化
ages := make(map[string]int)
ages := map[string]int{}
ages := map[string]int{
"郭哥": 18,
"马哥": 98,
}
移除元素
delete(ages, "郭哥")
map元素不是一个变量,不可以获取它的地址。
map不可比较,唯一合法的比较就是和nil做比较。
结构体
type Man struct {
age int
name string
}
//结构体方法
func (m Man) getName() string {
return m.name
}
结构体的成员变量名称首字母大小写决定了这个变量是否可导出(可导出代表可以在别的包访问)。
结构体不可以拥有相同类型的结构体成员变量,但是可以拥有该结构体类型的指针。
初始化:
man1 := new(Man)
// 通过指定部分或全部成员变量的名称和值来初始化结构体变量:
man1 := Man{age : 1, name : "郭哥"}
man1 := &Man{age : 1, name : "郭哥"}
如果结构体的所有成员变量都可以比较,那么这个结构体就可以使用 == 或者 != 比较,并且可以作为map的键类型。
匿名成员:没有指定成员的名称,只指定成员的类型,是一种内嵌的结构体
type Student struct {
Man
*Animal
school string
}
匿名成员初始化:
student1 := &student{
Man: Man{
age: 1,
name: "李雷",
},
school: "浙江农林大学",
}
访问匿名成员的成员变量不需要指定匿名成员的名称:
fmt.Println(student1.age)
同样的,访问匿名成员的方法不需要指定匿名成员的名称:
fmt.Println(student1.getName())
因为匿名成员以类型名称为自己的名字,所以不能在一个结构体里定义两个相同类型的匿名成员。同理,匿名成员的可导出性也取决于该类型名称首字母是否大写。
函数
func initEniNeutron(req *ntk.ReqNtkContent) (logLevel string, message error) {
return "abc", nil
}
实参是按值传递的,所以函数接收到的是每个实参的副本。
函数的类型称作函数签名。当两个函数拥有相同的形参列表和返回列表时,认为这两个函数的类型或签名是相同的。
函数也是一种变量,它们可以赋给变量或者传递或者从其他函数中返回。
函数类型的零值为nil,函数变量可以和空值相比较,但函数变量之间不可比较,
匿名函数:
abc := func(req *ntk.ReqNtkContent) (logLevel string, message error) {
return "abc", nil
}
变长函数:
abc := func(names ...string) {
return "abc", nil
}
在参数列表最后的类型名称之前使用省略号表示声明一个变长函数,调用这个函数的时候可以传递该类型任意数目的参数、
在函数体内,names是一个slice,向该函数传参的时候可以传slice数组,只需要在参数后面放一个省略号:
names := []string{"abc", "cde"}
abc(names...)
延迟函数调用:
defer f.close()
defer的表达式推迟到该函数执行完毕时,以调用defer语句顺序的倒序执行。
return操作分为 赋值+返回 两步,defer在赋值与返回间执行:
func test3() int {
var i int
defer i++
return i
}
//等价
//func test3() int {
// var i int
// ret:=i
// defer i++
// return ret
//}
延迟执行的匿名函数可以观察到函数的返回结果。
当宕机发生时,正常的程序执行会终止,goroutine中的所有延迟函数会执行。
方法
func (p Point) initEniNeutron(req string) error {
return nil
}
// 指针接收者
func (p *Point) initEniNeutron(req string) error {
return nil
}
结构体的方法和字段来自于同一个命名空间,所以方法名和字段名不能相同。
方法可以绑定在同一个包下的任何一种类型上(除指针和接口以外),可以方便地为简单的类型定义附加的行为。
方法和函数一样,会像复制实参变量一样复制接收者,如果想避免复制过大的接收者,或者需要更新一个接收者,可以使用指针接收者。
习惯上遵循如果Point的任何一个方法使用指针接收者,那么所有的Point方法都应该使用指针接收者。
和函数一样,方法也可以赋值给一个变量,但是调用的时候必须提供接收者,并且接收者替换成函数的第一个形参:
type1 := type2.getB
ff := &type2{}
type1(*ff)
方法的首字母是否大写也决定了方法是否被封装。