mirror of
https://github.com/chai2010/advanced-go-programming-book.git
synced 2025-05-24 04:22:22 +00:00
修改 ch1-03 的排版
This commit is contained in:
parent
6c6fe3f000
commit
b52cc97335
@ -21,7 +21,7 @@ var d = [...]int{1, 2, 4: 5, 6} // 定义长度为6的int型数组, 元素为 1,
|
|||||||
|
|
||||||
第二种方式定义数组,可以在定义的时候顺序指定全部元素的初始化值,数组的长度根据初始化元素的数目自动计算。
|
第二种方式定义数组,可以在定义的时候顺序指定全部元素的初始化值,数组的长度根据初始化元素的数目自动计算。
|
||||||
|
|
||||||
第三种方式是以索引的方式来初始化数组的元素,因此元素的初始化值出现顺序比较随意。这种初始化方式和`map[int]Type`类型的初始化语法类似。数组的长度以出现的最大的索引为准,没有明确初始化的元素依然用0值初始化。
|
第三种方式是以索引的方式来初始化数组的元素,因此元素的初始化值出现顺序比较随意。这种初始化方式和 `map[int]Type` 类型的初始化语法类似。数组的长度以出现的最大的索引为准,没有明确初始化的元素依然用零值初始化。
|
||||||
|
|
||||||
第四种方式是混合了第二种和第三种的初始化方式,前面两个元素采用顺序初始化,第三第四个元素零值初始化,第五个元素通过索引初始化,最后一个元素跟在前面的第五个元素之后采用顺序初始化。
|
第四种方式是混合了第二种和第三种的初始化方式,前面两个元素采用顺序初始化,第三第四个元素零值初始化,第五个元素通过索引初始化,最后一个元素跟在前面的第五个元素之后采用顺序初始化。
|
||||||
|
|
||||||
@ -31,7 +31,6 @@ var d = [...]int{1, 2, 4: 5, 6} // 定义长度为6的int型数组, 元素为 1,
|
|||||||
|
|
||||||
*图 1-7 数组布局*
|
*图 1-7 数组布局*
|
||||||
|
|
||||||
|
|
||||||
Go 语言中数组是值语义。一个数组变量即表示整个数组,它并不是隐式的指向第一个元素的指针(比如 C 语言的数组),而是一个完整的值。当一个数组变量被赋值或者被传递的时候,实际上会复制整个数组。如果数组较大的话,数组的赋值也会有较大的开销。为了避免复制数组带来的开销,可以传递一个指向数组的指针,但是数组指针并不是数组。
|
Go 语言中数组是值语义。一个数组变量即表示整个数组,它并不是隐式的指向第一个元素的指针(比如 C 语言的数组),而是一个完整的值。当一个数组变量被赋值或者被传递的时候,实际上会复制整个数组。如果数组较大的话,数组的赋值也会有较大的开销。为了避免复制数组带来的开销,可以传递一个指向数组的指针,但是数组指针并不是数组。
|
||||||
|
|
||||||
```go
|
```go
|
||||||
@ -165,7 +164,6 @@ type StringHeader struct {
|
|||||||
|
|
||||||
*图 1-8 字符串布局*
|
*图 1-8 字符串布局*
|
||||||
|
|
||||||
|
|
||||||
分析可以发现,“Hello, world”字符串底层数据和以下数组是完全一致的:
|
分析可以发现,“Hello, world”字符串底层数据和以下数组是完全一致的:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
@ -375,7 +373,6 @@ type SliceHeader struct {
|
|||||||
|
|
||||||
*图 1-10 切片布局*
|
*图 1-10 切片布局*
|
||||||
|
|
||||||
|
|
||||||
让我们看看切片有哪些定义方式:
|
让我们看看切片有哪些定义方式:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
@ -412,7 +409,6 @@ var (
|
|||||||
|
|
||||||
如前所说,切片是一种简化版的动态数组,这是切片类型的灵魂。除了构造切片和遍历切片之外,添加切片元素、删除切片元素都是切片处理中经常遇到的问题。
|
如前所说,切片是一种简化版的动态数组,这是切片类型的灵魂。除了构造切片和遍历切片之外,添加切片元素、删除切片元素都是切片处理中经常遇到的问题。
|
||||||
|
|
||||||
|
|
||||||
**添加切片元素**
|
**添加切片元素**
|
||||||
|
|
||||||
内置的泛型函数 `append` 可以在切片的尾部追加 `N` 个元素:
|
内置的泛型函数 `append` 可以在切片的尾部追加 `N` 个元素:
|
||||||
@ -421,7 +417,7 @@ var (
|
|||||||
var a []int
|
var a []int
|
||||||
a = append(a, 1) // 追加 1 个元素
|
a = append(a, 1) // 追加 1 个元素
|
||||||
a = append(a, 1, 2, 3) // 追加多个元素, 手写解包方式
|
a = append(a, 1, 2, 3) // 追加多个元素, 手写解包方式
|
||||||
a = append(a, []int{1,2,3}...) // 追加一个切片, 切片需要解包
|
a = append(a, []int{1,2,3}...) // 追加 1 个切片, 切片需要解包
|
||||||
```
|
```
|
||||||
|
|
||||||
不过要注意的是,在容量不足的情况下,`append` 的操作会导致重新分配内存,可能导致巨大的内存分配和复制数据代价。即使容量足够,依然需要用 `append` 函数的返回值来更新切片本身,因为新切片的长度已经发生了变化。
|
不过要注意的是,在容量不足的情况下,`append` 的操作会导致重新分配内存,可能导致巨大的内存分配和复制数据代价。即使容量足够,依然需要用 `append` 函数的返回值来更新切片本身,因为新切片的长度已经发生了变化。
|
||||||
@ -520,7 +516,6 @@ a = a[:i+copy(a[i:], a[i+N:])] // 删除中间N个元素
|
|||||||
|
|
||||||
比如下面的 `TrimSpace` 函数用于删除 `[]byte` 中的空格。函数实现利用了 0 长切片的特性,实现高效而且简洁。
|
比如下面的 `TrimSpace` 函数用于删除 `[]byte` 中的空格。函数实现利用了 0 长切片的特性,实现高效而且简洁。
|
||||||
|
|
||||||
|
|
||||||
```go
|
```go
|
||||||
func TrimSpace(s []byte) []byte {
|
func TrimSpace(s []byte) []byte {
|
||||||
b := s[:0]
|
b := s[:0]
|
||||||
@ -549,7 +544,6 @@ func Filter(s []byte, fn func(x byte) bool) []byte {
|
|||||||
|
|
||||||
切片高效操作的要点是要降低内存分配的次数,尽量保证 `append` 操作不会超出 `cap` 的容量,降低触发内存分配的次数和每次分配内存大小。
|
切片高效操作的要点是要降低内存分配的次数,尽量保证 `append` 操作不会超出 `cap` 的容量,降低触发内存分配的次数和每次分配内存大小。
|
||||||
|
|
||||||
|
|
||||||
**避免切片内存泄漏**
|
**避免切片内存泄漏**
|
||||||
|
|
||||||
如前面所说,切片操作并不会复制底层的数据。底层的数组会被保存在内存中,直到它不再被引用。但是有时候可能会因为一个小的内存引用而导致底层整个数组处于被使用的状态,这会延迟自动内存回收器对底层数组的回收。
|
如前面所说,切片操作并不会复制底层的数据。底层的数组会被保存在内存中,直到它不再被引用。但是有时候可能会因为一个小的内存引用而导致底层整个数组处于被使用的状态,这会延迟自动内存回收器对底层数组的回收。
|
||||||
@ -592,7 +586,6 @@ a = a[:len(a)-1] // 从切片删除最后一个元素
|
|||||||
|
|
||||||
当然,如果切片存在的周期很短的话,可以不用刻意处理这个问题。因为如果切片本身已经可以被 GC 回收的话,切片对应的每个元素自然也就是可以被回收的了。
|
当然,如果切片存在的周期很短的话,可以不用刻意处理这个问题。因为如果切片本身已经可以被 GC 回收的话,切片对应的每个元素自然也就是可以被回收的了。
|
||||||
|
|
||||||
|
|
||||||
**切片类型强制转换**
|
**切片类型强制转换**
|
||||||
|
|
||||||
为了安全,当两个切片类型 `[]T` 和 `[]Y` 的底层原始切片类型不同时,Go 语言是无法直接转换类型的。不过安全都是有一定代价的,有时候这种转换是有它的价值的——可以简化编码或者是提升代码的性能。比如在 64 位系统上,需要对一个 `[]float64` 切片进行高速排序,我们可以将它强制转为 `[]int` 整数切片,然后以整数的方式进行排序(因为 `float64` 遵循 IEEE754 浮点数标准特性,当浮点数有序时对应的整数也必然是有序的)。
|
为了安全,当两个切片类型 `[]T` 和 `[]Y` 的底层原始切片类型不同时,Go 语言是无法直接转换类型的。不过安全都是有一定代价的,有时候这种转换是有它的价值的——可以简化编码或者是提升代码的性能。比如在 64 位系统上,需要对一个 `[]float64` 切片进行高速排序,我们可以将它强制转为 `[]int` 整数切片,然后以整数的方式进行排序(因为 `float64` 遵循 IEEE754 浮点数标准特性,当浮点数有序时对应的整数也必然是有序的)。
|
||||||
@ -631,4 +624,3 @@ func SortFloat64FastV2(a []float64) {
|
|||||||
第二种转换操作是分别取到两个不同类型的切片头信息指针,任何类型的切片头部信息底层都是对应 `reflect.SliceHeader` 结构,然后通过更新结构体方式来更新切片信息,从而实现 `a` 对应的 `[]float64` 切片到 `c` 对应的 `[]int` 类型切片的转换。
|
第二种转换操作是分别取到两个不同类型的切片头信息指针,任何类型的切片头部信息底层都是对应 `reflect.SliceHeader` 结构,然后通过更新结构体方式来更新切片信息,从而实现 `a` 对应的 `[]float64` 切片到 `c` 对应的 `[]int` 类型切片的转换。
|
||||||
|
|
||||||
通过基准测试,我们可以发现用 `sort.Ints` 对转换后的 `[]int` 排序的性能要比用 `sort.Float64s` 排序的性能好一点。不过需要注意的是,这个方法可行的前提是要保证 `[]float64` 中没有 NaN 和 Inf 等非规范的浮点数(因为浮点数中 NaN 不可排序,正 0 和负 0 相等,但是整数中没有这类情形)。
|
通过基准测试,我们可以发现用 `sort.Ints` 对转换后的 `[]int` 排序的性能要比用 `sort.Float64s` 排序的性能好一点。不过需要注意的是,这个方法可行的前提是要保证 `[]float64` 中没有 NaN 和 Inf 等非规范的浮点数(因为浮点数中 NaN 不可排序,正 0 和负 0 相等,但是整数中没有这类情形)。
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user