golang 50处陷阱
2021-01-01 本文已影响0人
voidFan
golang 50处陷阱
1.main()函数后面的大括号不能放在单独的一行
2.如果存在未使用的变量,将导致编译失败, 可以使用"_ = value"进行注释
3.如果存在未使用的import,会导致编译失败
4.简式的变量声明仅可以在函数内部使用
5.一个代码块内不用简式重复声明变量, 使用":="声明变量时,左边至少有一个变量未声明过
6.偶然的变量隐藏Accidental Variable Shadowing
- 简式的声明变量,只在当前的代码块内有效
- 可以借助工具帮忙检查: go tool vet -shadow xxx.go
package main
import "fmt"
func main() {
x := 1
fmt.Println(x) //prints 1
{
fmt.Println(x) //prints 1
x := 2
fmt.Println(x) //prints 2
}
fmt.Println(x) //prints 1 (bad if you need 2)
}
不使用显式类型,无法使用“nil”来初始化变量
- nil标志符用于表示interface、函数、maps、slices和channels的“零值”。
- 如果你不指定变量的类型,编译器将无法编译你的代码,因为它猜不出具体的类型。
package main
func main() {
var x = nil //error
var x interface{} = nil //ok
_ = x
}
使用“nil” Slices and Maps
- 在一个nil的slice中添加元素是没问题的,但对一个map做同样的事将会生成一个运行时的panic。
package main
func main() {
var s []int
s = append(s,1) //ok
var m map[]int
m["one"] = 1 //error
}
map的容量:可以在map创建时指定它的容量,但你无法在map上使用cap()函数
字符串不会为nil
- 这对于经常使用nil分配字符串变量的开发者而言是个需要注意的地方。
package main
func main() {
var x string = nil //error
if x == nil { //error
x = "default"
}
var x string //defaults to "" (zero value)
if x == "" {
x = "default"
}
}
数组作为函数的参数
- c/c++中数组作为函数参数是传引用。golang是传的值。
- 在go中,函数内修改数组的数据,函数外是不可见的。
package main
import "fmt"
func main() {
x := [3]int{1,2,3}
func(arr [3]int) {
arr[0] = 7
fmt.Println(arr) //prints [7 2 3]
}(x)
fmt.Println(x) //prints [1 2 3] (not ok if you need [7 2 3])
//可以通过传指针的方式实现:func(arr *[3]int) {...}(&x)
//另外如果只是修改数据,可以使用slice
}
数组和切片使用range语句遍历会得到两个值
数组和切片是一维的
-
Go好像支持多维的Array和Slice,但不是这样的。尽管可以创建数组的数组或者切片的切片
-
对于依赖于动态多维数组的数值计算应用而言,Go在性能和复杂度上还相距甚远。
-
你可以使用纯一维数组、“独立”切片的切片,“共享数据”切片的切片来构建动态的多维数组。
-
如果你使用纯一维的数组,你需要处理索引、边界检查、当数组需要变大时的内存重新分配。
-
使用“独立”slice来创建一个动态的多维数组需要两步。
- 首先,你需要创建一个外部的slice。
- 然后,你需要分配每个内部的slice。
- 内部的slice相互之间独立。你可以增加减少它们,而不会影响其他内部的slice。
package main
func main() {
x := 2
y := 4
table := make([][]int,x)
for i:= range table {
table[i] = make([]int,y)
}
}
-
使用“共享数据”slice的slice来创建一个动态的多维数组需要三步。
- 首先,你需要创建一个用于存放原始数据的数据“容器”。
- 然后,你再创建外部的slice。
- 最后,通过重新切片原始数据slice来初始化各个内部的slice。
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>
}
访问不存在的Map Keys
- 判断map中是否存在某个key时,
- 如果对应key的零值是“nil”, 那返回的什将会是nil, 没有问题。
- 如果是string,零值为"",如果key的值本身就是"",此时就不能用零值来判断是否存在对应的key
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")
}
}
////////////////////////////////////////////////////////////
func main() {
x := map[string]string{"one":"a","two":"","three":"c"}
if _,ok := x["two"]; !ok { //good
fmt.Println("no entry")
}
}
Strings无法修改
- 尝试使用索引操作来更新字符串变量中的单个字符将会失败。
- string是只读的byte slice(和一些额外的属性)。
- 如果你确实需要更新一个字符串,那么使用byte slice,并在需要时把它转换为string类型。
package main
import "fmt"
func main() {
x := "text"
x[0] = 'T' //Compile Error:
fmt.Println(x)
}
/////////////////////////////////////
func main() {
x := "text"
xbytes := []byte(x)
xbytes[0] = 'T'
fmt.Println(string(xbytes)) //prints Text
}
注意:这并不是在文字string中更新字符的正确方式,因为给定的字符可能会存储在多个byte中。如果你确实需要更新一个文字> > string,先把它转换为一个rune slice。即使使用rune slice,单个字符也可能会占据多个rune,比如当你的字符有特定的重音> 符号时就是这种情况。这种复杂又模糊的“字符”本质是Go字符串使用byte序列表示的原因。
String和Byte Slice之间的转换
-
当你把一个字符串转换为一个byte slice(或者反之)时,你就得到了一个原始数据的完整拷贝。
-
这和其他语言中cast操作不同,也和新的slice变量指向原始byte slice使用的相同数组时的重新slice操作不同。
-
Go在[]byte到string和string到[]byte的转换中确实使用了一些优化来避免额外的分配(在todo列表中有更多的优化)。
- 第一个优化避免了当[]byte keys用于在map[string]集合中查询时的额外分配:m[string(key)]。
- 第二个优化避免了字符串转换为[]byte后在for range语句中的额外分配:for i,v := range []byte(str) {...}
17. String和索引操作
- 字符串上的索引操作返回一个byte值,而不是一个字符
- 如果你需要访问特定的字符串“字符”(unicode编码的points/runes),使用for range。
- 官方的“unicode/utf8”包和实验中的utf8string包(golang.org/x/exp/utf8string)也可以用。
- utf8string包中包含了一个很方便的At()方法。把字符串转换为rune的切片也是一个选项。
package main
import "fmt"
func main() {
x := "text"
fmt.Println(x[0]) //print 116
fmt.Printf("%T",x[0]) //prints uint8
}