这是我参与「第五届青训营 」伴学笔记创作活动的第2天。
Go的常用标准函数
字符串相关
1.字符串遍历,同时处理有中文的问题
r := []rune(str)
是先把字符串转为一个rune的切片,防止出现乱码
2.字符串转整数
n, err := strconv.Atoi("12")
n, err := strconv.Atoi("hello")
if err != nil{
fmt.Println("转换错误", err)
} else {
fmt.Println("转换成功", n)
}
3.整数转字符串
str = strconv.Itoa(12345)
4.字符串转为byte切片
var bytes = []byte("hello")
将字符串中的内容转为对应的acill字符
5.byte转为字符串
str = string([]byte{97, 98, 99})
6.10进制转其他进制
str = strconv.FormatInt(123, 2)
//将123转为2进制
7.查找子串是否在指定的字符串
strings.Contains("seafood", "foo") // true
8.统计一个字符串有几个指定的子串
strings.Count("chinese", "e") // 2
9.不区分大小写的字符串比较(==是区分大小写的)
strings.EqualFold("abc", "Abc") // true
10.返回子串在字符串中第一次出现的index值,如果没有则返回-1
strings.Index("NLT_abc", "abc") // 4
11.返回子串在字符串中最后一次出现的index值,如果没有则返回-1
strings.LastIndex("NLT_abc", "abc") // 4
12.将指定的子串替换为另一个子串
strings.Replace("go go hello", "go", "go语言", n)
n指定希望替换几个,如果n = -1则表示全部替换
13.按照指定的某个字符,为分割标识,将一个字符串拆分为字符串数组
strings.Split("hello,world,ok", ",")
14.大小写转换
strings.ToLower("GO")
15.将字符串左右两边的空格去掉
strings.TrimSpace(" hello ")
16.将字符串左右两边的指定字符去掉
strings.Trim("! hello !", "!")
17.将字符串左(右)边的指定字符去掉
strings.TrimLeft("! hello !", "!")
18.判断字符串是否以指定的字符串开头
strings.HasPrefix("ftp//192.168.10.1", "ftp") // true
19.判断字符串是否以指定的字符串结束
strings.HasSuffix("ftp//192.168.10.1", ".1") // true
内存管理相关
new
用来分配内存,主要用来分配值类型,比如int、float32、struct,返回的是一个指针
num := new(int)
分配了一个指针类型,*int,值为一个地址,地址指向0
*num = 100
将num的值改为100
make
用来分配内存,主要用来分配引用类型,比如channel、map、slice
Go的错误处理机制
defer与recover
go语言遵循代码简洁、清晰的原则,抛弃了如Java、Python中try...catch(except)、Raise的异常处理语句(虽然我不理解这些语言的异常处理为什么不简洁、清晰了....)go中的引入的处理方式为:defer、panic、recover。
go中可以抛出一个panic的异常,然后在defer中通过recover捕获这个异常,然后进行处理
如:
func test() {
defer func() {
err := recover() // recover是内置函数,可以捕获到异常
if err != nil {
fmt.Println("err =", err)
}
}()
num1 := 10
num2 := 0
fmt.Println(num1 / num2)
}
注意:defer要以函数的形式调用,不然会报错
自定义错误
go也支持自定义错误,使用errors.New和panic内置函数
panic内置函数,可以接收一个interface{}(空接口)的值,即任何值作为参数。可以接受error类型的变量,输出错误信息,并退出程序,类似于Python中Raise,Java的thorws
func readConf(name string) (err error) {
if name == "config.ini" {
// 读取..
return nil
} else {
// 返回一个自定义错误
return errors.New("读取文件错误")
}
}
func test2() {
err := readConf("config.ini")
if err != nil {
// 如果读取文件发生错误,就输出这个错误,并终止程序
panic(err)
}
fmt.Println("继续执行ing...")
}
数组
基本语法
数组可以存放多个同一类型的数据,数组也是一种数据类型,在Go中,数组是值类型 var 数组名 [数组长度]数据类型
当定义完数组时,其中的每个元素都有一个默认值,该默认值取决于定义的数据类型
使用步骤:
1.声明数组并开辟空间
2.给数组各个元素赋值
3.使用数组
内存分布
数组的地址可以通过数组名来获取:&Arr
数组的地址就是第一个元素的地址,后续元素的地址,就是在前一个元素的地址上加数据类型的大小
四种数组初始化的方式:
1.var numsArray1 [3]int = [3]int{1, 2, 3}
2.var numsArray2 = [3]int{1, 2, 4}
3.var numsArray3 = [...]int{1, 2, 5}
// 可以通过索引直接定义数组的值
var numsArray4 = [...]int{1: 100, 0: 90, 2: 110}
4.strArray := [...]string{"Jack", "rose"} // 类型推导
range遍历
数组当然是可以用for循环简单遍历的,这里介绍一种类似于Python中的enumerate的遍历方式
range遍历
for index, value := range array{
}
函数修改数组
数组属于值类型,故在默认情况下是值传递,因此会进行值拷贝,数组间不会相互影响
比如如果将一个数组传入到一个函数中,对数组的值进行修改,修改的是函数所有的栈中的数组的值,而不是main栈中的,所以没有全局修改作用
func editArr(arr *[3]int) {
(*arr)[0] = 88
}
注意:
1.传入的应是数组的地址
2.在函数中传入数组,应要注意将数组的长度也要传入,长度是类型的一部分
切片
基本语法
切片是数组的一个引用,是引用类型,在进行传递时,遵循引用传递的机制
切片的使用和数组类似,但切片的长度可以变化,是一个可以动态变化的数组
基本语法:
var 变量名 []类型
如:var a [] int
var intArr = [...]int{1, 2, 3, 4, 5}
// slice引用数组从第2个元素到第4个元素,不包括第4个元素
slice := intArr[1:3]
// 容量为切片目前可以存放的最大数,是可以动态变化的,一般是长度的两倍
fmt.Println("slice的容量", cap(slice))
内存布局
切片在内存中的三部分
1.引用的第一个元素的地址
2.存放slice的长度
3.存放slice的容量
从底层上来说,slice是一个结构体,即一种数据结构
type slice struct{
ptr *[2]int
len int
cap int
}
注意:由于slice是引用类型,那么如果修改了slice中的元素,会对其引用的数组也会造成修改
三种使用方式
1.定义一个切片,然后去引用已经创建好的数组
var intArr = [...]int{1, 2, 3, 4, 5}
slice := intArr[1:3]
2.通过make来创建切片
基本语法:
var 切片名 [] 数据类型 = make([]数据类型, len, [cap])
如果分配了cap,一定保证cap > len
var slice1 = make([]float64, 5, 10)
slice1[1] = 2
slice1[0] = 1
fmt.Println(slice1)
用make方法的话,在内存中还是引用了一个数组,但是一个内部数组,对外是不可见的,由make底层维护
3.直接就指定具体数组,使用原理和make类似
var slice []int = []int {1, 3, 5}
注意事项
1.切片的使用可以像Python一样,如arr[0:end] == arr[:end]
2.cap是一个内置函数,用于统计切片的容量,即最大可以存放多少个元素
3.切片定义完成后一定要引用一个数组或make
4.切片还可以再切
append与copy
append
语法:
slice = append(slice, 10, 20, 30)
接收的变量,才是扩容后的切片
底层原理
1.切片append操作的本质是对数组的扩容
2.go底层会按照扩容后的大小创建一个新的数组newArr
3.将slice原来包含的内容与新内容拷贝到新的数组,而原数组会作为垃圾被回收
4.slice引用到newArr
5.newArr也是在底层维护的
copy
语法:
copy(slice, slice_passive)
要求必须都是切片类型,两者的数据空间始终都是独立的
注意:如果被拷贝的切片长度大于接收拷贝的切片长度,那么多余的元素会直接不被拷贝
string和slice
string的底层是一个byte数组,因此string也可以进行切片操作
string是不可变的,也就是不能通过索引的方式来修改字符串,如str[0] = 'z'是不行的
如果需要修改字符串,可以先将字符串转为一个byte切片或者rune切片,然后进行修改,再转为字符串
如:
str := "hello"
arr1 := []byte(str)
arr1[0] = 'z'
str = string(arr1)
注意:转成[]byte后,是不能处理中文的,会出现乱码,汉字为3个字节
如果要处理中文,转为[]rune即可,因为byte是按字节来处理,而rune是按字符来处理的
map
基本语法
map是go的key-value数据结构,和slice一样,也是一种引用类型
基本语法:
var 变量名 map[key的数据类型]value的数据类型
map中的key,可以是多种数据类型,除基本数据类型外,还可以是channel、指针、包含基本数据类型的接口、结构体、数组
注意:slice、map和function是不可以的,因为没有办法用==来判断,map的一个高频率应用就是通过==来判断key存不存在
value的类型限制和key基本一样,通常为int、string、map、struct
如:var a map[string]map[string]string
注意:声明时不会分配内存的,初始化需要make,分配内存后才能赋值和使用
使用方式
1.先声明再make
var cities map[string]string
cities = make(map[string]string, 10)
2.声明就直接make
var cities = make(map[string]string)
3.声明就直接赋值
var cities map[string]string = map[string]string{
"no.1": "北京",
"no.2": "上海",
}
底层实现
// Go map的一个header结构
type hmap struct {
count int // map的大小. len()函数就取的这个值
flags uint8 //map状态标识
B uint8 // 可以最多容纳 6.5 * 2 ^ B 个元素,6.5为装载因子即:map长度=6.5*2^B
//B可以理解为buckets已扩容的次数
noverflow uint16 // 溢出buckets的数量
hash0 uint32 // hash 种子
buckets unsafe.Pointer //指向最大2^B个Buckets数组的指针. count==0时为nil.
oldbuckets unsafe.Pointer //指向扩容之前的buckets数组,并且容量是现在一半.不增长就为nil
nevacuate uintptr // 搬迁进度,小于nevacuate的已经搬迁
extra *mapextra // 可选字段,额外信息
}
//额外信息
type mapextra struct {
overflow *[]*bmap
oldoverflow *[]*bmap
nextOverflow *bmap
}
//在编译期间会产生新的结构体,bucket
type bmap struct {
tophash [8]uint8 //存储哈希值的高8位
data byte[1] //key value数据:key/key/key/.../value/value/value...
overflow *bmap //溢出bucket的地址
}
转自https://blog.csdn.net/qq_48826531/article/details/125907606
CRUD
增:map["key"] = value 未存在是增加,已存在是修改
删:delete(map, "key") 若存在会删除键值对,不存在则不操作,不会报错
若要全部删除,则可以把该map重新make一下,则原map会被gc回收
查:value, findRes = cities["no.1"] 若存在,findRes返回true,否则为false
注意:map的遍历就不能用简单的for循环了,只能用range的方法遍历
map切片
使用slice of map,使得map的元素个数可以动态变化 可以使用append等
map1 := make(map[int]int, 10)
map1[10] = 100
map1[1] = 12
map1[4] = 34
map1[8] = 78
map排序
go中没有一个专门的方法对map的key进行排序,每一次遍历输出的数据可能是不一样的
排序输出:
1.先将map的key放入到切片中
2.对切片排序
3.遍历切片,任何按照key来输出map的值
var keys []int
for k, _ := range map1 {
keys = append(keys, k)
}
// 排序
sort.Ints(keys)
fmt.Println(keys)
for _, k := range keys{
fmt.Println(k, map1[k])
}
注意事项
1.map是一个引用类型,会遵循引用类型传递的机制,即如果用一个函数修改了map,会直接修改原来的map
2.map的容量达到后,再想map增加元素,会自动扩容,并不会发生panic,也就是说map能动态增长键值对
3.map的value经常使用struct类型,适合管理复杂的数据,比如对于有多个信息表
切片和map在要使用前一定要make呦!
以上内容若有不正之处,恳请您不吝指正!