• 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" // 不会被接收处理
    }
    
    

    运行结果:

  • 相关阅读:
    HNUSTOJ-1675 Morse Code(DFS+字典序搜索)
    HNUSTOJ-1638 遍地桔子(贪心)
    HNUSTOJ-1521 塔防游戏
    HNUSTOJ-1565 Vampire Numbers(暴力打表)
    HDUSTOJ-1559 Vive la Difference!(简单题)
    HDUSTOJ-1558 Flooring Tiles(反素数)
    HNUSTOJ-1600 BCD时钟
    胡雪岩04
    新概念4-24
    曾国藩家训02
  • 原文地址:https://www.cnblogs.com/qiqiloved/p/15778334.html
Copyright © 2020-2023  润新知