数组、切片和map
- 1、数组
- 1.1、数组定义
- 1.2、数组的初始化
- 1.3、数组的遍历
- 1.4、数组是值类型
- 1.5、多维数组
- 2、切片
- 2.1、切片的定义
- 2.2、关于nil
- 2.3、切片的遍历
- 2.4、基于数组定义切片
- 2.5、基于切片定义切片
- 2.6、关于切片的长度和容量
- 2.7、切片的本质
- 2.8、使用make函数构造切片
- 2.9、append()方法为切片添加元素
- 2.10、使用copy()函数复制切片
- 2.11、从切片中删除元素
- 2.12、修改字符串
- 3、排序算法和sort包
- 3.1、选择排序
- 3.2、冒泡排序
- 3.3、sort包
- 4、map
- 4.1、map基本使用
- 4.2、map的curd操作
- 4.3、元素为map类型的切片
- 4.4、值为切片类型的map
- 4.5、map类型是引用数据类型
- 4.6、按照key升序输出map
- 4.7、编写一个程序统计字符串中单词出现的次数
1、数组
1.1、数组定义
数组是指一系列同一类型数据的集合。 数组中包含的每个数据被称为数组元素(element),这种类型可以是任意的原始类型, 比如int、string等, 也可以是用户自定义的类型。一个数组包含的元素个数被称为数组的长度。在Golang中数组是一个长度固定的数据类型,数组的长度是类型的一部分,也就是说[5]int和[10]int是两个不同的类型。Golang中数组的另一个特点是占用内存的连续性,也就是说数组中的元素是被分配到连续的内存地址中的,因而索引数组元素的速度非常快。
和数组对应的类型是Slice(切片) ,Slice是可以增长和收缩的动态序列,功能也更灵活,但是想要理解Slice工作原理的话需要先理解数组,所以本节主要为大家讲解数组的使用。
数组的定义:
数组的长度也是类型的一部分。
var arr1 [3]int
var arr2 [5]int
var strArr [5]string
fmt.Printf("值: %v, 类型: %T\n", arr1, arr1)
fmt.Printf("值: %v, 类型: %T\n", arr2, arr2)
fmt.Printf("值: %v, 类型: %T\n", strArr, strArr)
1.2、数组的初始化
方式一:先定义后初始化。
var arr [3]int
arr[0] = 1
arr[1] = 2
arr[2] = 3
方式二:定义的时候初始化。
var arr1 = [3]int{1, 2, 3}
var arr2 = [3]string{"C++", "Java", "Go"}
方式三:一般情况下我们可以让编译器根据初始值的个数自行推断数组的长度。
var arr1 = [...]int{1, 5, 2, 67, 123}
fmt.Println(arr1)
fmt.Println(len(arr1))
方式四:使用指定索引值的方式来初始化数组。
arr := [...]int{1: 10, 2: 30, 6: 7}
fmt.Println(arr)
fmt.Println(len(arr))
上面的{}中,冒号左边表示下标,右边表示值,最终数组长度取决于最大的下标,其他没有赋值的下标都为默认值0。
1.3、数组的遍历
方式一:for循环。
var arr = [5]int{1, 2, 3, 4, 5}
for i := 0; i < len(arr); i++ {fmt.Printf("arr[%d]: %d\n", i, arr[i])
}
方式二:for range遍历。
strArr := [...]string{"C++", "Java", "Golang", "Python"}
for k, v := range strArr {fmt.Printf("strArr[%d]: %s\n", k, v)
}
1.4、数组是值类型
值类型:改变变量副本值的时候,不会改变变量本身的值。
引用类型:改变变量副本值的时候,会改变变量本身的值。
类似于C++中的引用类型。
值类型:基本数据类型和数组都是值类型。
var arr1 = [...]int{1, 2, 3}
arr2 := arr1
arr2[0] = 10
fmt.Println(arr1)
fmt.Println(arr2)
下面展示切片,切片是引用类型。
var arr1 = []int{1, 2, 3} // 切片
arr2 := arr1
arr2[0] = 10
fmt.Println(arr1)
fmt.Println(arr2)
切片相对于数组区别就在于:数组[]中指明了元素个数,而切片[]中不填写任何值。我们发现拷贝的arr2数组,修改了第一个元素的值后,arr1和arr2数组的第一个元素都变成了10。这是因为arr1和arr2指向的空间是同一块。
1.5、多维数组
Go语言是支持多维数组的,我们这里以二维数组为例(数组中又嵌套数组)。
二维数组的定义:
var arr = [3][2]string{{"北京", "上海"},{"广州", "深圳"},{"成都", "重庆"},
}
fmt.Println(arr)
fmt.Printf("类型: %T", arr)
二维数组的另一种定义方式,可以自动推导行,但不可推导列。
var arr = [...][2]string{{"北京", "上海"},{"广州", "深圳"},{"成都", "重庆"},
}
二位数组的两种遍历方式,for和for range。
var arr = [...][2]string{{"北京", "上海"},{"广州", "深圳"},{"成都", "重庆"},
}for i := 0; i < len(arr); i++ {for j := 0; j < len(arr[i]); j++ {fmt.Printf("%s ", arr[i][j])}fmt.Println()
}for _, v1 := range arr {for _, v2 := range v1 {fmt.Printf("%s ", v2)}fmt.Println()
}
2、切片
为什么要有切片?因为数组的长度是固定的并且数组长度属于类型的一部分,所以数组有很多的局限性。
2.1、切片的定义
切片(Slice)是一个拥有相同类型元素的可变长度的序列。它是基于数组类型做的一层封装。它非常灵活, 支持自动扩容。切片是一个引用类型, 它的内部结构包含地址、 长度和容量。
声明切片类型的基本语法如下:
var arr1 []int
fmt.Printf("值: %v, 类型: %T, 长度: %d\n", arr1, arr1, len(arr1))var arr2 = []int{1, 2, 3, 4}
fmt.Printf("值: %v, 类型: %T, 长度: %d\n", arr2, arr2, len(arr2))var arr3 = []string{1: "java", 2: "C++", 5: "golang"}
fmt.Printf("值: %v, 类型: %T, 长度: %d\n", arr3, arr3, len(arr3))
2.2、关于nil
当你声明了一个变量,但却还并没有赋值时,golang中会自动给你的变量赋值一个默认零值。
以下是每种类型对应的零值:
bool -> false
numbers -> 0
string-> ""
pointers -> nil
slices -> nil
maps -> nil
channels -> nil
functions -> nil
interfaces -> nil
Golnag中申明切片以后,切片的默认值就是nil。
var arr1 []int
var arr2 = []int{1, 2, 3, 4}
fmt.Println(arr1 == nil)
fmt.Println(arr2 != nil)
2.3、切片的遍历
和数组是一样的。
var strs = []string{"C++", "java", "golang", "python"}
for i := 0; i < len(strs); i++ {fmt.Printf("%s ", strs[i])
}
fmt.Println()for k, v := range strs {fmt.Println(k, v)
}
2.4、基于数组定义切片
a := [5]int{1, 2, 3, 4, 5}
b := a[:] // 获取数组里面所有元素
c := a[1:4] // 获取数组里面下标[1, 4)的元素
d := a[2:] // 获取数组里面下标2开始往后的所有元素
e := a[:2] // 获取数组里面下标[0, 2)的元素
fmt.Println("a:", a)
fmt.Println("b:", b)
fmt.Println("c:", c)
fmt.Println("d:", d)
fmt.Println("e:", e)
2.5、基于切片定义切片
a := []string{"C++", "Java", "Go", "Python"}
b := a[2:]
fmt.Println(b)
基本上同上面的基于数组定义切片。
2.6、关于切片的长度和容量
切片拥有自己的长度和容量,我们可以通过使用内置的len()函数求长度,使用内置的cap()函数求切片的容量。
切片的长度就是它所包含的元素个数。
切片的容量是从它的第一个元素开始数,到其底层数组元素末尾的个数。
切片s的长度和容量可通过表达式len(s)和cap(s)来获取。
先看现象:
a := []int{1, 2, 3, 4, 5, 6}
fmt.Printf("a->长度: %d, 容量: %d\n", len(a), cap(a))b := a[2:]
fmt.Printf("b->长度: %d, 容量: %d\n", len(b), cap(b))c := a[1:3]
fmt.Printf("c->长度: %d, 容量: %d\n", len(c), cap(c))d := a[:3]
fmt.Printf("d->长度: %d, 容量: %d\n", len(d), cap(d))
如图所示,切片a的长度和容量都是6没啥好说的。
切片b从下标2开始到数组结束,所以长度为4,容量也为4。
切片c从下标1开始,到下标3,但是是左闭右开区间,因此实际上只有两个元素,对应下标1和2。容量是从该切片c的起始元素也就是下标1开始计算,计算的不是当前数组,而是底层那个完整的数组,所以容量为5。
切片d长度为3很好理解,容量是从第一个元素,也就是0下标开始计算,计算的是底层的数组,所以就是底层数组的整个容量。
2.7、切片的本质
切片的本质就是对底层数组的封装,它包含了三个信息:底层数组的指针、切片的长度(len)和切片的容量(cap)。
举个例子,现在有一个数组a := [8]int{0, 1, 2, 3, 4, 5, 6, 7},切片 s1 := a[:5],相应示意图如下。
切片s2 := a[3:6], 相应示意图如下:
2.8、使用make函数构造切片
我们上面都是基于数组来创建的切片,如果需要动态的创建一个切片,我们就需要使用内置的 make()函数, 格式如下:
make([]T, size, cap)
T:切片的元素类型。
size:切片中元素的数量。
cap:切片的容量。
var sliceA = make([]int, 4, 8)
fmt.Printf("值: %d, 长度: %d, 容量: %d\n", sliceA, len(sliceA), cap(sliceA))
切片之间是不能比较的,我们不能使用==操作符来判断两个切片是否含有全部相等元素。切片唯一合法的比较操作是和nil比较。一个nil值的切片并没有底层数组,一个nil值的切片的长度和容量都是0。但是我们不能说一个长度和容量都是0的切片一定是nil。
所以要判断一个切片是否是空的,要是用len(s) == 0来判断, 不应该使用s == nil来判断。
2.9、append()方法为切片添加元素
Go语言的内建函数append()可以为切片动态添加元素,每个切片会指向一个底层数组,这个数组的容量够用就添加新增元素。当底层数组不能容纳新增的元素时,切片就会自动按照一定的策略进行扩容,此时该切片指向的底层数组就会更换。扩容操作往往发生在append()函数调用时,所以我们通常都需要用原变量接收append函数的返回值。
1、append添加单个元素。
var sliceA []int
sliceA = append(sliceA, 1)
sliceA = append(sliceA, 2)
fmt.Println(sliceA)
2、append添加多个元素。
var sliceB []int
sliceB = append(sliceB, 1, 2, 3, 4)
fmt.Printf("值: %d, 长度: %d, 容量: %d\n", sliceB, len(sliceB), cap(sliceB))
3、append还可以合并切片。
sliceA := []string{"C++", "java"}
sliceB := []string{"python", "php"}
sliceA = append(sliceA, sliceB...)
fmt.Printf("值: %v, 长度: %d, 容量: %d\n", sliceA, len(sliceA), cap(sliceA))
4、切片的扩容策略。
var sliceA []int
for i := 1; i <= 10; i++ {sliceA = append(sliceA, i)fmt.Printf("值: %v, 长度: %d, 容量: %d\n", sliceA, len(sliceA), cap(sliceA))
}
可以看到,上面的扩容方式是2倍扩容。
有兴趣可以了解一下下面的扩容内容:
1、首先判断,如果新申请容量(cap)大于2倍的旧容量(old.cap), 最终容量(newcap)就是新申请的容量(cap) 。
2、否则判断,如果旧切片的长度小于1024,则最终容量(newcap)就是旧容量(old.cap)的两倍, 即(newcap=doublecap) 。
3、 否则判断,如果旧切片长度大于等于1024,则最终容量(newcap)从旧容量(old.cap)开始循环增加原来的1/4, 即(newcap=old.cap,for {newcap += newcap/4}) 直到最终容量(newcap) 大于等于新申请的容量(cap), 即(newcap >= cap)
4、 如果最终容量(cap) 计算值溢出, 则最终容量(cap) 就是新申请容量(cap) 。
需要注意的是, 切片扩容还会根据切片中元素的类型不同而做不同的处理, 比如 int 和 string类型的处理方式就不一样。
2.10、使用copy()函数复制切片
由于切片是引用类型,所以修改副本会影响其本身的值,我们可以使用copy函数拷贝一份出来,这样修改副本就不会影响变量本身。
var sliceA = []int{1, 2, 3}
sliceB := make([]int, 3, 3)
copy(sliceB, sliceA)
sliceB[0] = 10
fmt.Println(sliceA)
fmt.Println(sliceB)
2.11、从切片中删除元素
Go语言中并没有删除切片元素的专用方法,我们可以使用切片本身的特性来删除元素。
a := []int{1, 2, 3, 4, 5, 6}
a = append(a[:2], a[3:]...)
fmt.Printf("值: %v, 长度: %d, 容量: %d\n", a, len(a), cap(a))
上面删除了数组中的3。
2.12、修改字符串
这个我们在之前有讲过,只不过当时不知道字符串要先转换成的另外一种数据类型,也就是切片。如下:
str1 := "hello world"
arr1 := []byte(str1)
arr1[0] = 'l'
fmt.Println(string(arr1))str2 := "你好golang"
arr2 := []rune(str2)
arr2[0] = '哈'
fmt.Println(string(arr2))
3、排序算法和sort包
3.1、选择排序
选择排序:进行从小到大排序
概念:通过比较,首先选出最小的数放在第一个位置上,然后在其余的数中选出次小数放在第二个位置上,依此类推,直到所有的数成为有序序列。
var arr = []int{7, 6, 5, 4, 3, 2, 1}
for i := 0; i < len(arr); i++ {for j := i + 1; j < len(arr); j++ {if arr[i] > arr[j] {tmp := arr[i]arr[i] = arr[j]arr[j] = tmp}}
}
fmt.Println(arr)
3.2、冒泡排序
概念:从头到尾,比较相邻的两个元素的大小,如果符合交换条件,交换两个元素的位置。
特点:每一轮比较中,都会选出一个最大的数,放在正确的位置。
var arr = []int{7, 6, 5, 4, 3, 2, 1}
for i := 0; i < len(arr); i++ {for j := 0; j < len(arr)-i-1; j++ {if arr[j] > arr[j+1] {tmp := arr[j]arr[j] = arr[j+1]arr[j+1] = tmp}}
}
fmt.Println(arr)
3.3、sort包
对于int、float64和string数组或是切片的排序,go分别提供了sort.Ints()、sort.Float64s()和sort.Strings()函数,默认都是从小到大排序。
intList := []int{2, 4, 3, 5, 7, 6, 9, 8, 1, 0}
floatList := []float64{4.2, 5.9, 12.4, 10.2, 50.7, 99.9, 31.4, 27.81828, 3.14}
stringList := []string{"a", "c", "b", "z", "x", "w", "y", "d", "f", "i"}
sort.Ints(intList)
sort.Float64s(floatList)
sort.Strings(stringList)
fmt.Println(intList)
fmt.Println(floatList)
fmt.Println(stringList)
使用sort包排降序会麻烦一些:
intList := []int{2, 4, 3, 5, 7, 6, 9, 8, 1, 0}
floatList := []float64{4.2, 5.9, 12.4, 10.2, 50.7, 99.9, 31.4, 27.81828, 3.14}
stringList := []string{"a", "c", "b", "z", "x", "w", "y", "d", "f", "i"}
sort.Sort(sort.Reverse(sort.IntSlice(intList)))
sort.Sort(sort.Reverse(sort.Float64Slice(floatList)))
sort.Sort(sort.Reverse(sort.StringSlice(stringList)))
fmt.Println(intList)
fmt.Println(floatList)
fmt.Println(stringList)
4、map
4.1、map基本使用
map是一种无序的基于key-value的数据结构,Go语言中的map是引用类型,必须初始化才能使用。
Go语言中map的定义语法如下:
注意:获取map的容量不能使用cap,cap返回的是数组切片分配的空间大小,不能用于map。要获取map的容量, 可以用len函数。
使用make创建map类型:
var userinfo = make(map[string]string)
userinfo["username"] = "张三"
userinfo["age"] = "18"
userinfo["sex"] = "男"
fmt.Println(userinfo)
另外两种方式:
var userinfo1 = map[string]string{"username": "张三","age": "18","sex": "男",
}
fmt.Println(userinfo1)userinfo2 := map[string]string{"username": "李四","age": "20","sex": "女",
}
fmt.Println(userinfo2)
循环遍历map——for range。
userinfo := map[string]string{"username": "张三","age": "18","sex": "男",
}
fmt.Println(userinfo["username"])
for k, v := range userinfo {fmt.Println(k, v)
}
4.2、map的curd操作
1、创建并修改map类型的数据。
var userinfo1 = make(map[string]string)
userinfo1["username"] = "张三"
userinfo1["age"] = "18"
fmt.Println(userinfo1)var userinfo2 = map[string]string{"username": "张三","age": "20",
}
userinfo2["username"] = "李四"
fmt.Println(userinfo2)
2、获取/查找map类型的数据。
var userinfo = map[string]string{"username": "张三","age": "20",
}
fmt.Println(userinfo["username"]) // 获取v1, ok1 := userinfo["age"]
fmt.Println(v1, ok1)v2, ok2 := userinfo["xxx"]
fmt.Println(v2, ok2)
3、删除map数据里面的key以及对于的值。
使用delete()内建函数从map中删除一组键值对,delete()函数的格式如下:
delete(map 对象,key)其中,map对象表示要删除键值对的map对象,key表示要删除的键值对的键。
var userinfo = map[string]string{"username": "张三","age": "18","sex": "男","height": "180cm",
}
fmt.Println(userinfo)
delete(userinfo, "age")
fmt.Println(userinfo)
4.3、元素为map类型的切片
我们想在切片里面放一系列用户的信息,这时候我们就可以定义一个元素为map类型的切片。
var userinfo = make([]map[string]string, 2, 2)
fmt.Println(userinfo)
fmt.Println(userinfo[0] == nil)
可以看到未初始化的map实际上就是nil。
var userinfo = make([]map[string]string, 2, 2)
fmt.Println(userinfo)
fmt.Println(userinfo[0] == nil)if userinfo[0] == nil {userinfo[0] = make(map[string]string)userinfo[0]["username"] = "张三"userinfo[0]["age"] = "18"userinfo[0]["sex"] = "男"
}if userinfo[1] == nil {userinfo[1] = make(map[string]string)userinfo[1]["username"] = "李四"userinfo[1]["age"] = "20"userinfo[1]["sex"] = "女"
}
fmt.Println(userinfo)for _, v1 := range userinfo {for k2, v2 := range v1 {fmt.Println(k2, v2)}
}
4.4、值为切片类型的map
如果我们想在map对象中存放一系列的属性的时候,我们就可以把map类型的值定义成切片。
var userinfo = make(map[string][]string)
userinfo["hobby"] = []string{"跑步","爬山","写代码",
}
userinfo["work"] = []string{"C++", "Java", "Python", "Go",
}
fmt.Println(userinfo)
4.5、map类型是引用数据类型
var userinfo = make(map[string]string)
userinfo["username"] = "张三"
userinfo["age"] = "18"
copy := userinfo
fmt.Println(userinfo)
fmt.Println(copy)copy["username"] = "李四"
fmt.Println(userinfo)
fmt.Println(copy)
4.6、按照key升序输出map
map1 := make(map[int]int)
map1[10] = 100
map1[1] = 13
map1[4] = 56
map1[8] = 90
map1[11] = 100
map1[2] = 13fmt.Println(map1)
var arr []int
for k, _ := range map1 {arr = append(arr, k)
}
sort.Ints(arr)
for i := 0; i < len(arr); i++ {fmt.Printf("%d:%d\n", arr[i], map1[arr[i]])
}
4.7、编写一个程序统计字符串中单词出现的次数
str := "how do you do"
var strSlice = strings.Split(str, " ")
var strMap = make(map[string]int)
for _, v := range strSlice {strMap[v]++
}
for k, v := range strMap {fmt.Printf("%s[%d]\n", k, v)
}