这是我参与「第五届青训营 」伴学笔记创作活动的第 4 天
今天和大家分享go基础的第二部分。主要包括切片,map,结构体和指针的概念和常见操作。
切片
切片是对数组的一个连续片段的引用,所以切片是一个引用类型,sllice会将数组作为其底层的数据结构,只是没有固定长度。
声明一个切片:
var slice []int //声明整形切片
var numListEmpty []int{} //声明一个空切片
//make函数构造一个切片
numList := make([]int, 3, 5) //表明类型是int型数组,后两个参数切片的长度,切片的容量
// 此时底层会开辟一段数组内存,其长度为5,但是可以使用的只有前三个元素,要使用后面两个需要进行扩容操作,否则会panic
// 从数组中截取切片
arr := [5]int{1, 2, 3, 4, 5}
slcie := arr[2:4] //若是[:]则代表全部的元素
slice2 := arr[2:4:2] //表示切片取数组[2,4), 其容量为2,否则其容量会根据底层数组的截取大小设置为3
fmt.Println(slcie)
//[3 4]
若没有对引用类型进行赋值,那么其值默认为nil
切片的扩容:
slice := []int{1, 2} // 此时切片的长度和容量相同,都是2
slice = append(slice, 1) // 我们使用追加函数,由于元素被追加进来时没有多余的空间了,所以进行扩容操作
fmt.Println(slice) // 扩容后的切片的容量为4,长度为3
//[1 2 1]
这里重点说一下扩容时的底层操作:
- 将原切片的值复制
- 生成一个原切片值两倍容量的底层数组,并将切片和追加的元素放进去
- 返回数组的引用
可以看到扩容时并不是在原有数组上进行操作,而是重新生成了一个长度为数组两倍的数组作为新的底层数组,其地址已经不同了。所以我们可以说扩容后的切片已经不等于原切片了。我们可以进行以下实验:
func main() {
a1 := []string{"1", "2"}
a2 := append(a1, "3") // 进行扩容,扩容后的底层数组长度为4,即切片容量为4
a3 := append(a2, "4") // 不需要扩容
a3[0] = "3"
fmt.Println(a1, a2, a3) // [1 2] [3 2 3] [3 2 3 4]
}
可以看到,当改变a3时,a2变化了,a1没有变化,说明a1的底层数组和a2,a3已经不是同一个引用了。
对切片的复制
a := []int{1, 2}
b := []int{3}
copy(a, b)
fmt.Println(a)
// 3 2
copy(a[1:], b) // 可以指定从确定下标开始向a复制b的值
fmt.Println(a)
// 3 3
在函数中使用切片:
func main() {
s := []int{1, 2, 3} // 创建了底层数组并创建一个切片指向它
func(myStr []int) {
myStr[1] = 3 // myStr是对s的复制,但同时两者都指向一个底层数组
}(s)
fmt.Println(s) // [1 3 3] 说明改变myStr其实就是改变底层数组,相当于也影响了s
}
利用方法和切片实现各种功能(例如排序)
我们可以在Go语言中声明底层为切片或者数组的类型,并为其绑定相应的方法。跟其他语言的类(class)相比,Go语言在类型之上声明方法的能力无疑更为通用。
例如标准库的sort包声明了StringSlice类型:type StringSlice []String并且该类型还关联了方法:func (p StringSlice) sort()
为了按照字母顺序对某一个切片排序,我们就可以利用上面的sort方法:
func main() {
s := []string{"a", "c", "b"} // 创建了底层数组并创建一个切片指向它
sort.StringSlice(s).Sort() // 将s变为StringSlice类型之后进行排序 这里sort包提供了辅助函数简化操作:sort.Stringss(s)
fmt.Println(s) // a b c
}
Map
通过字面值创建Map:
mapOne := map[string]string{
"a": "A",
"b": "B",
}
fmt.Println(mapOne)
//map[a:A b:B]
通过make创建Map:
mapTwo := make(map[string]string)
mapTwo["a"] = "cat"
fmt.Println(mapTwo)
// map[a:cat]
注意map是无序的,并且map是一个引用类型
添加和删除map,判断是否存在某个键值对:
mapOne := map[string]string{
"a": "A",
"b": "B",
}
mapOne["c"] = "C" //插入
delete(mapOne, "a")
fmt.Println(mapOne)
value, ok := mapOne["c"]
fmt.Printf("%s %t\n", value, ok)
/*map[b:B c:C]
C true*/
虽然map是无序的,但是我们仍然可以进行遍历map,我们可以使用for range语法:
for key, value := range mapOne {
fmt.Println(key, value)
}
/*
c C
b B
*/
更多range的参考可以查看:www.runoob.com/go/go-range…
结构体
声明一个结构体
type Person struct {
name string //名称
age int //年龄
target string //目标
behave string //行为
}
我们在使用的时候就可以采用
xiaoming := Person{
name: "xiaoming",
age: 10,
target: "xx",
behave: "yy",
}
println(xiaoming.name)
同时,我们也可以这样:
xiaoming := Person{"xiaoming", 19, "xx", "yy"} //必须按照顺序
我们还可以先声明该结构体的变量后进行赋值:
type Person struct {
name string //名称
age int //年龄
target string //目标
behave string //行为
}
xiaoming := Person{}
xiaoming.name = "123"
匿名结构体
xiaoming := struct {
name string //名称
age int //年龄
target string //目标
behave string //行为
}{
name: "xiaoming",
age: 10,
target: "xx",
behave: "yy",
}
fmt.Println(xiaoming) //{xiaoming 10 xx yy}
结构体指针
结构体声明的变量指针比较特殊,它可以直接代替变量去访问属性:
type Person struct {
name string //名称
age int //年龄
target string //目标
behave string //行为
}
ptr := &Person{
name: "xiaoming",
age: 18,
target: "xx",
behave: "yy",
}
fmt.Println((*ptr).age, ptr.age) // 18 18
这里其实是统一了C语言中的指针使用->访问属性和变量使用.访问属性的形式,统一使用.
结构体转发
结构体的嵌套和字段提升:
type Other struct {
x int
y int
}
type Person struct {
name string
age int
other Other
}
s := Person{
name: "xiaoming",
age: 18,
other: Other{
x: 1,
y: 2,
},
}
fmt.Println(s.other.x) // 这里我们访问x需要先访问other
此时,我们若将Other定义为匿名字段的类型,则会出现字段提升,我们访问Other内部的字段可以直接使用Person,这种特性称为转发:
type Other struct {
x int
y int
}
type Person struct {
name string
age int
Other
}
s := Person{
name: "xiaoming",
age: 18,
Other: Other{
x: 1,
y: 2,
},
}
fmt.Println(s.x) //注意我们这里使用的是s.x,不必再去访问Other
同时,这种匿名字段也可以实现对方法的转发,给结构体添加方法:
type Person struct {
name string
age int
}
func (p Person) ShowFullName(str string) (fullName string) {
fullName = str + p.name
return
}
func main() {
s := new(Person)
s.name = "ming"
name := s.ShowFullName("xiao")
fmt.Println(name) // xiao ming
}
指针
创建一个指针:
func main() {
str := "hello"
ptr := &str
fmt.Println(ptr, *ptr)
}
//0xc000010250 hello
注意,go语言中的指针不支持例如ptr++这样的运算(只能使用ptr + 1来找到指针后的地址)
解引用
指针为数组和结构体提供了可以自动解引用的操作,即(&arr)[0]可以直接使用arr[0]代替:
arr := [...]int{1, 2}
arrPointer := &arr
fmt.Println(arr[0], arrPointer[0]) // 1 1
需要注意,虽然go给数组提供了自动解引用的操作,但是并没有为切片和映射提供自动解引用的特性。