Golang可能会踩的58个坑之初级篇( 二 )


// map 错误示例func main() {var m map[string]intm["one"] = 1// error: panic: assignment to entry in nil map// m := make(map[string]int)// map 的正确声明,分配了实际的内存}// slice 正确示例func main() {var s []ints = append(s, 1)}10.map 容量在创建 map 类型的变量时可以指定容量,但不能像 slice 一样使用 cap() 来检测分配空间的大?。?
// 错误示例func main() {m := make(map[string]int, 99)println(cap(m))// error: invalid argument m1 (type map[string]int) for cap}11.string 类型的变量值不能为 nil对那些喜欢用 nil 初始化字符串的人来说,这就是坑:
// 错误示例func main() {var s string = nil// cannot use nil as type string in assignmentif s == nil {// invalid operation: s == nil (mismatched types string and nil)s = "default"}}// 正确示例func main() {var s string// 字符串类型的零值是空串 ""if s == "" {s = "default"}}12.Array 类型的值作为函数参数在 C/C++ 中 , 数组(名)是指针 。将数组作为参数传进函数时,相当于传递了数组内存地址的引用 , 在函数内部会改变该数组的值 。
在 Go 中,数组是值 。作为参数传进函数时,传递的是数组的原始值拷贝 , 此时在函数内部是无法更新该数组的:
// 数组使用值拷贝传参func main() {x := [3]int{1,2,3}func(arr [3]int) {arr[0] = 7fmt.Println(arr)// [7 2 3]}(x)fmt.Println(x)// [1 2 3]// 并不是你以为的 [7 2 3]}如果想修改参数数组:

  • 直接传递指向这个数组的指针类型:
// 传址会修改原数据func main() {x := [3]int{1,2,3}func(arr *[3]int) {(*arr)[0] = 7fmt.Println(arr)// &[7 2 3]}(&x)fmt.Println(x)// [7 2 3]}
  • 直接使用 slice:即使函数内部得到的是 slice 的值拷贝 , 但依旧会更新 slice 的原始数据(底层 array)
// 会修改 slice 的底层 array,从而修改 slicefunc main() {x := []int{1, 2, 3}func(arr []int) {arr[0] = 7fmt.Println(x)// [7 2 3]}(x)fmt.Println(x)// [7 2 3]}13.range 遍历 slice 和 array 时混淆了返回值与其他编程语言中的 for-in 、foreach 遍历语句不同,Go 中的 range 在遍历时会生成 2 个值,第一个是元素索引 , 第二个是元素的值:
// 错误示例func main() {x := []string{"a", "b", "c"}for v := range x {fmt.Println(v)// 1 2 3}}// 正确示例func main() {x := []string{"a", "b", "c"}for _, v := range x {// 使用 _ 丢弃索引fmt.Println(v)}}14.slice 和 array 其实是一维数据看起来 Go 支持多维的 array 和 slice,可以创建数组的数组、切片的切片,但其实并不是 。
对依赖动态计算多维数组值的应用来说 , 就性能和复杂度而言,用 Go 实现的效果并不理想 。
可以使用原始的一维数组、“独立“ 的切片、“共享底层数组”的切片来创建动态的多维数组 。
1.使用原始的一维数组:要做好索引检查、溢出检测、以及当数组满时再添加值时要重新做内存分配 。
2.使用“独立”的切片分两步:
  • 创建外部 slice
  • 对每个内部 slice 进行内存分配
  • 注意内部的 slice 相互独立,使得任一内部 slice 增缩都不会影响到其他的 slice
// 使用各自独立的 6 个 slice 来创建 [2][3] 的动态多维数组func main() {x := 2y := 4table := make([][]int, x)for i:= range table {table[i] = make([]int, y)}}1.使用“共享底层数组”的切片
  • 创建一个存放原始数据的容器 slice
  • 创建其他的 slice
  • 切割原始 slice 来初始化其他的 slice
func main() {h, w := 2, 4raw := make([]int, h*w)for i := range raw {raw[i] = i}// 初始化原始 slicefmt.Println(raw, &raw[4])// [0 1 2 3 4 5 6 7] 0xc420012120table := make([][]int, h)for i := range table {// 等间距切割原始 slice , 创建动态多维数组 table// 0: raw[0*4: 0*4 + 4]// 1: raw[1*4: 1*4 + 4]table[i] = raw[i*w : i*w + w]}fmt.Println(table, &table[1][0])// [[0 1 2 3] [4 5 6 7]] 0xc420012120}15.访问 map 中不存在的 key和其他编程语言类似 , 如果访问了 map 中不存在的 key 则希望能返回 nil,比如在 PHP 中:
> php -r '$v = ["x"=>1, "y"=>2]; @var_dump($v["z"]);'NULLGo 则会返回元素对应数据类型的零值 , 比如 nil、'' 、false 和 0,取值操作总有值返回,故不能通过取出来的值来判断 key 是不是在 map 中 。
检查 key 是否存在可以用 map 直接访问,检查返回的第二个参数即可:
// 错误的 key 检测方式func main() {x := map[string]string{"one": "2", "two": "", "three": "3"}if v := x["two"]; v == "" {fmt.Println("key two is no entry")// 键 two 存不存在都会返回的空字符串}}// 正确示例func main() {x := map[string]string{"one": "2", "two": "", "three": "3"}if _, ok := x["two"]; !ok {fmt.Println("key two is no entry")}}

推荐阅读