Go语言学习-部分(6) 切片 map
切片(Slice):
引言:
- 之前学习的数组限制性很多,首先长度也是数组类型的一部分,这样使得定义出来的数组只能接收固定长度的数组
- 另外如果定义了一个数组
var s = [3]{1,2,3}
这样数组s已经有三个值了,没有办法在向里面添加值.
切片:
切片(Slice)是一个拥有相同类型元素的可变长度的序列
。它是基于数组
类型做的一层封装。它非常灵活,支持自动扩容
。
(简单的,我们就可以把它理解成数组切割一部分变成的,方便记忆)
切片是一个引用类型
,它的内部结构包含地址、长度和容量。切片一般用于快速地操作一块数据集合。
声明切片类型的基本语法如下:
var a = []T
其中
- name:表示变量名
- T:表示切片中的元素类型
注
:用法基本和数组类似,但是[]里面没有东西.
初始化:与数组类似
var a = []int{1,2,3}
var b = []string{"大大","小小","钱钱"}
切片的长度和容量
切片拥有自己的长度和容量,我们可以通过使用内置的len()
函数求长度,使用内置的cap()
函数求切片的容量。
由数组得到切片:
var a = [...]int {1,2,3,4,5,6}//定义一个数组
s := a[0:4]//通过数组切割,左包含右不包含1,2,3,4
fmt.Println(s)
还可以这样截:
a[1:]//从前面索引截到最后
a[:4]//从后面这个索引截到前面
a[:]//截取所有
切片再切片
- 切片的长度就是元素的个数
- 而切片的容量是他底层数组的容量,准确的说是从切片地第一个索引到底层数组的最后一个索引.
make函数创建切片
s1 := make([]int,5,10) //make(类型,长度,容量(不写默认和长度一致))
//此时内容全是0,没有初始化(赋值)默认都是0
切片的本质:
- 切片就是一个框,框住了一块连续的内存
- 切片属于引用类型,真正的数据都是保存在底层数组里
如果切片是nil的那么它长度和容量一定是0;
但如果她的长度和容量是0,切片并不一定是nil;
var s1 []int //len(s1)=0;cap(s1)=0;s1==nil
s2 := []int{} //len(s2)=0;cap(s2)=0;s2!=nil
s3 := make([]int, 0) //len(s3)=0;cap(s3)=0;s3!=nil
判断切片是否为空的需要用len()判断,而不能s==nil
切片的赋值拷贝:
下面的代码中演示了拷贝前后两个变量共享底层数组,对一个切片的修改会影响另一个切片的内容,这点需要特别注意。
func main() {
s1 := make([]int, 3) //[0 0 0]
s2 := s1 //将s1直接赋值给s2,s1和s2共用一个底层数组
s2[0] = 100
fmt.Println(s1) //[100 0 0]
fmt.Println(s2) //[100 0 0]
}
切片遍历
- 索引的方式
- for range
append()方法为切片添加元素:
Go语言的内建函数append()可以为切片动态添加元素。 可以一次添加一个元素,可以添加多个元素,也可以添加另一个切片中的元素(后面加…
)。
func main(){
var s []int
s = append(s, 1) // [1]
s = append(s, 2, 3, 4) // [1 2 3 4]
s2 := []int{5, 6, 7}
s = append(s, s2...) // [1 2 3 4 5 6 7]
}
举例错误写法:
var s = []string{"大","小","多"}
append(s,"少")//错误写法,会导致切片索引越界
调用append函数必须用原来的切片变量接收返回值原因:
举个例子,搜狐公司开始两个人,公司只有两个位置,后来又来一个员工,位置不够了,那么公司搬家,搬到更大的地方去,这个公司还是这个公司。
append函数同理,在原来底层数组放不下的时候,Go就会把原来的底层数组(公司地址)换一个
s = append(s,"少")
在使用append时候,这个切片没有必要先初始化,可以直接用。注意
:通过var声明的零值切片可以在append()函数直接使用,无需初始化。
var s []int
s = append(s, 1, 2, 3)
没有必要像下面的代码一样初始化一个切片再传入append()函数使用
s := []int{} // 没有必要初始化
s = append(s, 1, 2, 3)
var s = make([]int) // 没有必要初始化
s = append(s, 1, 2, 3)
使用copy()函数复制切片
首先我们来看一个问题:
func main() {
a := []int{1, 2, 3, 4, 5}
b := a
fmt.Println(a) //[1 2 3 4 5]
fmt.Println(b) //[1 2 3 4 5]
b[0] = 1000
fmt.Println(a) //[1000 2 3 4 5]
fmt.Println(b) //[1000 2 3 4 5]
}
由于切片是引用类型,所以a和b其实都指向了同一块内存地址。修改b的同时a的值也会发生变化。
Go语言内建的copy()函数可以迅速地将一个切片的数据复制到另外一个切片空间中,copy()函数的使用格式如下:
copy(destSlice, srcSlice []T)
其中:
- srcSlice: 数据来源切片
- destSlice: 目标切片
举个例子:
func main() {
// copy()复制切片
a := []int{1, 2, 3, 4, 5}
c := make([]int, 5, 5)
copy(c, a) //使用copy()函数将切片a中的元素复制到切片c
fmt.Println(a) //[1 2 3 4 5]
fmt.Println(c) //[1 2 3 4 5]
c[0] = 1000
fmt.Println(a) //[1 2 3 4 5]
fmt.Println(c) //[1000 2 3 4 5]
}
从切片中删除元素
Go语言中并没有删除切片元素的专用方法,我们可以使用切片本身的特性来删除元素。 代码如下:
func main() {
// 从切片中删除元素
a := []int{30, 31, 32, 33, 34, 35, 36, 37}
// 要删除索引为2的元素
a = append(a[:2], a[3:]...)
fmt.Println(a) //[30 31 33 34 35 36 37]
}
总结一下就是
:要从切片a中删除索引为index的元素,操作方法是a = append(a[:index], a[index+1:]...)
指针:
- &取地址
- *根据地址取值
//取地址
a := 12
p := &a
fmt.Printf(p)
//是一个16进制的内存地址
fmt.Printf("%T\n",p)//*int代表int型指针
//根据地址p 取值
m := *p
fmt.Printf(m)//输出12
New函数:
用来申请一块内存地址
//错误示范:
var a = *int//这里虽然定义的是一个int型指针,但是却是nil指针
//所以我们需要用到new函数
var a = new(int)//这时a才是一个真正的不是空的内存地址
*a = 100
fmt.Printf(*a)
make和new的区别:
- make和new都是用来申请内存的
- new很少用,一般用来给基本数据类型申请内存的:string、int,返回的是对应类型的指针
- make是用来给
slice
,map
,chan
申请内存的,返回的是对应三个类型的本身。
map
定义:
var a map[int]string //定义了一个map集合 key是int型 value是string型
//初始化
a = make(map[int]string,10) //这里10是容量,键值对个数,估算好容量避免不够在要
a[3] = "大大"
map基本使用:
map中的数据都是成对出现的,map的基本使用示例代码如下:
func main() {
scoreMap := make(map[string]int, 8)
scoreMap["张三"] = 90
scoreMap["小明"] = 100
fmt.Println(scoreMap)
fmt.Println(scoreMap["小明"])
fmt.Printf("type of a:%T\n", scoreMap)
}
输出:
//map[小明:100 张三:90]
//100
//type of a:map[string]int
//map也支持在声明的时候填充元素,例如:
func main() {
userInfo := map[string]string{
"username": "沙河小王子",
"password": "123456",
}
fmt.Println(userInfo) //
}
判断某个键是否存在
value, ok := map[key]
map的遍历
Go语言中使用for range遍历map。
func main() {
scoreMap := make(map[string]int)
scoreMap["张三"] = 90
scoreMap["小明"] = 100
scoreMap["娜扎"] = 60
for k, v := range scoreMap {
fmt.Println(k, v)
}
}
但我们只想遍历key的时候,可以按下面的写法:
func main() {
scoreMap := make(map[string]int)
scoreMap["张三"] = 90
scoreMap["小明"] = 100
scoreMap["娜扎"] = 60
for k := range scoreMap {
fmt.Println(k)
}
}
注意
:遍历map时的元素顺序与添加键值对的顺序无关。
使用delete()函数删除键值对
使用delete()内建函数从map中删除一组键值对,delete()函数的格式如下:
delete(map, key)
其中,
- map:表示要删除键值对的map
- key:表示要删除的键值对的键
示例代码如下:
func main(){
scoreMap := make(map[string]int)
scoreMap["张三"] = 90
scoreMap["小明"] = 100
scoreMap["娜扎"] = 60
delete(scoreMap, "小明")//将小明:100从map中删除
for k,v := range scoreMap{
fmt.Println(k, v)
}
}
按照指定顺序遍历map
func main() {
rand.Seed(time.Now().UnixNano()) //初始化随机数种子
var scoreMap = make(map[string]int, 200)
for i := 0; i < 100; i++ {
key := fmt.Sprintf("stu%02d", i) //生成stu开头的字符串
value := rand.Intn(100) //生成0~99的随机整数
scoreMap[key] = value
}
//取出map中的所有key存入切片keys
var keys = make([]string, 0, 200)
for key := range scoreMap {
keys = append(keys, key)
}
//对切片进行排序
sort.Strings(keys)
//按照排序后的key遍历map
for _, key := range keys {
fmt.Println(key, scoreMap[key])
}
}
元素为map类型的切片
下面的代码演示了切片中的元素为map类型时的操作:
func main() {
var mapSlice = make([]map[string]string, 3)
for index, value := range mapSlice {
fmt.Printf("index:%d value:%v\n", index, value)
}
fmt.Println("after init")
// 对切片中的map元素进行初始化
mapSlice[0] = make(map[string]string, 10)
mapSlice[0]["name"] = "小王子"
mapSlice[0]["password"] = "123456"
mapSlice[0]["address"] = "沙河"
for index, value := range mapSlice {
fmt.Printf("index:%d value:%v\n", index, value)
}
}
值为切片类型的map
下面的代码演示了map中值为切片类型的操作:
func main() {
var sliceMap = make(map[string][]string, 3)
fmt.Println(sliceMap)
fmt.Println("after init")
key := "中国"
value, ok := sliceMap[key]
if !ok {
value = make([]string, 0, 2)
}
value = append(value, "北京", "上海")
sliceMap[key] = value
fmt.Println(sliceMap)
}