共计 4395 个字符,预计需要花费 11 分钟才能阅读完成。
微信搜寻【吴亲强的 深夜食堂】公众号,分享一下奇奇怪怪的设计,关注我,获取更多学习材料
上篇文章我提出 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 main
import "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 main
import "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 main
import "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 的根底上截取出新的 slice
。slice
将会援用原切片的底层数组。如果是一个大的切片,会导致内存的节约。
咱们能够通过额定定义一个容量大小适合的变量,而后通过 copy
操作。
package main
import "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 main
import "fmt"
func main() {var test1, test2 []int
test1 = []int{1, 2, 3}
copy(test2, test1)
fmt.Println("test2 的值:", test2)
}
// test2 的值: []
为什么会这样?
copy
复制的元素数目是两个切片中最小的长度。以后 test1
和 test2
的最小长度为 test2
的 0,因而最终返回空切片,咱们能够为 test2
调配长度。
package main
import "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 main
import "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 main
import "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 main
import "fmt"
type User struct {
Name string
Age int
}
func main() {user1 := User{}
user2 := User{}
fmt.Println(user1 == user2)
// true
}
这样是没问题的,如果我往构造体加一个这样的字段呢?
package main
import "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 main
import ("fmt")
func main() {
var hi string
go func() {hi = "golang"}()
fmt.Println(hi)
}
以上输入什么?
大概率啥都没有,因为在子协程给 hi 变量赋值前,主协程大略率先打印,而后运行完结,接着整个程序完结了。
用最愚昧的办法让主协程停一下。
package main
import (
"fmt"
"time"
)
func main() {
var hi string
go func() {hi = "golang"}()
time.Sleep(10 * time.Millisecond)
fmt.Println(hi)
//golang
}
再来看这题,
package main
import (
"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 main
import (
"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 main
import "fmt"
func main() {recover()
panic("make error")
}
// 谬误姿态
package main
import "fmt"
func main() {doRecover()
panic("make error")
}
func doRecover() {defer func() {if err := recover(); err != nil {fmt.Println("出错了")
}
}()}
// 谬误姿态
package main
import "fmt"
func main() {defer func() {defer func() {if err := recover(); err != nil {fmt.Println(err)
}
}()}()
panic("make error")
}
// 谬误姿态
它只有在提早函数中间接调用能力失效。
package main
import "fmt"
func main() {defer func() {if err := recover(); err != nil {fmt.Println(err)
}
}()
panic("make error")
}
// 正确姿态
还有好多谬误姿态没有列举,你有不一样的错误操作嘛?欢送下方留言一起探讨。
如果文章对你有所帮忙,点赞、转发、留言都是一种反对!