https://segmentfault.com/a/

Slice 和 Array 维度是一维

级别:新手入门级

Go 看上去支持多维的 Array 和 Slice,但是其实不然。尽管可以创建 Array 的 Array,也可以创建 Slice 的 Slice。对于依赖多维 Array 的计算密集型的程序,无论是从性能还是复杂程度,Go 都不是最佳选择。

当然,如果你选择创建嵌套的 Array 与嵌套的 Slice,那么你就得自己负责进行索引、进行下表检查、以及 Array 增长时的内存分配。嵌套 Slice 分为两种,Slice 中嵌套独立的 Slice,或者 Slice 中嵌套共享数据的 Slice。

使用嵌套的独立 Slice 创建多维的 Array 需要两步。第一步,创建外围 Slice,然后分配每个内部的 Slice。内部的 Slice 是独立的,可以对每个单独的内部 Slice 进行缩放。

1
2
3
4
5
6
7
8
9
10
11
package main
func main() {
x := 2
y := 4
table := make([][]int,x)
for i:= range table {
table[i] = make([]int,y)
}
}

使用嵌套、共享数据的 Slice 创建多维 Array 需要三步。第一,创建数据“容器”,第二部,创建外围 Slice,第三部,对内部的 Slice 进行初始化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package main
import "fmt"
func main() {
h, w := 2, 4
raw := make([]int,h*w)
for i := range raw {
raw[i] = i
}
fmt.Println(raw,&raw[4])
//prints: [0 1 2 3 4 5 6 7] <ptr_addr_x>
table := make([][]int,h)
for i:= range table {
table[i] = raw[i*w:i*w + w]
}
fmt.Println(table,&table[1][0])
//prints: [[0 1 2 3] [4 5 6 7]] <ptr_addr_x>
}

Go 语言也有对于支持多维 Array 和 Slice 的提案,不过不要期待太多。Go 语言官方将这些需求分在“低优先级”组中。

试图访问不存在的 Map 键值

级别:新手入门级

并不能在所有情况下都能通过判断 map 的记录值是不是 nil 判断记录是否存在。在 Go 语言中,对于“零值”是 nil 的数据类型可以这样判断,但是其他的数据类型不可以。简而言之,这种做法并不可靠(例如布尔变量的“零值”是 false)。最可靠的做法是检查 map 记录的第二返回值。
错误代码:

1
2
3
4
5
6
7
8
9
10
11
package main
import "fmt"
func main() {
x := map[string]string{"one":"a","two":"","three":"c"}
if v := x["two"]; v == "" { //incorrect
fmt.Println("no entry")
}
}

修正代码:

1
2
3
4
5
6
7
8
9
10
11
package main
import "fmt"
func main() {
x := map[string]string{"one":"a","two":"","three":"c"}
if _,ok := x["two"]; !ok {
fmt.Println("no entry")
}
}

String 不可变

级别:新手入门级

对于 String 中单个字符的操作会导致编译失败。String 是带有一些附加属性的只读的字节片(Byte Slices)。所以如果想要对 String 操作的话,应当使用字节片操作,而不是将它转换为 String 类型。

错误信息:

1
2
3
4
5
6
7
8
9
10
package main
import "fmt"
func main() {
x := "text"
x[0] = 'T'
fmt.Println(x)
}

错误信息:

1
/tmp/sandbox305565531/main.go:7: cannot assign to x[0]

修正代码:

1
2
3
4
5
6
7
8
9
10
11
package main
import "fmt"
func main() {
x := "text"
xbytes := []byte(x)
xbytes[0] = 'T'
fmt.Println(string(xbytes)) //prints Text
}

注意这里的操作并不就是最正确的操作,因为有些字符可能会存储在多个字节中。如果你的开发情景有这种情况的话,需要先把 String 转换为 rune 格式。即便是 rune,一个字符也可能会保存在多个 rune 中,因此 Go String 也表现为字节序列(String Sequences)。Rune 一般理解为“字符”,“一个字符也可能会保存在多个 rune 中”的情况我们将会在下面提到。

String 与 Byte Slice 的转换

级别:新手入门级

当将 String 类型和 Byte Slice 类型互相转化的时候,得到的新数据都是原数据的拷贝,而不是原数据。类型转化和切片重组(Resliciing)不一样,切片重组后的变量仍然指向原变量,而类型转换后的变量指向原变量的拷贝。

Go 语言已经对 []byte 和 String 类型的互相转化做了优化,并且还会继续优化。

The first optimization avoids extra allocations when []byte keys are used to lookup entries in map[string] collections: m[string(key)].
一个优化是

The second optimization avoids extra allocations in for range clauses where strings are converted to []byte: for i,v := range []byte(str) {…}.

String 与下标

级别:新手入门级

和其他语言不同,String 的下表返回值是 Byte 类型的值,而不是字符类型。

1
2
3
4
5
6
7
8
9
package main
import "fmt"
func main() {
x := "text"
fmt.Println(x[0]) //print 116
fmt.Printf("%T",x[0]) //prints uint8
}

如果需要在 UTF8 类型的 String 中取出指定字符,那么需要用到 unicode/utf8 与实验性的 [utf8string]() 包。utf8string 包包含 AT() 方法,可以取出字符,也可以将 String 转换为 Rune SLice。

String并不一定是UTF8格式

级别:新手入门级

String 类型不一定是 UTF8 格式,String 中也可以包含自定义的文字/字节。只有需要将字符串显示出来的时候才需要用 UTF8 格式,其他情况下可以随便用转义来表示任意字符。

可以使用 unicode/utf8 包中体重的 ValidString() 方法判断是否是 UTF8 类型的文本。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package main
import (
"fmt"
"unicode/utf8"
)
func main() {
data1 := "ABC"
fmt.Println(utf8.ValidString(data1)) //prints: true
data2 := "A\xfeC"
fmt.Println(utf8.ValidString(data2)) //prints: false
}

String 长度

级别:新手入门级

Python 开发者的代码:

1
2
data = u'♥'
print(len(data)) #prints: 1

转换成类似的 Go 代码如下:

1
2
3
4
5
6
7
8
package main
import "fmt"
func main() {
data := "♥"
fmt.Println(len(data)) //prints: 3
}

Go 的 len() 方法和 Python 的并不相同,和 Python 的 len 方法等价的 Go 方法是 RuneCountInString。

1
2
3
4
5
6
7
8
9
10
11
package main
import (
"fmt"
"unicode/utf8"
)
func main() {
data := "♥"
fmt.Println(utf8.RuneCountInString(data)) //prints: 1
}

当然有些情况(例如法语)的情况,RuneCountInString 也并不能完全返回字符数目,因为有些字符是使用多个字符的方式进行存储的。

1
2
3
4
5
6
7
8
9
10
11
12
package main
import (
"fmt"
"unicode/utf8"
)
func main() {
data := "é"
fmt.Println(len(data)) //prints: 3
fmt.Println(utf8.RuneCountInString(data)) //prints: 2
}

多行的 Slice Arry 和 Map 赋值中缺少逗号

Missing Comma In Multi-Line Slice, Array, and Map Literals

级别:新手入门级
错误信息:

1
2
3
4
5
6
7
8
9
package main
func main() {
x := []int{
1,
2 //error
}
_ = x
}

Compile Errors:

1
/tmp/sandbox367520156/main.go:6: syntax error: need trailing comma before newline in composite literal /tmp/sandbox367520156/main.go:8: non-declaration statement outside function body /tmp/sandbox367520156/main.go:9: syntax error: unexpected }

修正代码:

1
2
3
4
5
6
7
8
9
10
11
12
package main
func main() {
x := []int{
1,
2,
}
x = x
y := []int{3,4,} //no error
y = y
}

当然,如果把这些东西写在一行中,可以不加最后的逗号。

log.Fatal 与 log.Panic 在后台悄悄做了一些事情

级别:新手入门级

日志库提供了不同级别的日志记录,如果使用 Fatal 和 Panic 级别的日志,那么记录完这条日志后,应用程序便会退出而不会继续执行。

1
2
3
4
5
6
7
8
package main
import "log"
func main() {
log.Fatalln("Fatal Level: log entry") //app exits here
log.Println("Normal Level: log entry")
}

内置的数据结构操作是无锁的

级别:新手入门级

虽然 Go 语言天生高并发,但是 Go 并没有考虑到数据安全。为了实现“原子化”的数据操作,开发者需要自己对数据操作进行加锁。当然, goroutine 和 channel 以及 sync 都是手动加锁的好方案。