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满足条件。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容