zoukankan      html  css  js  c++  java
  • 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" // 不会被接收处理
    }
    
    

    运行结果:

  • 相关阅读:
    AcWing 157. 树形地铁系统 (hash判断树同构)打卡
    AcWing 156. 矩阵 (哈希二维转一维查询)打卡
    AcWing 144. 最长异或值路径 01字典树打卡
    AcWing 143. 最大异或对 01字典树打卡
    AcWing 142. 前缀统计 字典树打卡
    AcWing 139. 回文子串的最大长度 hash打卡
    AcWing 138. 兔子与兔子 hash打卡
    常用C库函数功能及用法
    编程实现C库函数
    C语言面试题5
  • 原文地址:https://www.cnblogs.com/qiqiloved/p/15778334.html
Copyright © 2011-2022 走看看