GO语言核心30讲 进阶技术 (数组切片,list ring,字典,通道,函数,结构体)

 原站地址: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 := 5
  ch := 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) 如果基本类型和指针类型的方法集合不同,那么它们实现的接口类型的数量也会有差异。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值