Go内建容器
第三章 内建容器
3.1 数组
3.1.1 Go语言数组格式
Go语言数组语法格式要点如下:
- 数组成员数量要写在类型的前面
- 用“ := ”来创建数组时需要加上“ {} ”来给数组一个初值
- “ [] ”里可以用“ ... ”来让编译器识别有多少个数组成员
fmt.Println()
可以直接打印数组的成员
package main
import "fmt"
func main() {
var arr1 [3]int
arr2 := [3]int{}
arr3 := [...]int{1, 4, 5, 6, 7}
var grid [3][4]int
fmt.Println(
arr1, //[0 0 0]
arr2, //[0 0 0]
arr3, //[1 4 5 6 7]
grid, //[[0 0 0 0] [0 0 0 0] [0 0 0 0]]
)
}
3.1.2 数组的遍历(range
关键词)
可以用传统的遍历数组方法遍历数组,但一般在Go中用range
关键词来遍历
传统:
package main
import "fmt"
func main() {
arr := [...]int{2, 4, 6, 8, 10}
for i := 0; i < len(arr); i++ {
fmt.Printf("%d ", arr[i])
}
}
用range
关键词可以获得数组的下标以及下标对应的值
- 只想获得数组的下标:
package main
import "fmt"
func main() {
arr := [...]int{2, 4, 6, 8, 10}
for i := range arr {
fmt.Println(i)
}
//输出结果:
//0
//1
//2
//3
//4
}
- 想获得数组下标以及下标对应的值:
package main
import "fmt"
func main() {
arr := [...]int{2, 4, 6, 8, 10}
for i, v := range arr {
fmt.Println(i, v)
}
//输出结果:
//0 2
//1 4
//2 6
//3 8
//4 10
}
- 只想获得数组的各个值,通过下划线“ _ ”来省略变量,不仅在
range
里,任何地方都能用下划线来省略变量
package main
import "fmt"
func main() {
arr := [...]int{2, 4, 6, 8, 10}
for _, v := range arr {
fmt.Println(v)
}
//输出结果:
//2
//4
//6
//8
//10
}
总之,
i
代表下标,v
代表值
3.1.3 在Go中,数组是值类型
[i]int
和[j]int
(i
不等于j
)是不同类型,若函数要求传入的是[i]int
类型但传入的是[j]int
类型,那就会报错- 调用
func f(arr [10]int)
会在函数中拷贝数组 - Go语言一般不会直接使用数组,而是使用切片
下面将Go和Java中在函数(Go)/方法(Java)中对数组成员进行操作的对比:
1. Go:
package main
import "fmt"
func f(arr [5]int) {
arr[0] = 100
for _, v := range arr {
fmt.Println(v)
}
fmt.Printf("函数内,数组第一位是:%d\n", arr[0])
}
func main() {
arr := [5]int{2, 4, 6, 8, 10}
f(arr)
fmt.Printf("main中数组第一位是:%d", arr[0])
}
Go输出结果:
2. Java:
package com.oop;
import java.text.ParseException;
public class Application {
public static void main(String[] args) {
int[] arr = {2, 4, 6, 8, 10};
f(arr);
System.out.println("在main中,数组第一位是:" + arr[0]);
}
public static void f(int[] arr) {
arr[0] = 100;
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
System.out.println("在方法中,数组第一位是:" + arr[0]);
}
}
Java输出结果:
可以发现:在Go语言中,函数内对数组的操作并不会改变原本的数组数据,但在Java中,方法内对数组的操作会对原本的数组产生改变
在Go中若想在函数内对数组的操作对原数组产生改变,可以将指向数组的指针传入函数内,以此来达到改变数组的目的:
- 不同于C语言中数组名是数组的地址,在Go语言中,想获得数组的地址还是要进行
&arr
操作
package main
import "fmt"
func f(arr *[5]int) {
arr[0] = 100//Go语言很灵活,即使这里传入的是指针,也不用(*arr)[0] = 100
for _, v := range arr {
fmt.Println(v)
}
fmt.Printf("函数内,数组第一位是:%d\n", arr[0])
}
func main() {
arr := [5]int{2, 4, 6, 8, 10}
f(&arr)
fmt.Printf("main中数组第一位是:%d", arr[0])
}
输出结果:
传入数组的指针后达到了改变数组内值的目的
3.2 切片(Slice)的概念
3.2.1 切片的特点
切片:对数组进行左闭右开区间的一个切分得出的数组,如下:
package main
import "fmt"
func main() {
arr := [...]int{0, 1, 2, 3, 4, 5, 6, 7}
s1 := arr[2:6]
s2 := arr[2:]
s3 := arr[:6]
s4 := arr[:]
fmt.Println(
s1, //[2 3 4 5]
s2, //[2 3 4 5 6 7]
s3, //[0 1 2 3 4 5]
s4, //[0 1 2 3 4 5 6 7]
)
}
- 切片本身没有数据,其是对底层array的一个view(所谓view,就是通过view来看array的样子,所以切片变了那么array就会变)
- 对切片进行改变,不仅切片会发生改变,原来的array也会发生改变
例子如下:
package main
import "fmt"
//方括号里不加数组长度就是切片类型,加了就是数组类型
func updateSlice(s []int) {
s[0] = 100
}
func main() {
arr := [...]int{0, 1, 2, 3, 4, 5, 6, 7}
s1 := arr[2:6] //s1:[2 3 4 5]
s2 := arr[:] //[0 1 2 3 4 5 6 7]
fmt.Println("After updateSlice(s1):")
updateSlice(s1)
fmt.Println(s1) //[100 3 4 5]
fmt.Println(arr) //[0 1 100 3 4 5 6 7]
fmt.Println(s2) //[0 1 100 3 4 5 6 7]
}
在3.13中所阐述的,若想在函数中改变数组的值,可以传入数组的指针来达到目的,但一般不这么做,一般会传入切片类型给函数,这样就能对原数组产生影响,如下:
package main
import "fmt"
func f(arr []int) {
arr[0] = 100
for _, v := range arr {
fmt.Println(v)
}
fmt.Println("函数内,数组第一位是:", arr[0])
}
func main() {
arr := [5]int{2, 4, 6, 8, 10}
f(arr[:])//这里的arr[:]就是传入的切片类型
fmt.Println("main中数组第一位是:", arr[0])
}
3.2.2 Reslice
可以对切片再次进行切片
package main
import "fmt"
func updateSlice(s []int) {
s[0] = 100
}
func main() {
arr := [...]int{0, 1, 2, 3, 4, 5, 6, 7}
s1 := arr[2:6] //s1:[2 3 4 5]
s2 := arr[:] //[0 1 2 3 4 5 6 7]
fmt.Println("After updateSlice(s1):")
updateSlice(s1)
fmt.Println(s2) //[0 1 100 3 4 5 6 7]
fmt.Println("Reslice")
s2 = s2[:5]
fmt.Println(s2) //[0 1 100 3 4]
s2 = s2[2:]
fmt.Println(s2) //[100 3 4]
}
3.2.3 Slice
的扩展
看一个例子:
package main
import "fmt"
func main() {
arr := [...]int{0, 1, 2, 3, 4, 5, 6, 7}
s1 := arr[2:6]
s2 := s1[3:5]
fmt.Println(
s1, //[2 3 4 5]
s2, //[5 6]
)
}
s1
很容易得出是[2 3 4 5]
,但s2 := s1[3:5]
这一句,s1
下标最大为3,为什么s2
能够取到s1
下标为4的元素呢?这涉及到Slice
的扩展
Slice
的扩展图解:因为切片是对底层array的一个view,所以s1
还是有下标为4,5两个隐藏元素的,同理可得s2
有下标为2的隐藏元素
Slice
的实现图解:ptr
指向slice
开头的元素;len
(length)是slice
的长度,在用方括号”[]“取值时只能取到len
里的值;cap
(capacity)代表ptr
到数组尾部的长度,也代表着slice
能扩展的最大范围
值得注意的是:
slice
可以向后扩展,但不能向前扩展s[i]
不可以超越len(s)
,向后扩展不可以超越底层数组cap(s)
函数名 | 说明 |
---|---|
func cap(v Type) int |
返回 v 的容量 |
func len(v Type) int |
返回 v 的长度 |
切片的length和capacity都是可以通过len(s)
和cap(s)
来得到的:
package main
import "fmt"
func main() {
arr := [...]int{0, 1, 2, 3, 4, 5, 6, 7}
s1 := arr[2:6]
s2 := s1[3:5]
fmt.Println(
arr, //[0 1 2 3 4 5 6 7]
s1, "len(s1)=", len(s1), "cap(s2)=", cap(s1), //[2 3 4 5] len(s1)= 4 cap(s2)= 6
s2, "len(s2)=", len(s2), "cap(s2)=", cap(s2), //[5 6] len(s2)= 2 cap(s2)= 3
)
}
3.3 切片的操作
缩写解释:
dst
:即destination,目标src
:即source,来源
函数名 | 说明 |
---|---|
func append(slice []Type, elems ...Type) []Type |
将元素追加到切片的末尾 |
func make(Type, size IntegerType) Type |
分配并初始化一个类型为切片、映射、或通道的对象 |
func copy(dst, src []Type) int |
将元素从来源切片复制到目标切片中,也能将字节从字符串复制到字节切片中 |
3.3.1 对切片进行新增func append(slice []Type, elems ...Type) []Type
- 在切片中添加元素时如果超越了cap,系统会重新分配更大的底层数组
append()
语法示例:
- 新增切片成员,第一位写目标切片,后面写任意数量的要新增的数值
- 拼接两个切片,第一位写目标切片,后面的切片后要加” ... “
slice = append(slice, elem1, elem2)
slice = append(slice, anotherSlice...)
package main
import "fmt"
func main() {
arr := [...]int{0, 1, 2, 3, 4, 5, 6, 7}
s1 := arr[2:6] //[2 3 4 5]
s2 := s1[3:5] //[5 6]
s3 := append(s2, 10)
s4 := append(s3, 11)
s5 := append(s4, 12)
fmt.Println(
s3, //[5 6 10]
s4, //[5 6 10 11]
s5, //[5 6 10 11 12]
arr, //[0 1 2 3 4 5 6 10]
//s4和s5的capacity已经超过了arr,所以s4和s5的view对象就不再是arr了,而是系统给分配的其他更长的数组
)
}
3.3.2 对切片进行创建func make(Type, size IntegerType) Type
package main
import "fmt"
//用来打印切片的内容、长度、容量
func printSlice(s []int) {
fmt.Printf("%v,len=%d,cap=%d\n", s, len(s), cap(s))
}
func main() {
//不先创建数组,直接创建切片
//1.创建不赋初值的切片,不给切片赋初值,其初值默认为nil
var s []int
for i := 0; i < 10; i++ {
s = append(s, 2*i+1)
}
fmt.Println(s) //[1 3 5 7 9 11 13 15 17 19]
//2.创建赋初值的切片
s1 := []int{2, 4, 6, 8}
printSlice(s1) //[2 4 6 8],len=4,cap=4
//3.在创建大小已知的切片但切片里的值还不知道时,用make()来创建切片
s2 := make([]int, 16)
s3 := make([]int, 10, 32)
printSlice(s2) //[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0],len=16,cap=16
printSlice(s3) //[0 0 0 0 0 0 0 0 0 0],len=10,cap=32
}
3.3.3 对切片进行复制func copy(dst, src []Type) int
package main
import "fmt"
func printSlice(s []int) {
fmt.Printf("%v,len=%d,cap=%d\n", s, len(s), cap(s))
}
func main() {
s1 := []int{2, 4, 6, 8}
printSlice(s1) //[2 4 6 8],len=4,cap=4
s2 := make([]int, 16)
printSlice(s2) //[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0],len=16,cap=16
copy(s2, s1)
printSlice(s2) //[2 4 6 8 0 0 0 0 0 0 0 0 0 0 0 0],len=16,cap=16
}
3.3.4 对切片进行删减
package main
import "fmt"
func printSlice(s []int) {
fmt.Printf("%v,len=%d,cap=%d\n", s, len(s), cap(s))
}
func main() {
s := []int{2, 4, 6, 8, 0, 0, 0, 0, 0}
printSlice(s) //[2 4 6 8 0 0 0 0 0],len=9,cap=9
//1.删除切片中间的成员:将s中的”8“删除:将切片s切成两个切片再拼接即可
s = append(s[:3], s[4:]...)
printSlice(s) //[2 4 6 0 0 0 0 0],len=8,cap=9
//2.poping from back
tail := s[len(s)-1]
s = s[:len(s)-1]
fmt.Println(tail) //0
printSlice(s) //[2 4 6 0 0 0 0],len=7,cap=9
//3.poping from front
front := s[0]
s = s[1:]
fmt.Println(front) //2
printSlice(s) //[4 6 0 0 0 0],len=6,cap=8
}
3.4 map
3.4.1 map
的三种定义方式
package main
import "fmt"
func main() {
//第一种声明方式
//声明myMap1是一种map类型,key是string,value是string
var myMap1 map[string]string
if myMap1 == nil {
fmt.Println("myMap1是一个空map")
}
//在使用map前,需先用make()给map分配空间
myMap1 = make(map[string]string, 10)
myMap1["one"] = "java"
myMap1["two"] = "php"
myMap1["three"] = "python"
fmt.Println(myMap1) //map[one:java three:python two:php]
//输出的map成员顺序是无序的
//第二种声明方式
myMap2 := make(map[int]string)
myMap2[1] = "cpp"
myMap2[2] = "c"
myMap2[3] = "go"
fmt.Println(myMap2) //map[1:cpp 2:c 3:go]
//第三种声明方式
myMap3 := map[string]string{
"one": "javascript",
"two": "cpp",
"three": "golang",
}
fmt.Println(myMap3)//map[one:javascript three:golang two:cpp]
}
- 还有复合
map
:map[K1]map[K2]V
,即外面一层的map
的key是K1,值是map[K2]V,里面一层map
的key是K2,值是V
3.4.2 map
的操作
1.map
的各类操作:
- 创建:
m := make(map[string]string)
- 获取元素:
m[key]
- key不存在时,获得Value类型的初始值
- 用
value,ok = m[key]
来判断是否存在key - 用
delete()
删除一个key
2.map
的遍历:
- 使用
range
遍历key,或者遍历key,value对 - 不保证遍历顺序,如需顺序,需要手动对key排序
- 使用
len()
来获得map
的元素个数
3.可以当map
的key所需条件:
map
使用哈希表,必须可以比较是否相等- 除了
slice
,map
,function
的内建类型可以作为key Struct
类型不包含上述字段,也可以作为key
package main
import (
"fmt"
)
func main() {
myMap := map[string]string{
"name": "ccmouse",
"course": "golang",
"site": "imooc",
"quality": "notbad",
}
//1.遍历map,可以用range来遍历map,和数组一样,可以读取map的key以及value
for k, v := range myMap {
fmt.Println(k, v)
}
for k := range myMap {
fmt.Println(k)
}
for _, v := range myMap {
fmt.Println(v)
}
fmt.Println("============================")
//2.读取map中的值
courseName := myMap["course"]
fmt.Println(courseName) //golang
fmt.Println("============================")
//3.判断某个键是否存在
//Go语言中有个判断map中键是否存在的特殊写法,格式如下:value, ok := map[key]
teacherName1, ok := myMap["teacher"]
fmt.Println(teacherName1, ok) // false
teacherName2, ok := myMap["name"]
fmt.Println(teacherName2, ok) //ccmouse true
// 如果key存在ok为true,v为对应的值;不存在ok为false,v为值类型的零值
fmt.Println("============================")
//4.删除元素
delete(myMap, "name")
name, ok := myMap["name"]
fmt.Println(name, ok) // false
fmt.Println(myMap)
fmt.Println(len(myMap))
}
3.5 map
案例
需求:寻找最长不含有重复字符的字串,请从字符串中找出一个最长的不包含重复字符的子字符串,计算该最长子字符串的长度。
例:
abcabcbb
→abc
,输出3bbbbb
→b
,输出1pwwkew
→wke
,输出3
package main
import "fmt"
func lengthOfNonRepeatingSubStr(s string) int {
lastOccurred := make(map[byte]int)//记录每个字符位置的map,此时创建的是空map
start := 0//表示无重复字符串的开头
maxLength := 0//最大长度子串的长度
//[]byte(s)意思是将s强转为byte切片
for i, ch := range []byte(s) {
//若字符在map中存在过(即此字符出现过),且出现的位置>=start,将start移动至上次出现位置的+1处
if lastI, ok := lastOccurred[ch]; ok && lastI >= start {
start = lastI + 1
}
//若此字符到i的长度>maxLength,令maxLength=此字符到i的长度
if i-start+1 > maxLength {
maxLength = i - start + 1
}
//将此次循环到的字符所存在的位置记录在map中
lastOccurred[ch] = i
}
return maxLength
}
func main() {
fmt.Println(lengthOfNonRepeatingSubStr("abcaebb"))
}
3.6 字符和字符串处理
3.6.1 unicode/utf8
包
- utf8包实现了对
utf-8
文本的常用函数和常数的支持,包括rune
和utf-8
编码byte序列之间互相翻译的函数。
3.6.2 unicode/utf8
包常用函数
函数名 | 说明 |
---|---|
func RuneCount(p []byte) int |
返回p中的utf-8 编码的码值的个数 |
func RuneCountInString(s string) (n int) |
类似RuneCount 但输入参数是一个字符串 |
func DecodeRune(p []byte) (r rune, size int) |
函数解码p开始位置的第一个utf-8 编码的码值,返回该码值和编码的字节数。如果编码不合法,会返回(RuneError, 1) |
package main
import (
"fmt"
"unicode/utf8"
)
func main() {
//part.1 用len()直接打印含中文字符串的长度
s := "Yes我爱慕课网!"
fmt.Println(len(s)) //若用len()查看s的长度会发现不是9而是19,len()只能获得字节长度而不能获得字符长度
for _, b := range []byte(s) {
fmt.Printf("%X ", b)
} //59 65 73 E6 88 91 E7 88 B1 E6 85 95 E8 AF BE E7 BD 91 21
//若用range遍历将s转换为字节切片再遍历,会发现只有9个字符的含中文的字符串会打印出19个字节
fmt.Println()
//part.2 中文字符占3个字节
for i, ch := range s { //ch是rune类型,int32是rune的别名,rune是4个字节
fmt.Printf("(%d %X) ", i, ch)
} //(0 59) (1 65) (2 73) (3 6211) (6 7231) (9 6155) (12 8BFE) (15 7F51) (18 21)
//用range遍历s字符串,会发现中文字符占3字节
//此外,在UTF-8编码中,一个中文标点符号占三个字节;一个英文字符等于一个字节,一个英文标点占一个字节;一个数字符号等于一个字节
fmt.Println()
//part.3.1 用func RuneCountInString(s string) (n int)获得含中文的字符串的字符数量
//对含有中文的字符串进行操作可以用utf8包中的函数会很方便
fmt.Println("Rune count:", utf8.RuneCountInString(s)) //Rune count: 9,RuneCountInString可以返回utf-8编码的码值的个数
//part.3.2 func DecodeRune(p []byte) (r rune, size int)
bytes := []byte(s)
for len(bytes) > 0 { //若bytes的长度大于0则进入循环
ch, size := utf8.DecodeRune(bytes) //获取bytes第一个编码的码值和该码值占的字节数
fmt.Printf("%c ", ch) //打印bytes第一个编码的码值
bytes = bytes[size:] //将bytes切掉第一个码值
} //输出结果:Y e s 我 爱 慕 课 网 !
fmt.Println()
//part.4 将字符串转换为rune切片,再遍历即可获得字符串每个字符的下标,不像转换为byte切片时会因为字符的字节数不一样而跳过一些下标
for i, ch := range []rune(s) {
fmt.Printf("(%d %c)", i, ch)
}
}
rune
相当于go的char
- 遍历字符串可以使用
[]rune(s)
- 使用
utf8.RuneCountInString()
获得字符数量 - 使用
len()
获得字节长度 - 使用
[]byte
获得字节
3.5map
案例不支持中文,将[]byte(s)
改为[]rune(s)
即可:
package main
import "fmt"
func lengthOfNonRepeatingSubStr(s string) int {
lastOccurred := make(map[rune]int)//改为[]rune(s)
start := 0
maxLength := 0
for i, ch := range []rune(s) {//改为[]rune(s),这里不能直接range s,这样会将中文字符算作3个字节
if lastI, ok := lastOccurred[ch]; ok && lastI >= start {
start = lastI + 1
}
if i-start+1 > maxLength {
maxLength = i - start + 1
}
lastOccurred[ch] = i
}
return maxLength
}
func main() {
fmt.Println(lengthOfNonRepeatingSubStr("一二三三三"))
}
其他字符串操作
关于字符串操作的函数在strings包中
Fields
,Split
,Join
Contains
,Index
ToLower
,ToUpper
Trim
,TrimRight
,TrimLeft
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现