GO语言核心30讲 进阶技术 (第一部分)

devtools/2024/9/23 10:20:43/

 原站地址:Go语言核心36讲_Golang_Go语言-极客时间

一、数组和切片

1. 两者最大的不同:数组的长度是固定的,而切片的长度是可变的。

2. 可以把切片看成是对数组的一层封装,因为每个切片的底层数据结构中,一定会包含一个数组。 切片也可以被看作是对底层数组的某个连续片段的引用(窗口)。

3. go语言不存在传值或传引用两种区分。 只要传递的是引用类型的,就是“传引用”。如果传递的是值类型的,就是“传值”。   切片属于引用类型,数组属于值类型。

4. 内建函数 len()计算长度,cap()计算容量。

例子:  s1是切片,底层数组是8个元素。 s2 := s1[3:6]

len(s2) = 6-3, 即 结束索引 减去 起始索引

cap(s2) = 8-3,即 底层数组长度 减去  起始索引 , 从起始索引位置向右扩展到最末端

5. 切片容量的增长逻辑:

(1) 一般情况下,新切片的容量将会是原切片容量的 2 倍。

(2) 如果新长度比原容量的 2 倍还要大,那么新容量就会以新长度为基准。

(3) 如果原长度大于等于1024,会以原容量的1.25倍作为基准,一直扩展到满足扩容需求。

6. 切片的底层数组在扩容时不会被替换。 扩容时是使用了新的切片和新的底层数组。 旧切片不变。

二、container包中的容器:List 和 Ring

1. List 容器,是一个双向链表,所以它支持下面的方法:

(1) 把内部元素移动到链表头部或尾部,或移动到另一个元素的前面或后面

(2) 插入新元素到链表头部或尾部,或一个元素的前面或后面

(3) 如果自己生成元素,直接传给链表的方法做 移动操作,链表不会接受。因为可以根据前后元素的指针数据做合法性判定。

2. List 支持开箱即用。经过声明的List变量 是一个长度为0的链表,是一个空壳,但可以直接使用。 使用之后再做初始化。这种延迟初始化可以分散初始化带来的计算量压力。

3. List内部就是一个循环链表。它的根元素不持有实际的元素值,但连接了这个循环链表的首尾两端。

4. List 和 Ring 的区别

(1) List 和 Ring 都是循环链表

(2) List 可以添加和删除元素, Ring不可以。Ring要添加元素,需要重建一个新的Ring.

(3) Ring的数据结构仅由它自身即可代表,而List则需要由它以及Element类型联合表示。

三、字典的操作和约束

1. 哈希表查找某个 键值对应的元素值 的过程:

(1) 用哈希函数把键值转换为哈希值。 哈希值是一个无符号的整数

(2) 哈希表持有一定数量的桶(bucket),这些哈希桶储存其所属的一定数量 键-元素对。

(3) 哈希表用这个键的哈希值去定位一个哈希桶,然后在这个哈希桶中查找这个键-元素对。

2.  Map的键类型必须支持操作符 == 和 != 。 函数、字典和切片类型的值并不支持判等操作,所以他们不能作为键。

     如果键的类型是接口类型,而且实际类型也是上述三种类型,在程序运行过程中会引发 panic。

     如果键的类型是自定义结构体,而且包含上述三种类型,也是会panic

3. 键的数据类型,优先选用数值和指针类型。

    求哈希和判等操作的速度越快,对应的类型就越适合作为键类型。

    宽度越小、长度越短,求哈希值的速度越快。

4. 在值为nil的字典上执行读和写操作会成功吗?

(1) 仅声明而不初始化 字典变量,它的值会是nil。

(2) 往nil字段添加删除元素值,会panic

(3) 其他nil字典上的任何操作,包括获取元素值,都不会panic

四、通道的基本操作

1.  通道相当于一个先进先出队列,先被发送到通道的元素值,一定会先被接收。

2.  声明并初始化通道: ch1 := make(chan int, 3)     元素类型为int、容量为3

     往通道发送数据: ch1 <- 1

     从通道接收数据: data := <- ch1

3. 通道的三个特性:

(1) 发送操作之间,接收操作之间是互斥的。即多个goroutine要同时发通道发送数据时,只有一个能起作用。  对于同一个数据而言, 发送和接收两个操作也是互斥的。

(2) 发送和接收时,对数据的处理是不可分割的。即 只会是没发送或发送完,不会是发送了一半。

在通道中的数据,是被复制的一个副本。

(3) 发送和接收操作在完成之前, goroutine会被阻塞。包含复制、放置、删除三个步骤。如此阻塞是为了保证数据的安全和完整性。

4. 发送和接收是,触发阻塞状态的几种情况:

(1) 非缓冲通道,一执行就会阻塞

(2) 带缓冲的通道,缓冲区满了时发送操作会阻塞,缓冲区空了是接收操作会阻塞。

(3) 值为nil的通道,执行操作时会立刻阻塞。 不要忘记初始化通道。

5. 会引发panic的情况

(1) 向已经关闭的通道发送数据会panic。 但是接收数据的话不会。

(2) 试图关闭一个已经关闭的通道,会panic

6. 关闭通道,要交给发送方来做。

    因为即使通道已经关闭,接收方也能成功完成接收操作 (第二个结果值是true)。如果根据这个结果值来判断是否要做关闭操作的话,会引发重复关闭通道,导致panic。  所以应该让发送方根据发送逻辑来判定是否要关闭通道。

五、通道的高级玩法

1. 创建一个通道: ch := make(chan int, 1)

    创建单向只发通道: ch := make(chan <- int, 1)

    创建单向只收通道: ch := make( <- chan int, 1)

2. 单向通道的用途是约束局部代码的行为。某部分代码值能发送,某部分代码值能接收。

3. 只发通道应用: 只发通道作为参数传入

func SendInt(ch chan<- int) {ch <- rand.Intn(1000)
}

    外部函数向上面这个函数传参时,可以传入一个双向通道,GO语言会自动把双向通道转换为单向通道。

4. 只收通道应用: 只收通道作为结果返回

func getIntChan() <-chan int {num := 5ch := make(chan int, num)for i := 0; i < num; i++ {ch <- i}close(ch)return ch
}

   注意,这里函数内部已经把通道给关闭了

5. 用 for range 接收通道数据

intChan2 := getIntChan()
for elem := range intChan2 {fmt.Printf("The element in intChan2: %v\n", elem)
}

  for 循环会一直从通道接收数据。 接收完之后,如果通道没关闭就会阻塞,如果关闭了就会直接执行退出for循环。

6. select语句与通道联用

// 准备好几个通道。
intChannels := [2]chan int{make(chan int, 1),make(chan int, 1),
}
// 随机选择一个通道,并向它发送元素值。
index := rand.Intn(2)
fmt.Printf("The index: %d\n", index)
intChannels[index] <- index
// 哪一个通道中有可取的元素值,哪个对应的分支就会被执行。
select {
case <-intChannels[0]:fmt.Println("The first candidate case is selected.")
case <-intChannels[1]:fmt.Println("The second candidate case is selected.")
default:fmt.Println("No candidate case is selected!")
}

(1) select语句只能与通道联用

(2) 加入了 default 分支后,当其他分支都出现阻塞,select语句就会进入default分支。

(3) 没有加入default 分支的话,其他分支都出现阻塞,select语句就会阻塞。

(4) select只会对分支的通道求值操作一次,如果要反复操作通道的话,需要加for循环。但要注意,default里的代码执行break,只会结束掉select语句,不会结束掉for语句。

7. select 的分支选择规则都有哪些?

(1) case 右边的表达式,可以是通道接收操作,也可以是发送操作。

(2) case 右边的表达式,可以有多个接收操作。

(3) select语句的执行是先全部分支case求值,完毕后再选择其中一个分支。

(4) case 求值顺序是从上至下,从左至右。

(5) 多个case满足条件的话,select语句会随机选择一个分支去执行。

(6) default 分支只有在无候选分支可选时才会被执行,与default分支是位置无关

六、使用函数的正确姿势

1. 什么是高阶函数?  (函数式编程的重要概念) 满足以下两条件之一:

(1) 接受其他函数作为参数输入

(2) 把其他函数作为结果返回

2. 如何实现高阶函数? 下面以 函数作为参数输入 做例子。

(1) 声明一个名叫operate的函数类型

type operate func(x, y int) int

(2) 实现这个函数(低阶),签名与上面声明的一样。并赋值给函数变量 op

op := func(x, y int) int {return x + y
}

(3) 实现高阶函数,把低阶函数 operate 作为参数输入。 并调用执行这个函数,op()

func calculate(x int, y int, op operate) (int, error) {res := op(x, y)return res , nil
}

    这个 calculate 就是高阶函数,提供给外部调用。

3. 那如何把 函数作为结果返回

func genCalculator() calculateFunc {f := func(x int, y int) (int, error) {return x + y}return f, nil
}

     genCalculator 内部实现了一个匿名函数,作为结果值返回

4. 什么是自由变量?作用是什么?

    自由变量 是一个低阶函数,这个低阶函数是从外部传入的,如何实现由外部决定。

    作用是 动态地生成部分程序逻辑, 再根据需要生成功能不同的函数。

5. 传入函数的那些参数值,被修改后,原值会发生变化吗?

    值类型比如数组的话,不会。

    引用类型比如切片、字典、通道的话,会。

七、结构体及其方法的使用法门

1. 什么叫嵌入字段?

// Category 代表动物分类学中的基本分类法。
type Category struct {family string // 科。species string // 种。
}func (ac Category) String() string {return fmt.Sprintf("%s%s", ac.family, ac.species)
}type Animal struct {name string // 名字。Category    // 分类。
}

    上面结构体 Animal 的 Category 字段就是嵌入字段,也叫匿名字段,没有声明名称,它既是类型也是名称

2.  怎样调用嵌入字段的方法?

    通过类型变量的名称 Category 后跟 “.”, 这样: 

animal := Animal{name: "Shorthair",
}
animal.Category.String()

3.  嵌入字段的方法 会被无条件地 合并进 被嵌入类型的方法集合中.

    比如上面 Animal类型变量animal可以直接使用Category变量的方法: animal.String()

4. 如果 嵌入结构体(Category) 和 被嵌入结构体(Animal) 存在相同的方法名字,那么 嵌入结构体(Category) 的方法会被屏蔽。 即外层优先。

5. GO语言没有继承,但是用 嵌入的方式实现了类型之间的组合。(但和继承有差别,且更优)

6. GO的组合,和继承的区别:

(1) 组合 不需要 显式地声明某个类型继承了另一个类型,只需要把类型当做字段嵌入进来。

(2) 组合 可以通过嵌入多个字段来实现功能强大的类型,却不会有多重继承那样复杂的层次结构。

7. 接口类型之间也可以组合,而且更加常见

8. 什么叫 接受者类型 和 接受者名称 ? 如下代码

func (cat *Cat) SetName(name string) {cat.name = name
}

   *Cat 叫 接收者类型, cat 叫 接收者名称

9. 什么叫指针方法?  就是 接收者类型是指针类型的方法。  不是指针类型的话,叫做 值方法。 

10. 值方法和指针方法的不同点:

(1) 值方法的修改 不会修改 接受者的原值; 但是指针方法的修改 会改变原值

(2) 值方法集合 和 指针方法集合 两者是不同的。但指针方法集合 包含了 值方法集合

(3) 严格来讲,基本类型的值上只能调用它的值方法。但是,Go 语言会自动转译,使得值上也能调用到指针方法。

(4) 如果基本类型和指针类型的方法集合不同,那么它们实现的接口类型的数量也会有差异。


http://www.ppmy.cn/devtools/31073.html

相关文章

数据结构与算法之经典排序算法

一、简单排序 在我们的程序中&#xff0c;排序是非常常见的一种需求&#xff0c;提供一些数据元素&#xff0c;把这些数据元素按照一定的规则进行排序。比如查询一些订单按照订单的日期进行排序&#xff0c;再比如查询一些商品&#xff0c;按照商品的价格进行排序等等。所以&a…

常用设计模式

单例模式 一个类只能创建一个对象&#xff0c;即单例模式&#xff0c;该设计模式可以保证系统中该类只有⼀个实例&#xff0c;并提供⼀个访问它的全局访问点&#xff0c;该实例被所有程序模块共享。比如在某个服务器程序中&#xff0c;该服务器的配置信息存放在⼀个文件中&…

高可用系列四:loadbalancer 负载均衡

负载均衡可以单独使用&#xff0c;也常常与注册中心结合起来使用&#xff0c;其需要解决的问题是流量分发&#xff0c;这是就需要定义分发策略&#xff0c;当然也包括了故障切换的能力。 故障切换 故障切换是负载均衡的基本能力&#xff0c;和注册中心结合时比较简单&#xf…

Arduino 推出带 Wi-Fi的 32 位 UNO 板

Arduino 推出了下一代 UNO 板&#xff0c;引入了 32 位 Renesas 微控制器和 Espressif ESP32-S3 模块、一键云连接和大量 I/O 以及 128 红色 LED 矩阵。新型 UNO R4 板有两个版本&#xff0c;带 Wi-Fi 连接和不带 Wi-Fi 连接&#xff0c;并保持了 UNO R3 的外形尺寸、屏蔽兼容性…

工具链工具——映射与调度、模拟与验证、开发与测试工具

本篇文章将重点介绍工具链的工具相关知识&#xff0c;我们将从工具链的基本概念出发&#xff0c;重点介绍工具链中的映射和调度工具、模拟与验证工具、开发和测试工具&#xff0c;最后提出对工具链发展的展望&#xff0c;从而对工具链的工具进行一个较为系统的讲解。 工具链的…

网络安全之密码学技术

文章目录 网络信息安全的概念数据加密|解密概念密码学概论密码学分类古典密码学现代密码学 现代密码学的相关概念对称加密算法对称加密算法—DES对称加密算法—3DES对称加密算法—AES对称加密算法—IDEA 非对称加密算法非对称加密算法—RSA非对称加密算法—ElGamal非对称加密算…

Matlab|基于多目标粒子群算法的微电网优化调度

目录 1 主要内容 2 部分代码 3 效果图 4 下载链接 1 主要内容 本程序为《基于多目标粒子群算法的微电网优化调度》-王金全文章的方法复现&#xff0c;考虑因素较文章复杂&#xff0c;除了考虑基本机组、储能等的出力&#xff0c;还考虑了弃风和弃光&#xff0c;很值得大家…

C++ 重载 [] 运算符

刚开始我是震惊的! 我从未想过[]下居然有逻辑! 从学步开始 曾因会使用a[0]访问数组元素而沾沾自喜 曾固步自封的认为[] ,理应是访问数组的一种方式 天真快乐的同时,认为[]只是一个无情的标识! 所以 当我们写下a[0]时,究竟是为了什么? 是为了找到a[0]对应的值 那么如何…