• Golang 常见知识点整理


    0、Go语言strconv包实现字符串和数值类型的相互转换

    Go语言strconv包实现字符串和数值类型的相互转换

    1、Slice

    1.1 定义

    数组是一个由固定长度的特定类型元素组成的序列,一个数组可以由零个或多个元素组成。因为数组的长度是固定的,所以在Go语言中很少直接使用数组。

    切片(slice)是对数组的一个连续片段的引用,所以切片是一个引用类型,这个片段可以是整个数组,也可以是由起始和终止索引标识的一些项的子集,需要注意的是,终止索引标识的项不包括在切片内

    切片是动态结构,只能与 nil 判定相等,不能互相判定相等。声明新的切片后,可以使用 append() 函数向切片中添加元素。

    1.2 切片的底层原理

    切片的底层结构,即 reflect.SliceHeader:

    type SliceHeader struct {
        Data uintptr
        Len int
        Cap int
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    由切片的结构定义可知,切片的结构由三个信息组成:

    指针:指向底层数组中切片指定的开始位置;
    长度:切片的长度;
    容量:当前切片的容量;

    1.3 切片的创建方式

    1.3.1 从已有的数组或切片生成新的切片

    切片默认指向一段连续内存区域,可以是数组,也可以是切片本身。
    从连续内存区域生成切片是常见的操作,格式如下:

    slice [开始位置 : 结束位置]
    
    • 1

    语法说明如下:

    • slice:表示目标切片对象;
    • 开始位置:对应目标切片对象的索引;
    • 结束位置:对应目标切片的结束索引。

    从数组生成切片,代码如下:

    var a  = [3]int{1, 2, 3}
    var aa = a[1:2]
    fmt.Println(a, aa)
    
    • 1
    • 2
    • 3

    其中 a 是一个拥有 3 个整型元素的数组,被初始化为数值 1 到 3,使用 a[1:2] 可以生成一个新的切片 aa,代码运行结果如下:
    [1 2 3] [2]

    其中 [2] 就是 aa 切片操作的结果。

    从数组或切片生成新的切片拥有如下特性:

    • 取出的元素数量为:结束位置 - 开始位置;
    • 取出元素不包含结束位置对应的索引,切片最后一个元素使用 slice[len(slice)] 获取;
    • 当缺省开始位置时,表示从连续区域开头到结束位置;
    • 当缺省结束位置时,表示从开始位置到整个连续区域末尾;
    • 两者同时缺省时,与源切片本身等效;
    • 两者同时为 0 时,等效于空切片,一般用于切片复位。

    1.3.2 直接声明新的切片

    除了可以从原有的数组或者切片中生成切片外,也可以声明一个新的切片,每一种类型都可以拥有其切片类型,表示多个相同类型元素的连续集合,因此切片类型也可以被声明,切片类型声明格式如下:

    var name []Type
    
    • 1

    其中 name 表示切片的变量名,Type 表示切片对应的元素类型。

    下面代码展示了切片声明的使用过程:

    // 声明字符串切片
    var strList []string
    // 声明整型切片
    var numList []int
    // 声明一个空切片
    var numListEmpty = []int{}
    // 输出3个切片
    fmt.Println(strList, numList, numListEmpty)
    // 输出3个切片大小
    fmt.Println(len(strList), len(numList), len(numListEmpty))
    // 切片判定空的结果
    fmt.Println(strList == nil)
    fmt.Println(numList == nil)
    fmt.Println(numListEmpty == nil)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    代码输出结果:

    [] [] []
    0 0 0
    true
    true
    false
    
    • 1
    • 2
    • 3
    • 4
    • 5

    1.3.3 使用 make() 函数构造切片

    如果需要动态地创建一个切片,可以使用 make() 内建函数,格式如下:

    make([]Type, Len, Cap)
    
    • 1

    其中 Type 是指切片的元素类型,Len 指的是为这个类型的切片长度,Cap 为该切片预分配的容量大小,这个值设定后不影响 Len只是能提前分配空间,降低多次分配空间造成的性能问题

    示例如下:

    a := make([]int, 2)
    b := make([]int, 2, 10)
    
    fmt.Println(a, b)
    fmt.Println(len(a), len(b))
    
    • 1
    • 2
    • 3
    • 4
    • 5

    代码输出如下:

    [0 0] [0 0]
    2 2
    
    • 1
    • 2

    其中 a 和 b 均是预分配 2 个元素的切片,只是 b 的内部存储空间已经分配了 10 个,但实际使用了 2 个元素。
    容量不会影响当前的元素个数,因此 a 和 b 取 len 都是 2。

    1.4 注意

    使用 make()函数生成的切片一定发生了内存分配操作,但给定开始与结束位置(包括切片复位)的切片只是将新的切片结构指向已经分配好的内存区域,设定开始与结束位置,不会发生内存分配操作。

    1.5 append()为切片添加元素

    Go语言的内建函数 append() 可以为切片动态添加元素,代码如下所示:

    var a []int
    a = append(a, 1) // 追加1个元素
    a = append(a, 1, 2, 3) // 追加多个元素, 手写解包方式
    a = append(a, []int{1,2,3}...) // 追加一个切片, 切片需要解包
    
    • 1
    • 2
    • 3
    • 4

    在使用 append() 函数为切片动态添加元素时,如果空间不足以容纳足够多的元素,切片就会进行“扩容”,此时新切片的长度会发生改变。切片在扩容时,容量的扩展规律是按容量的 2 倍数进行扩充,例如 1、2、4、8、16……。

    除了在切片的尾部追加,我们还可以在切片的开头添加元素:

    var a = []int{1,2,3}
    a = append([]int{0}, a...) // 在开头添加1个元素
    a = append([]int{-3,-2,-1}, a...) // 在开头添加1个切片
    
    • 1
    • 2
    • 3

    在切片开头添加元素一般都会导致内存的重新分配,而且会导致已有元素全部被复制 1 次,因此,从切片的开头添加元素的性能要比从尾部追加元素的性能差很多

    1.6 切片复制(切片拷贝)

    Go语言的内置函数 copy() 可以将一个数组切片复制到另一个数组切片中,如果加入的两个数组切片不一样大,就会按照其中较小的那个数组切片的元素个数进行复制。

    copy() 函数的使用格式如下:

    copy( destSlice, srcSlice []T) int
    
    • 1

    其中 srcSlice 为数据来源切片,destSlice 为复制的目标(也就是将 srcSlice 复制到 destSlice),目标切片必须分配过空间且足够承载复制的元素个数,并且来源和目标的类型必须一致,copy() 函数的返回值表示实际发生复制的元素个数。

    下面的代码展示了使用 copy() 函数将一个切片复制到另一个切片的过程:

    slice1 := []int{1, 2, 3, 4, 5}
    slice2 := []int{5, 4, 3}
    copy(slice2, slice1) // 只会复制slice1的前3个元素到slice2中
    copy(slice1, slice2) // 只会复制slice2的3个元素到slice1的前3个位置
    
    • 1
    • 2
    • 3
    • 4

    虽然通过循环复制切片元素更直接,不过内置的 copy() 函数使用起来更加方便,copy() 函数的第一个参数是要复制的目标 slice,第二个参数是源 slice,两个 slice 可以共享同一个底层数组,甚至有重叠也没有问题。

    1.7 切片中删除元素

    Go语言并没有对删除切片元素提供专用的方法或者接口,需要使用切片本身的特性来删除元素,根据要删除元素的位置有三种情况,分别是从开头位置删除、从中间位置删除和从尾部删除,其中删除切片尾部的元素速度最快。

    删除的原理是从已有的切片生成新的切片。切片中删除元素

    1.8 循环迭代切片

    切片是一个集合,可以使用 range 迭代其中的元素。

    2、map

    2.1 map的声明

    map 是引用类型,可以使用如下方式声明

    var mapname map[keytype]valuetype
    
    • 1

    其中:

    • mapname 为 map 的变量名。
    • keytype 为键类型。
    • valuetype 是键对应的值类型。

    提示:[keytype] 和 valuetype 之间允许有空格。

    在声明的时候不需要知道 map 的长度,因为 map 是可以动态增长的,未初始化的 map 的值是 nil,使用函数 len() 可以获取 map 中 pair 的数目。

    2.2 map的创建方式

    	//方式1
    	var mapLit = map[string]string{}
    	
    	//方式2
    	mapCreated := make(map[string]float32)
    
    • 1
    • 2
    • 3
    • 4
    • 5

    方式1和方式2等价。但不能使用 new() 来构造 map,如果错误的使用 new() 分配了一个引用对象,会获得一个空引用的指针,相当于声明了一个未初始化的变量并且取了它的地址:

    mapCreated := new(map[string]float)
    
    • 1

    接下来当我们调用mapCreated["key1"] = 4.5的时候,编译器会报错:

    invalid operation: mapCreated["key1"] (index of type *map[string]float).
    
    • 1

    2.3 K-V 一对多实现

    既然一个 key 只能对应一个 value,而 value 又是一个原始类型,那么如果一个 key 要对应多个值怎么办?通过将 value 定义为 []int 类型或者其他类型的切片,就可以优雅的解决这个问题,示例代码如下所示:

    mp1 := make(map[int][]int)
    mp2 := make(map[int]*[]int)
    
    • 1
    • 2

    2.4 map元素的删除和清空

    2.4.1 删除map中的元素

    使用 delete() 内建函数从 map 中删除一组键值对,delete() 函数的格式如下:

    delete(map,)
    
    • 1

    其中 map 为要删除的 map 实例,键为要删除的 map 中键值对的键。

    2.4.2 清空map中的所有元素

    Go语言中并没有为 map 提供任何清空所有元素的函数、方法,清空 map 的唯一办法就是重新 make 一个新的 map,不用担心垃圾回收的效率,Go语言中的并行垃圾回收效率比写一个清空函数要高效的多。

    2.5 sync.Map(在并发环境中使用的map)

    Go语言sync.Map

    3、Go语言list(列表)

    Go语言list(列表)

    在Go语言中,列表使用 container/list 包来实现,内部的实现原理是双向链表,列表能够高效地进行任意位置的元素插入和删除操作。
    list 的初始化有两种方法:分别是使用 New() 函数和 var 关键字声明,两种方法的初始化效果都是一致的。

    3.1 通过container/list包的New()函数初始化 list

    变量名 := list.New()
    
    • 1

    4、Go语言nil:空值/零值

    Go语言nil:空值/零值

    5、Go语言make和new关键字的区别及实现原理

    推荐 :Go语言make和new关键字的区别及实现原理

    • new 关键字只分配内存,当我们想要获取指向某个类型的指针时可以使用 new ;
    • make 关键字的主要作用是初始化内置的数据结构,也就是 slice、map 和 channel 的初始化。

    6、函数

    函数的基本组成:

    1. 关键字 func
    2. 函数名;
    3. 参数列表;
    4. 返回值列表;
    5. 函数体;
    6. 返回语句;

    在函数中,实参通过值传递的方式进行传递,因此函数的形参是实参的拷贝,对形参进行修改不会影响实参,但是,如果实参包括引用类型,如指针、slice(切片)、map、function、channel 等类型,实参可能会由于函数的间接引用被修改。

    6.1 函数类型的变量

    在Go语言中,函数也是一种类型,可以和其他类型一样保存在变量中。
    注意:
    在声明函数类型的变量时,要根据实际调用的具体函数来声明。主要分为三种情况。
    代码示例:

    package main
    
    import (
    	"fmt"
    	"strconv"
    )
    
    func main() {
    	//1、调用无参、无返回值的函数
    	var walkFun func()
    	walkFun = walk
    	walkFun()
    
    	//2、调用有参、无返回值的函数
    	var flyFun func(paramName string)
    	flyFun = fly
    	flyFun("鸟儿")
    
    	//3、调用有参、有返回值的函数
    	var runFun func(paramName string, paramDistance float64) (res string)
    	runFun = run
    	res := runFun("兔子", 500.00)
    	fmt.Println(res)
    }
    
    func walk() {
    	fmt.Println("walk")
    }
    
    func fly(name string) {
    	fmt.Printf("%s 在飞\n", name)
    }
    
    func run(name string, distance float64) (result string) {
    	result = name + ":跑了 " + strconv.FormatFloat(distance, 'f', 3, 64) + " 米"
    	return result
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    6.2 匿名函数

    匿名函数:没有函数名字的函数。

    6.3 闭包

    闭包: 是指匿名函数所使用的变量来自匿名函数外部时,就称这样的匿名函数为闭包(所谓的函数外部指的是不通过形参的方式使用变量,而是在匿名函数内部直接使用变量)。

    闭包是由匿名函数及其相关引用环境组合而成的实体(即:闭包=匿名函数+引用环境)

    闭包对它作用域上部的变量可以进行修改,修改引用的变量会对变量进行实际修改
    Go语言闭包(Closure)——引用了外部变量的匿名函数
    golang之闭包

    闭包有一个特点,包含闭包的函数,它的返回类型都是函数类型,返回的实际上是匿名函数。

    6.4 Go语言defer、panic、recover 详解

    6.4.1 defer

    • Go语言的 defer 语句会将其后面跟随的语句进行延迟处理,在 defer 归属的函数即将返回时,将延迟处理的语句按 defer 的逆序进行执行,也就是说,先被 defer 的语句最后被执行,最后被 defer 的语句,最先被执行。

    • 处理业务或逻辑中涉及成对的操作是一件比较烦琐的事情,比如打开和关闭文件、接收请求和回复请求、加锁和解锁等。在这些操作中,最容易忽略的就是在每个函数退出处正确地释放和关闭资源。

    • defer 语句正好是在函数退出时执行的语句,所以使用 defer 能非常方便地处理资源释放问题。

    defer,panic,recover详解 go 的异常处理

    7、Go语言Test功能

    要开始一个单元测试,需要准备一个 go 源码文件,在命名文件时文件名必须以_test.go结尾,单元测试源码文件可以由多个测试用例(可以理解为函数)组成,每个测试用例的名称需要以 Test 为前缀,例如:

    func TestXxx( t *testing.T ){
        //......
    }
    
    • 1
    • 2
    • 3

    编写测试用例有以下几点需要注意:

    • 测试用例文件不会参与正常源码的编译,不会被包含到可执行文件中;
    • 测试用例的文件名必须以_test.go结尾;
    • 需要使用 import 导入 testing 包;
    • 测试函数的名称要以Test或Benchmark开头,后面可以跟任意字母组成的字符串,但第一个字母必须大写,例如 TestAbc(),一个测试用例文件中可以包含多个测试函数;
    • 单元测试则以(t *testing.T)作为参数,性能测试以(t *testing.B)做为参数;
    • 测试用例文件使用go test命令来执行,源码中不需要 main() 函数作为入口,所有以_test.go结尾的源码文件内以Test开头的函数都会自动执行。

    8、结构体

    Go 语言中的类型可以被实例化,使用new&构造的类型实例的类型是类型的指针。

    结构体成员是由一系列的成员变量构成,这些成员变量也被称为“字段”。字段有以下特性:

    • 字段拥有自己的类型和值。
    • 字段名必须唯一。
    • 字段的类型也可以是结构体,甚至是字段所在结构体的类型。

    8.1 实例化

    8.1.1 基本的实例化形式

    var ins T
    
    • 1

    其中,T 为结构体类型,ins 为结构体的实例。

    8.1.2 创建指针类型的结构体来实例化

    使用 new 关键字对类型(包括结构体、整型、浮点数、字符串等)进行实例化,结构体在实例化后会形成指针类型的结构体。

    ins := new(T)
    
    • 1

    其中:

    • T 为类型,可以是结构体、整型、字符串等。
    • ins:T 类型被实例化后保存到 ins 变量中,ins 的类型为 *T,属于指针。

    8.1.3 取结构体的地址实例化

    在Go语言中,对结构体进行&取地址操作时,视为对该类型进行一次 new 的实例化操作,取地址格式如下:

    ins := &T{}
    
    • 1

    其中:

    • T 表示结构体类型。
    • ins 为结构体的实例,类型为 *T,是指针类型。

    取地址实例化是最广泛的一种结构体实例化方式

    8.2 初始化结构体的成员变量

    初始化有两种形式分别是以字段“键值对”形式多个值的列表形式

    • 键值对:键值对形式的初始化适合选择性填充字段较多的结构体;
    • 多个值:多个值的列表形式适合填充字段较少的结构体。

    8.2.1 键值对形式的初始化

    ins := 结构体类型名{
        字段1: 字段1的值,
        字段2: 字段2的值,}
    
    • 1
    • 2
    • 3
    • 4
    • 5

    键值之间以:分隔,键值对之间以,分隔。

    提示:结构体成员中只能包含结构体的指针类型,包含非指针类型会引起编译错误。

    示例:

    type People struct {
        name  string
        child *People
    }
    relation := &People{
        name: "爷爷",
        child: &People{
            name: "爸爸",
            child: &People{
                    name: "我",
            },
        },
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    8.2.2 多个值的列表初始化

    ins := 结构体类型名{
        字段1的值,
        字段2的值,}
    
    • 1
    • 2
    • 3
    • 4
    • 5

    多个值使用逗号分隔初始化结构体。

    8.3 Go语言方法和接收器、为任意类型添加方法

  • 相关阅读:
    赋能金融风控加分项的这30个问题,您都搞懂了吗
    Elasticsearch-使用Logstash同步Mysql
    解决Netty那些事儿之Reactor在Netty中的实现(创建篇)-上
    SpringBoot 快速入门(保姆级详细教程)
    el-table,列表合并,根据名称列名称相同的品名将其它列值相同的进行合并
    每日五问(java)
    性能测试:系统架构性能优化思路
    基于 ACK Fluid 的混合云优化数据访问(一):场景与架构
    【leetcode】【剑指offer Ⅱ】050. 向下的路径节点之和
    springcloud-gateway include-expression 配置说明
  • 原文地址:https://blog.csdn.net/Mr_XiMu/article/details/126229906