Go常见的坑

1、Go的String其实是类切片的数据结构

package main

import (
	"fmt"
	String "modLearn/String/demo"
)

func main() {
	s := "hello world"

	for i := 0; i < len(s); i++ {
		u := s[i]
                // 68 65 6c 6c 6f 20 77 6f 72 6c 64
		fmt.Printf("%x ", u)
	}
}

直接使用 for循环来打印字符串,并没有达到想要的效果,将每个字符打印出来,而是以十六进制打印出了它的byte值。这是因为Go的字符串其实就是uint8数组,如下图:

这也是为什么使用打印出来时是数字,如果想打印出对应的字符,有两种做法

  • 1、在打印时使用强制类型转换 fmt.Printf("%s", string(u))
  • 2、使用 strings.Split(s,"")切割字符串,切割后会变成字符串数组,再用for循环打印就可以打印出想要的字符了

2、string类型的值是常量,不可更改

尝试使用索引遍历字符串,来更新字符串中的个别字符串,是不允许的。string类型的值是只读的二进制byte array,如果真想修改字符串中的字符,将string转为[]byte修改后,再转为string即可

package main

import "fmt"

func main() {
	// 错误示范
	x := "text"
	//x[0] = "T"        // error: cannot assign to x[0]
	//fmt.Println(x)

	xBytes := []byte(x)
	xBytes[0] = 'T'    // 注意此时的 T 是 rune 类型
	x = string(xBytes)
	fmt.Println(x)    // Text

	// 推荐做法
	xRunes := []rune(x)
	xRunes[0] = '我'
	x = string(xRunes)
	fmt.Println(x)    // 我ext
}

使用[]byte()并不是更新字符串的正确姿势,因为一个UTF8编码的字符可能会占多个字节,比如汉字就需要3-4个字节来存储,此时更新其中的一个字节是错误的。

更新字符串的正确姿势:将string转为rune slice(此时1个rune可能占多个byte),直接更新rune中字符。

3、字符串的长度

func main() {
char := "♥"
fmt.Println(len(char))    // 3

Go的内建函数len()返回的是字符串的byte数量,而不是像python中那样计算Unicode字符数。

如果要得到字符串的字符数,可使用"unicode/utf8"包中的 RuneCountInString(str string) (n int))

注意:RuneCountInString 并不总是返回我们看到的字符数,因为有的字符会占用 2 个 rune:

char := "♥"
fmt.Println(len(char))    // 3
fmt.Println(utf8.RuneCountInString(char))    // 1

char1 := "é"
fmt.Println(len(char1))    // 3
fmt.Println(utf8.RuneCountInString(char1))    // 2
fmt.Println("cafe\u0301")    // café    // 法文的 cafe,实际上是两个 rune 的组合

运行结果:

4、Array是值类型,不是引用类型,但是切片是引用类型

Go中Array当成参数传给函数时,是按值传递的,也就是一个完全值拷贝,在函数里修改参数数组的值,并不会影响原始值,但是切片会影响,代码如下:

package main

import (
	"fmt"
)


func main() {
	// Array是值类型,而不是引用类型,函数传参时,在函数里修改数组,并不会更新数组的值
	// Slice是引用类型,在函数里修改,会影响原来的值
	arr := [3]int64{1, 2, 3}
	ChangeArrItem(arr)
	fmt.Println(arr)           // [1,2,3]

	s := []int64{1, 2, 3}
	ChangeSliceItem(s)         // [1,2,3]
	fmt.Println(s)
}

func ChangeArrItem(arr [3]int64) {
	arr[0] = 100
}

func ChangeSliceItem(arr []int64) {
	arr[0] = 100
}

运行结果:

img

5、defer函数的参数值是声明时求值的

对defer延迟执行的函数,他的参数会在声明的时候就算出具体值,而不是在执行时才求值

package main

import "fmt"

func main()  {
	// defer函数的参数值,对 defer 延迟执行的函数,它的参数会在声明时候就会求出具体值,而不是在执行时才求值
	var i = 1
	// result: 3, 而不是6
	defer fmt.Println("result1: ", func() int { return i * 3 }())
	i++
}

但是可以通过闭包传值来实现想要的结果

defer func(i int){
	fmt.Println("result2: ", func() int { return i * 3 }())
}(i)

运行结果:

6、defer执行顺序是栈形式的,后进先出,后进的defer函数先执行

就用上面的图:可以看到后声明的defer会先执行,先打印了result2: 6,后打印result1: 3

7、程序退出时还有goroutine在执行

主程序默认不等所有goroutine都执行完才退出,这点需要特别注意

package main

import (
	"fmt"
	"time"
)

func main() {
	workCount := 2
	for i := 0; i < workCount; i++ {
		go doIt(i)
	}

	time.Sleep(1 * time.Second)
	fmt.Println("all done!")
}

func doIt(workId int) {
	fmt.Printf("[%v] is running \n", workId)
	time.Sleep(3 * time.Second)           // 模拟goroutine正在执行
	fmt.Printf("[%v] is done \n", workId)
}

如图,main()主程序不等两个goroutine执行完就直接退出了。常见的解决办法:使用WaitGroup变量,它会让主程序等待所有goroutine执行完毕再退出。

如果你的goroutine要做消息的循环处理耗时操作,可以 向它们发送一条kill消息来关闭它们。或直接关闭一个它们都等待接受数据的channel:

package main

import (
	"fmt"
	"sync"
)

func main() {
	var wg sync.WaitGroup
	done := make(chan struct{})
	ch := make(chan interface{})

	workerCount := 2
	for i := 0; i < workerCount; i++ {
		wg.Add(1)
		go doIt(i, ch, done, &wg)      // wg 传指针,doIt() 内部会改变 wg 的值
	}

	for i := 0; i < workerCount; i++ { // 向 ch 中发送数据,关闭 goroutine
		ch <- i
	}

	close(done)
	wg.Wait()
	close(ch)
	fmt.Println("all done!")
}

func doIt(workId int, ch <-chan interface{}, done <-chan struct{}, wg *sync.WaitGroup) {
	fmt.Printf("[%v] is running \n", workId)
	defer wg.Done()
	for {
		select {
		case m := <-ch:
			fmt.Printf("[%v] m => %v\n", workId, m)
		case <-done:
			fmt.Printf("[%v] is done\n", workId)
			return
		}
	}
}

运行结果:

8、向无缓冲的channel发送数据,只要receiver准备好了就会立刻返回

只有在数据被receiver处理时,sender才会被阻塞。因运行环境差异,在sender发送完数据后,recevier的goroutine可能没有足够的时间处理下一个数据

package main

import (
	"fmt"
	"time"
)

func main() {
	ch := make(chan string)

	go func() {
		for m := range ch {
			time.Sleep(time.Second)
			fmt.Println("我收到了", time.Now().Format("15:04:05"))
			fmt.Println("Processed:", m)
			time.Sleep(10 * time.Second) // 模拟需要长时间运行的操作
			fmt.Println("我处理完了", time.Now().Format("15:04:05"))
		}
	}()

	fmt.Println("开始发送第一条数据", time.Now().Format("15:04:05"))
	ch <- "cmd.1"
	fmt.Println("第一条数据处理结束", time.Now().Format("15:04:05"))

	ch <- "cmd.2" // 不会被接收处理
}

运行结果:

原文地址:https://www.cnblogs.com/qiqiloved/p/15778334.html