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

4571fbeed14451179922ae504a3afaab.gif

在我看来,golnag有许多反直观的设计,而且这些设计通常不能自圆其说,导致gohper一而再再而三的调入陷阱。

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

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

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


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

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

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:

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

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

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 是值类型,形参是实参的内存拷贝

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

func main() {
    n := 0
    if true {
        n := 1
        n++
    }

    fmt.Println(n)  // 0
}

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

5. 不可修改的字符串

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

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 中转

s := "hello"

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

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

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).

src := []int{1, 2, 3}
    var dst []int      // 此时len(dst) =0
    copy(dst, src)
    fmt.Println(dst)   //  []

solution: 初始化足够空间的dst

src := []int{1, 2, 3}
    var dst []int = make([]int, 3)
    copy(dst, src)
    fmt.Println(dst) //  [1,2,3]

或者使用append方法

src := []int{1, 2, 3}

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

或者使用append方法

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:

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中操作迭代变量,原切片竟然没影响。

func main() {
    ss := []int{1, 1, 1}

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

solution:操作索引值

func main() {
    ss := []int{1, 1, 1}

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

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

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

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 会在每一次迭代被重新赋值而覆盖,赋值时也会触发拷贝

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值。

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:强转为同一类型

fmt.Println(err == (*os.PathError)(nil))  // print: true

或者显式返回nil error

func returnsError() error {
  if bad() {
      return ErrBad
  }
  return nil
}

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

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


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

(1)

8b719c141ced1931eb65c855689c599d.png

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

  (2)

330c23618af3ae53f3dfeb5b1950fadf.png

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

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

ed6193e0737620b0e6ae3495ea65eed3.png

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

(3)

958f93e39c7a9262c11037e5a1274c05.png

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

点“950b475c08b3f92d9e2c4db92b6e8cc2.gif戳“在看9f694cbf612e8119063c2bfe01e1a6f0.gif

体现态度很有必要!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: 成为一名 Golang 架构师需要掌握的技能包括:熟练掌握 Golang 语言的基础知识;熟悉 Golang 应用程序的构建;具备良好的编码能力;了解 Golang 常用的 Web 框架;熟悉分布式系统概念及架构;具有高可用架构设计能力;了解常用的数据库及缓存;具备良好的调试和性能优化能力。 ### 回答2: 作为Golang架构师,必备的技能主要包括以下几个方面。 首先,深厚的Golang编程基础是必不可少的。作为一门编程语言Golang具有简洁、高效的特点,因此熟练掌握Golang的基本语法和编程范式,能够灵活运用Golang的各种特性和库,是架构师必须具备的基本技能。 其次,设计和构建分布式系统的能力是重要的技能之一。在当今云计算和大数据时代,分布式系统成为了架构师必须面对的挑战。掌握分布式系统的设计原则,了解分布式系统的常见模式和技术栈,能够使用Golang构建高可用、高并发、可扩展的分布式系统,是架构师必备的技能之一。 此外,良好的系统设计能力和架构思维也是不可或缺的。架构师需要能够对系统进行全局的规划和设计,考虑系统的可靠性、可扩展性、性能等方面。通过分析和抽象复杂的问题,采用合适的模式和架构风格,将系统划分为不同的模块和组件,并定义它们之间的交互和通信方式,以实现系统的复杂需求。 另外,熟悉常用的开发工具和框架也是必备的技能之一。如Docker、Kubernetes等容器和容器编排工具,如gin、beego等Golang框架,如ELK、Prometheus等监控和日志分析工具等。熟悉这些工具和框架,能够帮助架构师更高效地构建和部署系统。 最后,良好的沟通和团队合作能力也是Golang架构师必不可少的技能。架构师通常需要与产品经理、开发团队、测试团队等进行有效的沟通和协作,了解需求和业务,提供技术方案和指导。同时,能够与团队成员密切合作,共同完成项目的设计和开发。 总之,作为Golang架构师,需要具备扎实的Golang编程基础、分布式系统设计能力、系统设计和架构思维、熟悉开发工具和框架,以及良好的沟通和团队合作能力。 ### 回答3: golang架构师必备技能包括以下几个方面: 1. 熟练掌握Golang语言:作为Golang架构师,首先需要对Golang语言有深入的理解和掌握。这包括了对Golang语法、特性、标准库的熟悉,以及对并发编程、内存管理等方面的了解。只有对Golang语言的熟悉,才能使用它来构建可靠、高效的系统。 2. 深刻了解分布式系统设计:架构师需要有扎实的分布式系统设计知识和经验,包括分布式计算、分布式存储、分布式消息传递等方面。对于Golang架构师来说,熟悉Golang在分布式系统中的特性和优势,能够合理地应用Goland来设计和开发分布式系统,提高系统性能和可扩展性。 3. 掌握常用的系统设计模式:架构师需要了解和掌握常用的系统设计模式,包括但不限于单例模式、工厂模式、观察者模式、代理模式等。这些模式能够帮助架构师更好地设计系统结构,提高系统的可维护性和可扩展性。 4. 熟悉微服务架构:微服务架构是一种将系统拆分成若干小的、自治的服务的架构风格。Golang架构师需要熟悉微服务架构的原理和设计思想,并且能够使用Golang来实现和部署微服务。熟练掌握微服务架构可以提高系统的灵活性、可扩展性和可维护性。 5. 具备高可用和高性能系统设计能力:架构师需要具备设计高可用和高性能系统的能力。这包括对系统性能的监控和优化、负载均衡、容错处理等方面的知识。Golang作为一门高性能的语言,架构师需要了解Golang的性能优化技巧,使用Golang构建高可用和高性能的系统。 总之,作为Golang架构师,不仅需要熟练掌握Golang语言,还需要具备分布式系统设计、系统架构设计、微服务架构等方面的知识和经验,才能设计和构建出高效、可靠的系统。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

有态度的马甲

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值