Go语言不太快速的入门(2),详解向文章| 青训营笔记

118 阅读10分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第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呦!

以上内容若有不正之处,恳请您不吝指正!