前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >问道Golang,6月龄必知必会(二)

问道Golang,6月龄必知必会(二)

作者头像
有态度的马甲
发布2023-04-27 15:52:30
2000
发布2023-04-27 15:52:30
举报
文章被收录于专栏:精益码农精益码农
在我看来,golnag有许多反直观的设计,而且这些设计通常不能自圆其说,导致gohper一而再再而三的调入陷阱。

网上也有很多gohper总结了一些笔记,我再提炼精简一下,挂在脑图树上便于记忆。

值类型包括:所有integer、所有float、bool、string、数组和structure

引用类型包括:指针、slice、map、chan、interface


1. map需要先先初始化,才能使用

issue:定义、不初始化使用,运行时panic; solution:make关键字初始化或者使用map字面量

代码语言:javascript
复制
m := make(map[string]float64, 5)
m["pi"] = 3.14

mm := map[string]float64{"pi": 3.14, "pi1": 4.14}  // map字面量

slice不需要初始化就可以被append. var ss []int ss = append(ss, 1, 2, 3) fmt.Println(ss) // [1,2,3]

2. map中取得不存在的键,会返回零值,

issue:

代码语言:javascript
复制
func main() {  
   x := map[string]string{"one":"a","three":"c"}
   if v := x["two"]; v == "" { // 不存在该键值对,也能返回零值“”
       fmt.Println("true")
   }
}

solution: 使用map取值的参数2 bool值来判断

代码语言:javascript
复制
func main() {  
    x := map[string]string{"one":"a","two":"","three":"c"}
    if _,ok := x["two"]; !ok {
        fmt.Println("no entry")
    }
}
3. array作为函数实参, array内容不受函数内影响

issue:golang array 是值类型,形参是实参的内存拷贝

代码语言:javascript
复制
package main

import "fmt"

func changeFunc(arr [3]int) {
    arr[0] = 222
}
func main() {
    var arr [3]int = [3]int{1, 2, 3}
    changeFunc(arr)
    for i, item := range arr {
        fmt.Printf("index : %d, item: %d \n\r", i, item)
    }
}

solution:实参从array改成引用类型的 slice。

slice切片的实质是SliceHeader 结构体,值传递slice时,正好将底层数组指针拷贝。 type SliceHeader struct { Data uintptr // 底层数组的指针 Len int Cap int }

4. 变量遮蔽

issue: 块内声明的变量会遮蔽上层的同名变量n

代码语言:javascript
复制
func main() {
    n := 0
    if true {
        n := 1
        n++
    }

    fmt.Println(n)  // 0
}

solution: 不要使用同名遮蔽变量,使用 n=1

5. 不可修改的字符串

issue:同大多数语言一样,golang的string是不可变的

代码语言:javascript
复制
func main() {
    s := "hello"
    s[0] = 'H'
}

# command-line-arguments
.\main.go:8:2: cannot assign to s[0] (value of type byte)

solution: 尝试通过byte/rune 中转

代码语言:javascript
复制
    s := "hello"

    buf := []rune(s)
    buf[0] = 'H'
    ss := string(buf)
    fmt.Println(ss)   // Hello
6. strings.TrimRight 并非剔除右侧后缀

issue: strings.TrimRight实际是将cutset字符串拆成字符,然后原字符串从右向左,直到遇到没有在cutset中国出现的字符。

代码语言:javascript
复制
fmt.Println(strings.TrimRight("ABBA", "BA"))  //  ""
fmt.Println(strings.TrimRight("ABBAABABCABAB", "BA")) //  "ABBAABABC"

solution:常规的移除后缀使用 strings.TrimSuffix

7. copy函数:最小化拷贝

issue: 内置函数Copyreturns the number of elements copied, which will be the minimum of len(src) and len(dst).

代码语言:javascript
复制
    src := []int{1, 2, 3}
    var dst []int      // 此时len(dst) =0
    copy(dst, src)
    fmt.Println(dst)   //  []

solution: 初始化足够空间的dst

代码语言:javascript
复制
src := []int{1, 2, 3}
    var dst []int = make([]int, 3)
    copy(dst, src)
    fmt.Println(dst) //  [1,2,3]

或者使用append方法

代码语言:javascript
复制
    src := []int{1, 2, 3}

    var dst1 []int
    dst1 = append(dst1, src...)
    fmt.Println(dst1) // [1,2,3]

或者使用append方法

代码语言:javascript
复制
    src := []int{1, 2, 3}

    var dst1 []int
    dst1 = append(dst1, src...)
    fmt.Println(dst1) // [1,2,3]

append方法既可以加元素,也可以加切片,真是活见久。 // slice = append(slice, elem1, elem2) // slice = append(slice, anotherSlice...)

8. range slice不是随机,range map是随机的

issue:

代码语言:javascript
复制
func main() {
    ss := []int{11, 4, 5, 2, 7}

    for i, item := range ss {
        fmt.Printf(" %d : %d \r\n", i, item)
    }

    mm := map[string]int{"11": 11, "4": 4, "5": 5, "2": 2, "7": 7}
    for i, m := range mm {
        fmt.Printf(" %s : %d \r\n", i, m)
    }
}
输出:
 0 : 11 
 1 : 4 
 2 : 5
 3 : 2
 4 : 7     // slice
 4 : 4     // map
 5 : 5
 2 : 2
 7 : 7
 11 : 11
9. 让人困惑的for-range 循环

golang中除了经典的三段式for循环外,还有帮助快速遍历 slice array map channel的 for range循环。

issue1:for range中操作迭代变量,原切片竟然没影响。

代码语言:javascript
复制
func main() {
    ss := []int{1, 1, 1}

    for _, x := range ss {
        x = x + 1
    }
    fmt.Println(ss)  // [1,1,1]
}

solution:操作索引值

代码语言:javascript
复制
func main() {
    ss := []int{1, 1, 1}

    for i,_ := range ss {
        ss[i] += 1
    }
    fmt.Println(ss)  // [2,2,2]
}

issue2:这也是一个有意思的case, 迭代体内对于[修改array元素值]无意识, 对于[修改slice元素值]有意识, 活见久。

代码语言:javascript
复制
func main() {
    aa := [2]int{0, 0}
    for _, x := range aa {
        fmt.Println(x) //  print  0,0
        aa[1] = 8
    }
    fmt.Println(aa) // print  [0,8]
}

solution:将array换成slice

代码语言:javascript
复制
    ss := []int{0, 0}
    for _, x := range ss {
        fmt.Println(x) //  print  0,8
        ss[1] = 8
    }
    fmt.Println(ss) // print  [0,8]

以上问题的关键是:

所有的 range 循环,Go 语言都会在编译期将原切片或者数组赋值给一个新变量 ha,在赋值的过程中就发生了拷贝,而我们又通过 len 关键字预先获取了切片的长度,所以在循环中追加新的元素也不会改变循环执行的次数,这也就解释了上面提到的现象。

而遇到这种同时遍历索引和元素的 range 循环时,Go 语言会额外创建一个新的 v2 变量存储切片中的元素,循环中使用的这个变量 v2 会在每一次迭代被重新赋值而覆盖,赋值时也会触发拷贝

代码语言:javascript
复制
ha := a
hv1 := 0
hn := len(ha)
v1 := hv1
v2 := nil
for ; hv1 < hn; hv1++ {
    tmp := ha[hv1]
    v1, v2 = hv1, tmp
    ...
}

C#中没有这么多诡异的情况。

C#数组是定长数组,一旦被创建,数组大小就无法改变;

span 带有底层数组指针和长度,但是长度也是只读,是类型安全、内存安全的滑块。

10. nil值比较[1]

issue: golang中:一个接口等于另一个接口,前提是它们的类型和动态值相同。这同样适用于nil值。

代码语言:javascript
复制
func Foo() error {
    var err *os.PathError = nil
    fmt.Println(err == nil) // print  true
    return err
}
func main() {
    err := Foo()
    fmt.Println(err)         // print: <nil>
    fmt.Println(err == nil)  // print: false
}

solution:强转为同一类型

代码语言:javascript
复制
 fmt.Println(err == (*os.PathError)(nil))  // print: true

或者显式返回nil error

代码语言:javascript
复制
func returnsError() error {
  if bad() {
      return ErrBad
  }
  return nil
}

在底层,接口被实现为两个元素,一个类型T和一个值V,V是一个具体的值,比如int、结构体或指针,而不是接口本身,它的类型是T, 上面的错误示例中:err 具备了T=*MyError, V=nil 的实现,故与nil不等。

只要记住,如果接口中存储了任何具体的值,该接口将不会为nil.


最后再提供几张图,供大家参考,也许上面的坑位能柳暗花明。

(1)

[4]int在内存中的表示形式只是按顺序排列的四个整数值。

(2)

s = make([]byte,5) 切片[2]是数组片段的描述符,它由指向数组的指针、片段的长度和它的容量(片段的最大长度)组成。

当我们对s进一步切片: s =s[2,4]

C# span是指向一段连续内存的类型安全的、内存安全的视图,也有数组指针和长度length,不过他的length是只读定长的,也不会有扩容的动作。

(3)

m := make(map[string]string)

m是指向Map Header数据结构的指针,Map Header包含了关于map[3]的所有元信息:

  • • map中当前的条目数
  • • map中的桶数总是等于2的幂,因此存储log(桶)以保持较小的值
  • • 指向连续内存位置的桶数组的指针
  • • 创建不同的map的哈希种子是随机

https://phati-sawant.medium.com/internals-of-map-in-golang-33db6e25b3f8

引用链接

[1] nil值比较: https://golang.org/doc/faq#nil_error [2] 切片: https://go.dev/blog/slices-intro [3] map: https://go.dev/blog/maps

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2023-03-12,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 精益码农 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. map需要先先初始化,才能使用
  • 2. map中取得不存在的键,会返回零值,
  • 3. array作为函数实参, array内容不受函数内影响
  • 4. 变量遮蔽
  • 5. 不可修改的字符串
  • 6. strings.TrimRight 并非剔除右侧后缀
  • 7. copy函数:最小化拷贝
  • 8. range slice不是随机,range map是随机的
  • 9. 让人困惑的for-range 循环
  • 10. nil值比较[1]
  • 引用链接
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档