微信搜寻【吴亲强的深夜食堂】公众号,分享一下奇奇怪怪的设计,关注我,获取更多学习材料

上篇文章我提出 channel 在什么样的操作下会引发 panic。这篇文章就让咱们来总结一下小白在 go 中常常会问的十万个为什么。

string

假如咱们要批改类型为字符串变量的某个字符,如果是之前世界上最平凡的语言,那么能够间接这样(差点忘本不会写php了):

<?php$name = "test";$name[0] = "T";var_dump($name);// string(4) "Test"

在 go 中是不容许应用索引下标操作字符串变量中的字符的,会间接编译谬误。

// 不被容许  x := "test"  x[0] = 'T'  //cannot assign to x[0]

个别要批改我会转换成 byte。

package mainimport "fmt"func main() {  s := "test"  bytes := []byte(s)  bytes[0] = 'T'  fmt.Println("s的值:", string(bytes))  // s的值: Test}

array

咱们来看这样一段程序,

import "fmt"func main() {  a := [3]int{10, 20, 30}  changeArray(a)  fmt.Println("a的值:", a)}func changeArray(items [3]int) {  items[2] = 50}// a的值: [10 20 30]

答案并不会是 [10,20,30]。因为下面把数组传递给函数时,数组将会被复制,是一个值传递,那么之后的批改当然和之前的没有关系。

当然你能够传递数组指针,这样他们指向的就是同一个地址了。

func main() {  a := [3]int{10, 20, 30}  changeArray(&a)  fmt.Println("a的值:", a)}//数组是值传递func changeArray(items *[3]int) {  items[2] = 50}//a的值: [10 20 50]

或者能够应用 slice。

package mainimport "fmt"func main() {  s := []int{10, 20, 30}  changeSlice(s)  fmt.Println("s的值是:",s)}func changeSlice(items []int) {  items[2] = 50}// s的值是: [10 20 50]

slice 实质上不存储任何数据,它只是形容根底数组的一部分。slice 在底层的构造是,

type slice struct {  array unsafe.Pointer // 底层数组的指针地位  len   int // 切片以后长度  cap   int //容量,当容量不够时,会触发动静扩容的机制}

当传递的是 slice,并且切片的底层构造 array 的值还是指向同一个数组指针地址时,对数组元素的批改会相互影响。

slice

看看上面的代码,

package mainimport "fmt"func main() {  data := cutting()  fmt.Printf("data's len:%v,cap:%vn", len(data), cap(data))}func cutting() []int {  val := make([]int, 1000)  fmt.Printf("val's len:%v,cap:%vn ", len(val), cap(val))  return val[0:10]}// val's len:1000,cap:1000// data's len:10,cap:1000

就像下面说的,当在原有 slice 的根底上截取出新的 sliceslice 将会援用原切片的底层数组。如果是一个大的切片,会导致内存的节约。

咱们能够通过额定定义一个容量大小适合的变量,而后通过 copy 操作。

package mainimport "fmt"func main() {  data := cutting()  fmt.Printf("data's len:%v,cap:%vn", len(data), cap(data))}func cutting() []int {  val := make([]int, 1000)  fmt.Printf("val's len:%v,cap:%vn ", len(val), cap(val))  res := make([]int, 10)  copy(res, val)  return res}//val's len:1000,cap:1000// data's len:10,cap:10

copy

既然下面呈现了 copy,那么咱们来看看 copy

package mainimport "fmt"func main() {  var test1, test2 []int  test1 = []int{1, 2, 3}  copy(test2, test1)  fmt.Println("test2 的值:", test2)}// test2 的值: []

为什么会这样?

copy 复制的元素数目是两个切片中最小的长度。以后 test1test2 的最小长度为 test2 的0,因而最终返回空切片,咱们能够为 test2 调配长度。

package mainimport "fmt"func main() {  var test1, test2 []int  test1 = []int{1, 2, 3}  test2=make([]int,len(test1))  copy(test2,test1)  fmt.Println("test2 的值:",test2)}// test2 的值: [1 2 3]

range

咱们来看上面的代码,

package mainimport "fmt"func main() {  res := []int{1, 2, 3}  for _, item := range res {    item *= 10  }  fmt.Println("res:", res)}// res: [1 2 3]

最终的值并没有设想中的 [10,20,30]。为什么?

因为在 go 中 range 第二个返回值实际上是一个值拷贝。

这也通知咱们当遍历切片类型为构造体时,须要防止这样的操作,此操作会耗费大量的内存,咱们能够通过索引下标搞定。

package mainimport "fmt"func main() {  res := []int{1, 2, 3}  for index, _ := range res {    res[index] *= 10  }  fmt.Println("res:", res)  // res: [10 20 30]}

struct

咱们常常会应用 “==” 去判断两个构造体是否相等,比方,

package mainimport "fmt"type User struct {  Name string  Age  int}func main() {  user1 := User{}  user2 := User{}  fmt.Println(user1 == user2)  // true}

这样是没问题的,如果我往构造体加一个这样的字段呢?

package mainimport "fmt"type User struct {  Name string  Age  int  IsChild func(age int) bool}func main() {  user1 := User{}  user2 := User{}  fmt.Println(user1 == user2)  // invalid operation: user1 == user2 (struct containing func(int) bool cannot be compared)}

间接报编译谬误,为什么?

如果构造体中的任一字段不具备可比性,那么应用 “==” 运算符会导致编译出错。下面我加的 IsChild 字段类型为闭包函数显然不具备可比性。

groutine

package mainimport (  "fmt")func main() {  var hi string  go func() {    hi = "golang"  }()  fmt.Println(hi)}

以上输入什么?

大概率啥都没有,因为在子协程给 hi 变量赋值前,主协程大略率先打印,而后运行完结,接着整个程序完结了。

用最愚昧的办法让主协程停一下。

package mainimport (  "fmt"  "time")func main() {  var hi string  go func() {    hi = "golang"  }()  time.Sleep(10 * time.Millisecond)  fmt.Println(hi)  //golang}

再来看这题,

package mainimport (  "fmt"  "sync")func main() {  var wg sync.WaitGroup  for i := 0; i < 5; i++ {    wg.Add(1)    go func() {      fmt.Println("值是:", i)      wg.Done()    }()  }  wg.Wait()}

每次运行,答案都不雷同,然而大概率都是5。这里的操作存在数据竞争,即 data race。这种状况产生的条件是,当两个或两个以上 groutine 并发地拜访同一个变量并且有一个拜访是写入时,就会引发 data race

能够通过命令行新增参数 -race 运行检测。

修复的形式也会简略,在启动 groutine 时应用局部变量并将数字作为参数传递。

package mainimport (  "fmt"  "sync")func main() {  var wg sync.WaitGroup  for i := 0; i < 5; i++ {    wg.Add(1)    go func(item int) {      fmt.Println("值是:", item)      wg.Done()    }(i)  }  wg.Wait()}

recover()

在 go 中能够应用 recover() 函数捕捉 panic,然而咱们也须要留神它的用法,以下应用姿态都是谬误的。

package mainimport "fmt"func main() {  recover()  panic("make error")}// 谬误姿态
package mainimport "fmt"func main() {  doRecover()  panic("make error")}func doRecover() {  defer func() {    if err := recover(); err != nil {      fmt.Println("出错了")    }  }()}// 谬误姿态
package mainimport "fmt"func main() {  defer func() {    defer func() {      if err := recover(); err != nil {        fmt.Println(err)      }    }()  }()  panic("make error")}// 谬误姿态

它只有在提早函数中间接调用能力失效。

package mainimport "fmt"func main() {  defer func() {    if err := recover(); err != nil {      fmt.Println(err)    }  }()  panic("make error")}// 正确姿态

还有好多谬误姿态没有列举,你有不一样的错误操作嘛?欢送下方留言一起探讨。

如果文章对你有所帮忙,点赞、转发、留言都是一种反对!