Golang学习记录的小知识点

map中的值传递问题

dic := make(map[int][]int)
val := []int{2}
dic[1] = val
res := dic[1]
res = append(res, 2)
fmt.Println(val)
fmt.Printf("%p\n", val)
fmt.Println(res)
fmt.Printf("%p", res)

//[2]
//0xc00000a0a0
//[2 2]
//0xc00000a0b0
dic := make(map[int]int)
dic[1] = 1
res := dic[1]
res = 2
fmt.Println(dic[1])
fmt.Println(res)

//1
//2

如上图所示,map值为int切片,直接传递给一个变量时,二者引用的变量地址相同;但是res变量修改切片内容后,二者引用的地址就发生了变化,res变量的修改不会影响map的值。

所以,对map中的值修改时,需要直接通过键修改,不能赋值给其他变量后修改。除非map中的值是指针类型

dic := map[string]*entry{
    "test" : {
        cnt: 1,
        left: 1,
        right: 1,
    },
}
t := dic["test"]
t.cnt = 2
fmt.Println(t)                  // &{2 1 1}
fmt.Println(dic["test"])        // &{2 1 1}

字符串的遍历

str := "He"
fmt.Println("Utf-8遍历")
for i := 0; i < len(str); i++ {
   ch := str[i]
   fmt.Println(reflect.TypeOf(ch))
   fmt.Println(ch)
}
/*
uint8
72
uint8
101
*/
fmt.Println("Unicode遍历")
for _, ch1 := range str {
   fmt.Println(reflect.TypeOf(ch1))
   fmt.Println(ch1)
}
/*
int32
72
int32
101
*/

字符的ASCII码

计算字符的ASCII码,可以直接用int类型或rune类型的值与'a'相减

var num int = 98
fmt.Println(num - 'a')  // 1

var num rune = 'c'
fmt.Println(num - 'a')  // 2

var num rune = 100
fmt.Println(num - 'a')  // 3

byte类型计算时,需要注意byte的值要大于'a'

var num byte = 1
fmt.Println(num - 'a')  // 160

var num byte = 98
fmt.Println(num - 'a')  // 1

var num byte = 'c'
fmt.Println(num - 'a')  // 2

整数转换为字符串

fmt.Println(string(97)) // a
fmt.Println(string(1))  // 

通过string(int)转换时,当整数对应的ascii码有对应的字母存在时,会直接转换为对应的字母,如果该ascii`码对应的字母不存在,则无法转换。

通过函数strconv.Itoa()转换,如下

num := 1
res := strconv.Itoa(num)  // "1"

对字符串进行索引取值和切片操作返回的数据类型

s := "hello"
fmt.Println(reflect.TypeOf(s[0]))       // uint8
fmt.Println(reflect.TypeOf(s[0: 2]))    // string

由于string的底层实现是[]byte数组,所以直接按索引取值得到的是byte类型的值,而切片操作还是string类型。

切片传入函数

切片直接传入函数,在函数内部进行append操作时,会进行深拷贝和copy函数的效果一致,不会影响到外部的切片,如下:

func main() {
    nums := []int{5, 2}

    test(nums)
    fmt.Println(nums)   // [5 2]
    test2(nums)
    fmt.Println(nums)   // [5 2]
}

func test(nums []int) {
    nums = append(nums, 10)
}

func test2(nums []int) {
    n := make([]int, len(nums))
    copy(n, nums)
    n[0] += 10
}

要想内部的修改能够影响到外部变量,需要传地址:

func main() {
    nums := []int{5, 2}
    test(&nums)
    fmt.Println(nums)   // [5 2 10]

}

func test(nums *[]int) {
    *nums = append(*nums, 10)
}

‘ ’初始化问题

t := ' '
fmt.Printf("%T", t)     // int32

var t byte = ' '
fmt.Printf("%T", t)     // uint8

var t rune = ' '
fmt.Printf("%T", t)     // int32

直接使用:=符号进行初始化,默认为rune类型,要得到byte类型的空字符,需要指明类型

接口和结构体使用时指针和值 T

结构体

  • 接收者无论是指针还是值 T,都可以自动类型转换

  • 接收者是值 T时,方法内会复制一份结构体副本,修改不会影响原属性

  • 接收者是指针时,方法内会直接修改原属性

    type Cat struct {}
    
    func (*Cat) Sing() {
      fmt.Println("Cat is singing")
    }
    
    func (Cat) Sing2() {
      fmt.Println("Cat is singing")
    }
    
    
    func main() {
      cat := new(Cat)
      cat.Sing()      // Cat is singing
      cat.Sing2()     // Cat is singing
      
      cat2 := Cat{}
      cat2.Sing()     // Cat is singing
      cat2.Sing2()    // Cat is singing
    }
    

接口

  • 接收者是指针*T时,接口实例必须是指针

    type Action interface {
      Sing()
    }
    
    type Cat struct {}
    
    func (*Cat) Sing() {
      fmt.Println("Cat is singing")
    }
    
    func Asing(a Action) {
      a.Sing()
    }
    
    func main() {
      cat := new(Cat)
      Asing(cat)      // Cat is singing
        Asing(*cat)       // error, 不能传一个实例
    }
    
  • 接收者是值 T时,接口实例可以是指针也可以是值

    type Action interface {
      Sing()
    }
    
    type Cat struct {}
    
    func (Cat) Sing() {
      fmt.Println("Cat is singing")
    }
    
    func Asing(a Action) {
      a.Sing()
    }
    
    func main() {
      cat := new(Cat)
      Asing(cat)      // Cat is singing
        Asing(*cat)       // Cat is singing
    }
    
  • 接口的定义和类型转换与接收者的定义是关联的

var _ PeerPicker = (*HTTPPool)(nil) 设计目的

确保接口被实现常用的方式。即利用强制类型转换,确保 struct HTTPPool 实现了接口 PeerPicker。这样 IDE 和编译期间就可以检查,而不是等到使用的时候

cap和len的区别

容量是指底层数组的大小,长度指可以使用的大小

  • 容量的用处在哪?在与当你用 append 扩展长度时,如果新的长度小于容量,不会更换底层数组,否则,go 会新申请一个底层数组,拷贝这边的值过去,把原来的数组丢掉。也就是说,容量的用途是:在数据拷贝和内存申请的消耗与内存占用之间提供一个权衡。

  • 长度,则是为了帮助你限制切片可用成员的数量,提供边界查询的。所以用 make 申请好空间后,需要注意不要越界

通道值的发送

  • 向未被初始化的通道发送值会永久阻塞

  • 向关闭的通道发送值会panic

  • 从未被初始化的通道接收值会永久阻塞

  • 从关闭的通道接收值会获得零值

chan通道

经由通道传递的值至少会被复制一次,至多会被复制两次。如果向一个已空的通道发送值,且已有至少一个接收方等待,该通道会绕过本身的缓冲队列把值复制给最早等待的接收方;如果从一个已满的通道接收值,且已有至少一个发送方等待,该通道会把缓冲队列中最早进入的那个值复制给对方,再把最早等待的发送方要发送的值复制到那个已发送的值的原先位置上(通道的缓冲队列是环形队列,可以直接复制到该位置)。

  • 通道传递的过程中会复制元素的值,如果元素是值类型,则会复制该值,接收方对该变量的修改不会影响发送方对变量的使用。如果元素是引用类型,则会复制该变量的地址,接收方对该变量的修改会影响收发双方持有的值。
package main

import (
    "fmt"
    "time"
)

var mapChan = make(chan map[string]int, 1)

func main() {
    syncChan := make(chan struct{}, 2)
    go func() {
        for {
            if elem, ok := <- mapChan; ok {
                elem["count"]++
            } else {
                break
            }
        }
        fmt.Println("stopped. [receiver]")
        syncChan <- struct{}{}
    }()

    go func() {
        countMap := make(map[string]int)
        for i := 0; i < 5; i++ {
            mapChan <- countMap
            time.Sleep(time.Millisecond)
            fmt.Println("the count map: %v. [sender]\n", countMap)
        }
        close(mapChan)
        syncChan <- struct{}{}
    }()
    <- syncChan
    <- syncChan
}

/*
the count map: %v. [sender]
 map[count:1]
the count map: %v. [sender]
 map[count:2]
the count map: %v. [sender]
 map[count:3]
the count map: %v. [sender]
 map[count:4]
the count map: %v. [sender]
 map[count:5]
stopped. [receiver]
*/
  • 当传递的引用类型中包含引用类型时,如结构体类型包含了切片字段。如果内部引用类型是引用变量,那么修改不会影响到原变量;如果内部引用类型是指针变量,那么修改会影响原变量。与修改map的值类似,赋值给其他变量后修改不会影响原变量,但是指针类型的变量修改会影响原始值。

    package main
    
    import (
      "fmt"
      "time"
    )
    
    type Counter struct {
      count int
    }
    
    func (counter *Counter) String() string {
      return fmt.Sprintf("{count: %d}", counter.count)
    }
    
    var mapCntChan = make(chan map[string]Counter, 1)
    
    func main() {
      syncChan := make(chan struct{}, 2)
      go func() {
          for {
              if elem, ok := <-mapCntChan; ok {
                  counter := elem["count"]
                  counter.count++
              } else {
                  break
              }
          }
          fmt.Println("stopped. [receiver]")
          syncChan <- struct{}{}
      }()
    
      go func() {
          countMap := map[string]Counter{
              "count": Counter{},
          }
          for i := 0; i < 5; i++ {
              mapCntChan <- countMap
              time.Sleep(time.Millisecond)
              fmt.Printf("the count map: %v. [sender]\n", countMap)
          }
          close(mapCntChan)
          syncChan <- struct{}{}
      }()
    
      <-syncChan
      <-syncChan
    }
    
    /*
    the count map: map[count:{0}]. [sender]
    the count map: map[count:{0}]. [sender]
    the count map: map[count:{0}]. [sender]
    the count map: map[count:{0}]. [sender]
    the count map: map[count:{0}]. [sender]
    */
    
    package main
    
    import (
      "fmt"
      "time"
    )
    
    type Counter struct {
      count int
    }
    
    func (counter *Counter) String() string {
      return fmt.Sprintf("{count: %d}", counter.count)
    }
    
    var mapCntChan = make(chan map[string]*Counter, 1)
    
    func main() {
      syncChan := make(chan struct{}, 2)
      go func() {
          for {
              if elem, ok := <-mapCntChan; ok {
                  counter := elem["count"]
                  counter.count++
              } else {
                  break
              }
          }
          fmt.Println("stopped. [receiver]")
          syncChan <- struct{}{}
      }()
    
      go func() {
          countMap := map[string]*Counter{
              "count": &Counter{},
          }
          for i := 0; i < 5; i++ {
              mapCntChan <- countMap
              time.Sleep(time.Millisecond)
              fmt.Printf("the count map: %v. [sender]\n", countMap)
          }
          close(mapCntChan)
          syncChan <- struct{}{}
      }()
    
      <-syncChan
      <-syncChan
    }
    
    /*
    the count map: map[count:{count: 1}]. [sender]
    the count map: map[count:{count: 2}]. [sender]
    the count map: map[count:{count: 3}]. [sender]
    the count map: map[count:{count: 4}]. [sender]
    the count map: map[count:{count: 5}]. [sender]
    */
    

单向通道

通道允许的数据传递方向是其类型的一部分,双向通道和转换后的单向通道都不是同一种类型。

利用函数声明将双向通道转为单向通道是一种语法糖,是可行的。但是不能将单向通道转换为双向通道。

select语句

select在执行的时候,所有跟在case关键字右边的发送语句或者接收语句中的通道表达式和元素表达式都会先求值(顺序为从左到右,自上而下),无论该case是否会被选择。

package main

import "fmt"

var intChan1 chan int
var intChan2 chan int
var channels = []chan int{intChan1, intChan2}

var numbers = []int{1, 2, 3, 4, 5}

func main() {
    select {
    case getChan(0) <- getNumber(0):
        fmt.Println("1th case is selected")
    case getChan(1) <- getNumber(1):
        fmt.Println("the 2nd case is selected")
    default:
        fmt.Println("Default!")
    }
}

func getNumber(i int) int {
    fmt.Println("numbers[%d]\n", i)
    return numbers[i]
}

func getChan(i int) chan int {
    fmt.Println("channels[%d]\n", i)
    return channels[i]
}

/*
channels[0]
numbers[0]
channels[1]
numbers[1]
Default!
*/

当一个case被选中时,会忽略其他case。若有多个case满足条件,则会使用伪随机算法选中一个。select语句中的所有case都不满足条件时,且没有default语句,那么当前goroutine就会一直被阻塞直到有一个case满足条件。

你可能感兴趣的:(Golang学习记录的小知识点)