lifelong learning & practice makes perfect
func main() {
var a = []int{1, 2, 3, 4, 5}
var r = make([]int, 0)
fmt.Println("a = ", a)
for i, v := range a {
if i == 0 {
a = append(a, 6, 7)
}
r = append(r, v)
}
fmt.Println("r = ", r)
fmt.Println("a = ", a)
}
// a = [1 2 3 4 5]
// r = [1 2 3 4 5]
// a = [1 2 3 4 5 6 7]
range表达式中的a实际上是原切片a的副本(暂称为a’),在该表达式初始化后,副本切片a’内部表示中的len字段就是5,并且在整个for range循环过程中并未改变,因此for range只会循环5次
在for i, v := range xxx这条语句中,i、v都被称为迭代变量
func main() {
var a = []int{1, 2, 3, 4, 5}
var wg sync.WaitGroup
for _, v := range a {
wg.Add(1)
go func() {
time.Sleep(time.Second)
fmt.Println(v)
wg.Done()
}()
}
wg.Wait()
}
// 5
// 5
// 5
// 5
// 5
实际上这个循环变量v仅仅被声明了一次并在后续整个迭代过程中被重复使用,等价于
for _, v := range a {
}
// 等价于
v := 0
for _, v = range a {
}
修改
for _, v := range a {
wg.Add(1)
go func(v int) {
time.Sleep(time.Second)
fmt.Println(v)
wg.Done()
}(v)
}
func main() {
for _, s := range "Hi,中国" {
fmt.Printf("0x%X\n", s)
}
}
// 0x48
// 0x69
// 0x2C
// 0x4E2D
// 0x56FD
string的for range每次返回一个rune,即utf-8的一个’码点’,不是一个字节. 按下标访问才是字节.
通过内建函数copy为新切片建立独立的存储空间以避免与原切片共享底层存储,从而避免空间的浪费
Go中的切片支持自动扩容。 当扩容发生时,新切片与原切片底层存储便会出现“分家”现象。一旦发生“分家”,后续对新切片的任何操作都不会影响到原切片
resp, err := http.Get("https://tip.golang.org")
if err != nil {
fmt.Println(err)
return
}
defer resp.Body.Close()
目前http包的实现逻辑是只有当应答的Body中的内容被全部读取完毕且调用了Body.Close(),默认的HTTP客户端才会重用带有keep-alive标志的HTTP连接,否则每次HTTP客户端发起请求都会单独向服务端建立一条新的TCP连接,这样做的消耗要比重用连接大得多
如果一个HTTP客户端与一个HTTP服务端之间要持续通信,那么向服务端建立一条带有keep-alive标志的HTTP长连接并重用该连接收发数据是十分必要的,也是最有效率的。但是如果我们的业务逻辑是向不同服务端快速建立连接并在完成一次数据收发后就放弃该连接,那么我们需要及时关闭HTTP连接以及时释放该HTTP连接占用的资源。但Go标准库HTTP客户端的默认实现并不会及时关闭已经用完的HTTP连接(仅当服务端主动关闭或要求关闭时才会关闭),这样一旦连接建立过多又得不到及时释放,就很可能会出现端口资源或文件描述符资源耗尽的异常。及时释放HTTP连接的方法有两种
s := "大家好"
fmt.Printf("字符串\"%s\"的长度为%d,字符个数:%d\n",
s, len(s), utf8.RuneCountInString(s)) // 长度为9
可以通过channel
func main() {
c := make(chan error, 1)
go func() {
// 做点什么
time.Sleep(time.Second * 2)
c <- nil // 或c <- errors.New("some error")
}()
err := <-c
fmt.Printf("sub goroutine exit with error: %v\n", err)
}
需要的情况下在goroutine里增加defer捕获panic
channel的状态
func main() {
var nilChan chan int
nilChan <- 5 // 阻塞
n := <-nilChan // 阻塞
fmt.Println(n)
var closedChan = make(chan int)
close(closedChan)
m := <-closedChan
fmt.Println(m) // int类型的零值:0
closedChan <- 5 // panic: send on closed channel
}
Go语言的方法(method)很独特,除了参数和返回值,它还拥有一个代表着类型实例的receiver。receiver有两类:值类型receiver和指针类型receiver。而采用值类型receiver的方法无法改变类型实例的状态。
一般break语句都是用来跳出某个for循环的,但在Go中,如果for循环与switch或select联合使用,我们就很可能掉入break的“坑”中
func breakWithForSwitch(b bool) {
for {
time.Sleep(1 * time.Second)
fmt.Println("enter for-switch loop!")
switch b {
case true:
break
case false:
fmt.Println("go on for-switch loop!")
}
}
fmt.Println("exit breakWithForSwitch")
}
func breakWithForSelect(c <-chan int) {
for {
time.Sleep(1 * time.Second)
fmt.Println("enter for-select loop!")
select {
case <-c:
break
default:
fmt.Println("go on for-select loop!")
}
}
fmt.Println("exit breakWithForSelect")
}
func main() {
go func() {
breakWithForSwitch(true)
}()
c := make(chan int, 1)
c <- 11
breakWithForSelect(c)
}
// enter for-select loop!
// enter for-switch loop!
// enter for-switch loop!
// enter for-select loop!
// go on for-select loop!
// enter for-switch loop!
// ...
无论是switch内的break还是select内的break,都没有跳出各自最外层的for循环, 而仅仅跳出了switch或select代码块, 但这就是Go语言break语句的原生语义:不接标签(label)的break语句会跳出最内层的switch、select或for代码块
如果要跳出最外层的循环,我们需要为该循环定义一个标签,并让break跳到这个标签处
func breakWithForSwitch(b bool) {
outerloop:
for {
time.Sleep(1 * time.Second)
fmt.Println("enter for-switch loop!")
switch b {
case true:
break outerloop
case false:
fmt.Println("go on for-switch loop!")
}
}
fmt.Println("exit breakWithForSwitch")
}