Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 10 additions & 12 deletions src/essential/base/60.slice.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,17 +100,17 @@ cap(nums)

### 切割

切割数组的格式为`arr[startIndex:endIndex]`,切割的区间为**左闭右开**例子如下:
切割数组的格式为`arr[startIndex:endIndex]`,切割的区间为**左闭右开**。且数组在切割后,就会变为切片类型。例子如下:

```go
nums := [5]int{1, 2, 3, 4, 5}
nums[1:] // 子数组范围[1,5) -> [2 3 4 5]
nums[:5] // 子数组范围[0,5) -> [1 2 3 4 5]
nums[2:3] // 子数组范围[2,3) -> [3]
nums[1:3] // 子数组范围[1,3) -> [2 3]
```

数组在切割后,就会变为切片类型
nums[:] // 子切片范围[0,5) -> [1 2 3 4 5]
nums[1:] // 子切片范围[1,5) -> [2 3 4 5]
nums[:5] // 子切片范围[0,5) -> [1 2 3 4 5]
nums[2:3] // 子切片范围[2,3) -> [3]
nums[1:3] // 子切片范围[1,3) -> [2 3]
```

```go
func main() {
Expand Down Expand Up @@ -184,7 +184,7 @@ nums := new([]int) // 指针

::: tip

切片的底层实现依旧是数组,是引用类型,可以简单理解为是指向底层数组的指针。
切片的底层实现依旧是数组,是引用类型,可以简单理解为是指向底层数组的指针(本质上切片在Go中是一个结构体,包含指向底层数组的指针、长度值、容量值)。因此切片作为函数参数传递时不复制底层数组,函数内对传入切片的修改会反映在原切片中

:::

Expand Down Expand Up @@ -414,8 +414,7 @@ s2 := s1[3:4] // cap = 9 - 3 = 6
```go
s1 := []int{1, 2, 3, 4, 5, 6, 7, 8, 9} // cap = 9
s2 := s1[3:4] // cap = 9 - 3 = 6
// 添加新元素,由于容量为6.所以没有扩容,直接修改底层数组
s2 = append(s2, 1)
s2 = append(s2, 1) // 添加新元素,由于容量为6.所以没有扩容,直接修改底层数组
fmt.Println(s2)
fmt.Println(s1)
```
Expand All @@ -433,8 +432,7 @@ fmt.Println(s1)
func main() {
s1 := []int{1, 2, 3, 4, 5, 6, 7, 8, 9} // cap = 9
s2 := s1[3:4:4] // cap = 4 - 3 = 1
// 容量不足,分配新的底层数组
s2 = append(s2, 1)
s2 = append(s2, 1) // 容量不足,分配新的底层数组
fmt.Println(s2)
fmt.Println(s1)
}
Expand Down
4 changes: 2 additions & 2 deletions src/essential/senior/110.concurrency.md
Original file line number Diff line number Diff line change
Expand Up @@ -2302,7 +2302,7 @@ func main() {

### CAS

`atmoic` 包还提供了 `CompareAndSwap` 操作,也就是 `CAS`,它是乐观锁的一种典型实现。乐观锁本身并不是锁,是一种并发条件下无锁化并发控制方式。之所以被称作乐观锁,是因为它总是乐观的假设共享数据不会被修改,仅当发现数据未被修改时才会去执行对应操作,而前面了解到的互斥量就是悲观锁,互斥量总是悲观的认为共享数据肯定会被修改,所以在操作时会加锁,操作完毕后就会解锁。由于无锁化实现的并发安全效率相对于锁要高一些,许多并发安全的数据结构都采用了 `cAS` 来进行实现,不过真正的效率要结合具体使用场景来看。看下面的一个例子:
`atomic` 包还提供了 `CompareAndSwap` 操作,也就是 `CAS`。它是实现乐观锁和无锁数据结构的核心。乐观锁本身并不是锁,是一种并发条件下无锁化并发控制方式:线程/协程在修改数据前,不会先加锁,而是先读取数据,进行计算,然后在提交修改时使用`CAS`来判断在此期间是否有其他线程修改过该数据。如果没有(值仍等于之前读取的值),则修改成功;否则,失败并重试。因此之所以被称作乐观锁,是因为它总是乐观的假设共享数据不会被修改,仅当发现数据未被修改时才会去执行对应操作,而前面了解到的互斥量就是悲观锁,互斥量总是悲观的认为共享数据肯定会被修改,所以在操作时会加锁,操作完毕后就会解锁。由于无锁化实现的并发,其安全性和效率相对于锁要高一些,许多并发安全的数据结构都采用了 `CAS` 来进行实现,不过真正的效率要结合具体使用场景来看。看下面的一个例子:

```go
var lock sync.Mutex
Expand Down Expand Up @@ -2371,7 +2371,7 @@ func main() {
func main() {
var val atomic.Value
val.Store("hello world")
val.Store(114154)
val.Store(114514)
fmt.Println(val.Load())
}
// panic: sync/atomic: store of inconsistently typed value into Value
Expand Down
12 changes: 6 additions & 6 deletions src/essential/senior/120.test.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ PASS
ok golearn/test 0.038s
```

上面三种情况虽然都完成了测试,但是输出结果太简介了,这时可以加上参数`-v`,来使输出结果更加详细一点,例如
上面三种情况虽然都完成了测试,但是输出结果太简洁了,这时可以加上参数`-v`,来使输出结果更加详细一点,例如

```sh
$ go test ./ -v
Expand Down Expand Up @@ -226,7 +226,7 @@ func ExampleWithDeadline() {
}
```

表面上看该测试函数就是一个普通的函数,不过示例测试主要是由`Output`注释来体现的,待测试函数只有一行输出时,使用`Output`注释来检测输出。首先创建一个`hello.go`的文化,写入如下代码
表面上看该测试函数就是一个普通的函数,不过示例测试主要是由`Output`注释来体现的,待测试函数只有一行输出时,使用`Output`注释来检测输出。首先创建一个名为`hello.go`的文件,写入如下代码

```go
package say
Expand Down Expand Up @@ -525,7 +525,7 @@ ok command-line-arguments 0.462s

### Helper

通过`t.Helper()`可以将当前函数标记为帮助函数,帮助函数不会单独作为一个测试用例用于执行,在记录日志时输出的行号也是帮助函数的调用者的行号,这样可以使得分析日志时定位更准确,避免的冗杂的其他信息。比如将上述`t.Cleanup`的例子就可以修改为帮助函数,如下。
通过`t.Helper()`可以将当前函数标记为帮助函数,帮助函数不会单独作为一个测试用例用于执行,在记录日志时输出的行号也是帮助函数的调用者的行号,这样可以使得分析日志时定位更准确,避免的冗杂的其他信息。比如上述`t.Cleanup`的例子就可以修改为帮助函数,如下。

```go
package tool_test
Expand Down Expand Up @@ -748,7 +748,7 @@ ok golearn/tool_test 0.450s

### 表格风格

在上述的单元测试中,测试的输入数据都是手动声明的一个个变量,当数据量小的时候无伤大雅,但如果想要测试多组数据时,就不太可能再去声明变量来创建测试数据,所以一般情况下都是尽量采用结构体切片的形式,结构体是临时声明的匿名结构体,因为这样的编码风格看起来就跟表格一样,所以称为`table-driven`下面举个例子,这是一个手动声明多个变量来创建测试数据的例子,如果有多组数据狠起来就不是很直观,所以将其修改为表格风格
在上述的单元测试中,测试的输入数据都是手动声明的一个个变量,当数据量小的时候无伤大雅,但如果想要测试多组数据时,就不太可能再去声明变量来创建测试数据,所以一般情况下都是尽量采用结构体切片的形式,结构体是临时声明的匿名结构体,因为这样的编码风格看起来就跟表格一样,所以称为`table-driven`下面举个例子,这是一个手动声明多个变量来创建测试数据的例子,如果有多组数据看起来就不是很直观,所以将其修改为表格风格

```go
func TestEqual(t *testing.T) {
Expand Down Expand Up @@ -1020,7 +1020,7 @@ geomean 543.6 541.7 -0.33%
² all samples are equal
```

从结果中可以看出 benchstat 将其分为了三组,分别是耗时,内存占用和内存分配次数,其中`geomoean`为平均值,`p`为 样本的显著性水平,临界区间通常为 0.05,高于 0.05 就不太可信,取其中一条数据如下:
从结果中可以看出 benchstat 将其分为了三组,分别是耗时,内存占用和内存分配次数,其中`geomean`为平均值,`p`为样本的显著性水平,临界区间通常为0.05,高于0.05就不太可信,取其中一条数据如下:

```
│ sec/op │ sec/op vs base │
Expand Down Expand Up @@ -1211,7 +1211,7 @@ func main() {
e4
```

究其原因在于 Go 在字符串单位是字节,而不是字符。所以再次修改待测源代码,如果传入的是非 utf8 字符串,直接返回错误
究其原因在于`\xe4`代表一个字节,但并不是一个有效的UTF-8序列(UTF-8编码中`\xe4`是一个三字节字符的开始,但后面缺少两个字节).转换成`[]rune`时,Golang自动将它变成含单个Unicode字符的`[]rune{"\uFFFD"}`,其反转后仍是`[]rune{"\uFFFD"}`,转换回`string`时该Unicode字符又被替换为其UTF-8编码`\xef\xbf\xbd`。因此一个解决办法是如果传入的是非 utf8 字符串,直接返回错误

```go
func Reverse(s string) (string, error) {
Expand Down