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


对依赖动态计算多维数组值的应用来说 , 就性能和复杂度而言 , 用 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")}}16.string 类型的值是常量 , 不可更改尝试使用索引遍历字符串 , 来更新字符串中的个别字符 , 是不允许的 。
string 类型的值是只读的二进制 byte slice , 如果真要修改字符串中的字符 , 将 string 转为 []byte 修改后 , 再转为 string 即可:
// 修改字符串的错误示例func main() {x := "text"x[0] = "T"// error: cannot assign to x[0]fmt.Println(x)}// 修改示例func main() {x := "text"xBytes := []byte(x)xBytes[0] = 'T'// 注意此时的 T 是 rune 类型x = string(xBytes)fmt.Println(x)// Text}注意: 上边的示例并不是更新字符串的正确姿势 , 因为一个 UTF8 编码的字符可能会占多个字节 , 比如汉字就需要 3~4个字节来存储 , 此时更新其中的一个字节是错误的 。
更新字串的正确姿势:将 string 转为 rune slice(此时 1 个 rune 可能占多个 byte) , 直接更新 rune 中的字符
func main() {x := "text"xRunes := []rune(x)xRunes[0] = '我'x = string(xRunes)fmt.Println(x)// 我ext}17.string 与 byte slice 之间的转换当进行 string 和 byte slice 相互转换时 , 参与转换的是拷贝的原始值 。这种转换的过程 , 与其他编程语的强制类型转换操作不同 , 也和新 slice 与旧 slice 共享底层数组不同 。
Go 在 string 与 byte slice 相互转换上优化了两点 , 避免了额外的内存分配:

经验总结扩展阅读