一、 数组
我们直接看一下Go语言中,数组的定义方法,代码如下:
package main
import "fmt"
func main() {
// Go中,数组的定义需要明确长度
// 定义一个五位的初始数组,默认每一位的值都为0
var arr1 [5]int
// 定义一个三位的数据,并赋初值1,3,5
arr2 := [3]int{1, 3, 5}
// 定义一个数组,赋初值2,4,6,8,10
arr3 := [...]int{2, 4, 6, 8, 10}
// 定义一个四行五列的初始二维数组
var grid [4][5]int
fmt.Println(arr1, arr2, arr3)
fmt.Println(grid)
}
定义好数组后,我们最先想到的一个数组的处理方法,便是数组的遍历,下面的代码便能展示Go语言下,数组的遍历
func main() {
// 省略部分前置代码
arr3 := [...]int{2, 4, 6, 8, 10}
// 数组遍历(最容易想到的)
for i := 0; i < len(arr3); i++ {
fmt.Println(arr3[i])
}
// Go中利用range来实现遍历
// range关键字可以获得数组的下标
for i := range arr3 {
fmt.Println(arr3[i])
}
// 此外我们也可以获取到下标和对应的值
for i, v := range arr3 {
fmt.Println(i, v)
}
// 当然也可以只获得对应的值(可以通过_来省略变量,之前也有提到)
for _, v := range arr3 {
fmt.Println(v)
}
}
Go语言下,遍历也可以通过关键字range很容易的实现出来
特别要注意的一点就是:数组是值类型,这也就意味着,定义数组的时候要明确长度,不同长度之间的数组会被认为是不同的类型;同时,在调用方法内对传入的数组修改,并不会影响方法外的数组,大家一起来看一下这段代码:
package main
import "fmt"
func printArray(arr [5]int) {
arr[0] = 50
for i := range arr {
fmt.Println(arr[i])
}
}
func main() {
// 省略部分前置代码
var arr1 [5]int
arr3 := [...]int{2, 4, 6, 8, 10}
// 先打印arr1, arr3
fmt.Println("arr1 arr3")
fmt.Println(arr1, arr3)
// 调用方法打印arr1
fmt.Println("printArray(arr1)")
printArray(arr1)
// 调用方法打印arr3
fmt.Println("printArray(arr3)")
printArray(arr3)
// 重新打印arr1, arr3
fmt.Println("arr1 arr3")
fmt.Println(arr1, arr3)
// 大家也可以尝试调用方法打印一下arr2
// printArray(arr2)
}
大家可以自行去打印一下先对应的结果,可能会更好的理解什么是值类型,打印结果如下,重点看图片中用红色框选出来的数字
所以,根据上述的打印结果,我们可以看到,原先的数组值其实并没有被修改,当然我们可以利用指针的方式,来进行对应的修改,但是会很麻烦,所以Go语言中,我们一般不直接使用数组,而是通过下面部分讲到的切片来进行操作
二、 切片(Slice)
敲黑板敲黑板,Go语言中,较为重要的一部分来了,它就是切片,我们先来看一下示例
arr := [...]int{0, 1, 2, 3, 4, 5, 6, 7}
// 定义一个切片
s := arr[2:6]
fmt.Println(s) // [2 3 4 5](左开右闭)
// 一些切片省略写法
fmt.Println("arr[2:6] = ", arr[2:6])
fmt.Println("arr[:6] = ", arr[:6])
fmt.Println("arr[2:] = ", arr[2:])
fmt.Println("arr[:] = ", arr[:])
在Go语言中,Slice本身是没有数据的,它是对底层array的一个view,这就跟array的值类型不一样了,意味着,Slice本身是可以被修改的,看下面一个示例
package main
import "fmt"
func update(s []int) {
s[0] = 100
}
func main() {
arr := [...]int{0, 1, 2, 3, 4, 5, 6, 7}
// 数组转切片
s1 := arr[:]
fmt.Println("BeforeUpdate arr = ", arr) // BeforeUpdate arr = [0 1 2 3 4 5 6 7]
fmt.Println("BeforeUpdate s1 = ", s1) // BeforeUpdate s1 = [0 1 2 3 4 5 6 7]
update(s1)
fmt.Println("AfterUpdate arr = ", arr) // AfterUpdate arr = [100 1 2 3 4 5 6 7]
fmt.Println("AfterUpdate s1 = ", s1) // AfterUpdate s1 = [100 1 2 3 4 5 6 7]
}
这里,我们可以很明显的看到,数组的值再通过切片的update方法后,很轻易的就产生了改变,这样,我们就可以解决上面数组部分,只能利用指针来更新的那个麻烦方法,即把数组编程其对应的切片后在进行响应的操作
此外,GO语言中,可以在切片的基础上继续切片,这个被称为ReSlice,具体的代码如下:
arr := [...]int{0, 1, 2, 3, 4, 5, 6, 7}
// 数组转切片
s1 := arr[:]
// ReSlice
s1 := s1[2:]
s1 := s1[:5]
A. Slice扩展
上面就是Slice切片的一些基础的用法,下面我们再来看一个示例,看看大家能不能够得到一个正确的答案:这里的s1和s2的值分别是什么,还是说会有报错
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]
fmt.Println(s2) // [5 6]
}
打印的结果见注释,是不是很惊奇,s2的打印居然打印出了不输入s1的值,这个就是slice的扩展,具体就是slice在切片时,除了我们约定本身的一个len长度,还有一个从约定头到数组结尾的cap长度,当我们打印的切片长度超过len的时候,就回去对应cap的长度,这个就是Slice的扩展
注意:slice可以向后拓展,但是不可以向前拓展
具体规则:s[i]时,是不可以超过len(s),向后扩展时不可以超过底层数组cap(s);后半句就是我们上述代码展示的s2,前半句也很好理解,大家可以打印一个s1[4]即可,这里就不多描述了,直接上代码,给大家打印一下
func main() {
arr := [...]int{0, 1, 2, 3, 4, 5, 6, 7}
s1 := arr[2:6]
s2 := s1[3:5]
fmt.Println(arr)
fmt.Printf("s1 = %v, len(s1) = %d, cap(s1) = %d\n", s1, len(s1), cap(s1))
fmt.Printf("s2 = %v, len(s2) = %d, cap(s2) = %d\n", s2, len(s2), cap(s2))
}
// 打印结果
arr= [0 1 2 3 4 5 6 7]
s1 = [2 3 4 5], len(s1) = 4, cap(s1) = 6
s2 = [5 6], len(s2) = 2, cap(s2) = 3
B. Slice添加元素
先来看下面这段代码,大家可以根据感觉先感受一下打印结果是什么
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]
s3 := append(s2, 10)
s4 := append(s3, 11)
s5 := append(s4, 12)
fmt.Println(s3) // [5 6 10]
fmt.Println(s4) // [5 6 10 11]
fmt.Println(s5) // [5 6 10 11 12]
fmt.Println(arr) // [0 1 2 3 4 5 6 10]
}
这个打印结果是不是出乎大家的意料,append(10),覆盖了7,然后append的11,12不见了
所以添加元素,在不超过cap的时候,会依次覆盖后续的原有元素;如果超过cap,系统会重新分配更大的底层数组来对应,也就是s4,s5不再是对arr的一个view;这时,如果arr在后续代码中不再使用的话,那么就会被垃圾回收掉
此外,由于值传递的关系,必须接收append的返回值,因为在append的时候,slice的ptr,len,cap都有可能改变,所以一般,s = append(s, val)
C. 创建Slice
上面的所有slice,我们都是通过一个数组来创建的;当然Go也可以直接创建一个Slice,如下:
// 不赋值
var s []int // Zero value for slice is nil
// 或者赋初值
s1 := []int{2, 4, 6, 8}
// 也可以通过make函数来创建 make(类型, len, cap)
s2 := make([]int, 10)
s2 := make([]int, 10, 32)
// 特别注意,slice的len可以为0或者任意正整数,但是在循环创建时cap一般为0和8的倍数
D. Sliceq其他操作
1) Slice还可以进行复制,代码如下
s1 := []int{2, 4, 6, 8}
s2 := make([]int, 10)
copy(s2, s1) // [2, 4, 6, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
2) Slice还可以删除其中摸个元素,代码如下
s2 = append(s2[:3], s2[4:]...) // 这样就删除掉了4号位元素 8
// 删除头
front := s2[0]
s2 = s2[1:]
// 删除尾
tail := s2[len(s2)-1]
s2 = s2[:len(s2)-1]
三、Map
上面说完了数组和切片,接下来我们继续来一种很常见的数据结构,那就是Map,先来看一下Map 的定义格式,如下:
// map[K]V
m := map[string]string {
"name": "xxx",
"course": "golang",
"age": "25",
}
// map也可以复合,格式为map[K1]map[K2]V
// 此外,还可以利用make函数来创建,格式如下
m2 := make(map[string]int) // 这样子定义出来是一个空map
// m2 == empty map
// 当然我们也可以通过var关键字来进行定义
var m3 map[string]int // 这样子定义出来是一个空map
// m3 = nil
上面就是Go语言日常定义一个map的方式,接下来我们来看一下map的遍历,代码如下
func main() {
m := map[string]string{
"name": "xxx",
"course": "golang",
"age": "25",
}
// 想必下面这种形式的遍历,看到这里大家应该都不陌生了
for k, v := range m {
fmt.Println(k, v)
}
}
大家可以打印一下这个map的遍历,多打印几遍,就会发现,每次打印kv的顺序是不一致的,也就是说,Go语言中的这个map是一个HashMap,是无序的
此外,针对于map的key,Go语言中map使用哈希表,所以要求必须可以比较相等,也就是除了slice,mp,function的内建类型都可以作为key
当自定义Struct类型不包含上述字段,也可以作为key
接下来我们来看一下map的常见操作
A. 获取Value
func main() {
m := map[string]string{
"name": "xxx",
"course": "golang",
"age": "25",
}
// 1. 获取value
courseValue := m["course"]
fmt.Println(courseValue) // golang
// 特殊情况,当你输入key的时候不小心敲错了
courseValue1 := m["cause"]
fmt.Println(courseValue1) // 空(ZeroValue)
// 应对上面的问题,可以如下处理
courseValue2, ok := m["cause"]
fmt.Println(courseValue2, ok) // 空(ZeroValue),false
// 所以,日常使用可以拼接if来判断key是否存在
if courseValue3, ok1 := m["cause"]; ok1 {
fmt.Println(courseValue3)
} else {
fmt.Println("key does not exist in this map")
}
}
B. 删除元素
delete(m, "name")
map的基本功能也就是这些了,因为map的用途非常广泛,在这里,我们用一道题来加深一下大家对于map的一个理解
例题:寻找最长不含有重复字符的字串(leetcode例题3)
题目如上所示,大家可以先思考一个思路
这个题目,我这里提供一种思路:滑动窗口,以供大家思考
我们分别定义一个左起点和右起点,左起点不懂,就指向第一个数,然后让右起点像右滑动,直到与左起点重复,记录窗口大小,然后左起点开始移动,同时随着左起点的移动,右起点继续滑动,并每次记录窗口大小,与之前的大小进行比对,直到窗口的大小,彻底小于最后记录的大小
完整的代码如下
package main
import "fmt"
//theme :无重复字符的最长子串
func lengthOfLongestSubstring(s string) int {
// 哈希集合,记录每个字符是否出现过
m := map[byte]int{}
n := len(s)
// 右指针,初始值为 -1,相当于我们在字符串的左边界的左侧,还没有开始移动
rk, ans := -1, 0
for i := 0; i < n; i++ {
if i != 0 {
// 左指针向右移动一格,移除一个字符
delete(m, s[i-1])
}
for rk + 1 < n && m[s[rk+1]] == 0 {
// 不断地移动右指针
m[s[rk+1]]++
rk++
}
// 第 i 到 rk 个字符是一个极长的无重复字符子串
ans = max(ans, rk - i + 1)
}
return ans
}
func max(x, y int) int {
if x < y {
return y
}
return x
}
/*
滑动窗口
在每一步的操作中,我们会将左指针向右移动一格,表示 我们开始枚举下一个字符作为起始位置,
然后我们可以不断地向右移动右指针,但需要保证这两个指针对应的子串中没有重复的字符。
在移动结束后,这个子串就对应着 以左指针开始的,不包含重复字符的最长子串。
复杂度:
时间复杂度:O(N),其中 N 是字符串的长度。左指针和右指针分别会遍历整个字符串一次。
空间复杂度 :O(∣Σ∣),其中 \Σ 表示字符集(即字符串中可以出现的字符)
*/
func main() {
s := "bbbbb"
result :=lengthOfLongestSubstring(s)
fmt.Println(result)
}
大家感兴趣的话,可以尝试自己写一下,然后去leetcode提交一下,这道题就到这里,下面我们来看一个前面提到的很重要的一个数据类型
四、 rune
rune这个类型,就相当于其他语言当中的char类型,它可以很好地帮助我们来处理中文这些字符,老样子,我们还是来看代码
package main
import "fmt"
func main() {
s := "Oh就要放假了yeah!!"
fmt.Println(s) // Oh就要放假了yeah!!
// 获取字符数量
fmt.Println(utf8.RuneCountInString(s)) // 13
// 这里我们来看一下它的具体长度
fmt.Println(len(s)) // 27
// 具体来拆解一下这个长度的计算
fmt.Printf("%X\n", []byte(s)) // 4F68E5B0B1E8A681E694BEE58187E4BA8679656168EFBC81EFBC81
// 为了方便查看,我们循环打印一下
for _, b := range []byte(s) {
fmt.Printf("%X ", b) // 4F 68 E5 B0 B1 E8 A6 81 E6 94 BE E5 81 87 E4 BA 86 79 65 61 68 EF BC 81 EF BC 81
}
fmt.Println()
}
上面的长度是不是很出乎我们的意料,所以我们循环按照ASCII码来进行了打印,这里我们主要看最后六个打印结果“EF BC 81 EF BC 81”,它对应了我们原始s中的两个中文感叹号,这也就是说,在这里,一个中文代表了三个字符,这样我们再来计算一下,‘就要放假了’+‘!!’为3×7=21个字符,再加上6个英文字符,一共就是27个字符,这就和我们的打印len的长度一致了
这就是UTF-8编码,它是一种可变长度的编码,这种编码下,英文为1字节,中文为3字节
如果还不能明显,大家可以按照下面去打印,注意看各个值打印的角标,会发现,中文部分,角标是按照+3的形式跳的,也说明了,中文是占了三个字节
// 打印对应的角标和对应Unicode编码
for i, ch := range s { // ch is a rune
fmt.Printf("(%d %X)", i ,ch)
}
// (0 4F)(1 68)(2 5C31)(5 8981)(8 653E)(11 5047)(14 4E86)(17 79)(18 65)(19 61)(20 68)(21 FF01)(24 FF01)
这样,解释清楚了这块,我们看这个角标,因为它是跳跃的,如果在取值的时候,不小心取错了角标,就有可能产生错误,而我们在处理数据的时候,还是会希望,能够取到第几位的内容,故我们可以按照下方的代码来处理
// 将整串转换为rune格式,再进行遍历
for i, ch := range []rune(s) {
fmt.Printf("(%d %c)", i, ch)
}
// (0 O)(1 h)(2 就)(3 要)(4 放)(5 假)(6 了)(7 y)(8 e)(9 a)(10 h)(11 !)(12 !)
fmt.Println()
五、 字符串操作
- Fields,Split, Join
- Contains, Index
- ToLower, ToUpper
- Trim, TrimRight, TrimLeft
至此为止,关于Go语言基础的语法部分,我们就告一段落了,后面就来看一些Go语言进阶很重要的东西
本文至此便告一段落,祝各位码上无ERROR,键盘无BUG!!