Go 语言小细节(四)


map 的容量
你可以在 map 创建时指定它的容量,但你无法在 map 上使用 cap() 函数。

package main
func main() {  
    m := make(map[string]int,99)
    cap(m)
}

匿名函数作用域陷阱

import (
    "fmt"
)

func main(){
    var msgs []func()
    array := []string{
        "1", "2", "3", "4",
    }
    for _, e := range array{
        msgs = append(msgs, func(){
            fmt.Println(e)
        })
    }
    for _, v := range msgs{
        v()
    }
}

阅读全文

Go 语言小细节(三)


在 for 语句的闭包中使用迭代变量会有问题
在 for 迭代过程中,迭代变量会一直保留,只是每次迭代值不一样。因此在 for 循环中在闭包里直接引用迭代变量,在执行时直接取迭代变量的值,而不是闭包所在迭代的变量值。如果闭包要取所在迭代变量的值,就需要 for 中定义一个变量来保存所在迭代的值,或者通过闭包函数传参。

package main

import (  
    "fmt"
    "time"
)

func forState1(){
    data := []string{"one","two","three"}

    for _,v := range data {
        go func() {
            fmt.Println(v)
        }()
    }
    time.Sleep(3 * time.Second)
    // three, three, three

    for _,v := range data {
        vcopy := v
        // 使用临时变量
        go func() {
            fmt.Println(vcopy)
        }()
    }
    time.Sleep(3 * time.Second)
    // one, two, three

    for _,v := range data {
        go func(in string) {
            fmt.Println(in)
        }(v)
    }
    time.Sleep(3 * time.Second)
    // one, two, three
}

func main() {  
    forState1()
}

阅读全文

Go 语言小细节(二)


数组用于函数传参时是值复制
方法或函数调用时,传入参数都是值复制,跟赋值一致,除非是 map、slice、channel、指针类型这些特殊类型是引用传递。

x := [3]int{1,2,3}

数组在函数中传参是值复制

func(arr [3]int) {
    arr[0] = 7
    fmt.Println(arr)
    // [7 2 3]
}(x)
fmt.Println(x)
// [1 2 3]

使用数组指针实现引用传参

func(arr *[3]int) {
    (*arr)[0] = 7
    fmt.Println(arr)
    // &[7 2 3]
}(&x)
fmt.Println(x)
// [7 2 3]

阅读全文

Go 语言小细节(一)


多个 defer 出现的时候,多个 defer 之间按照 LIFO(后进先出)的顺序执行

package main
import "fmt"
func main(){
    defer func(){
        fmt.Println("1")
    }()
    defer func(){
        fmt.Println("2")
    }()
    defer func(){
        fmt.Println("3")
    }()
}

对应的输出是:

3
2
1

阅读全文

Go 实现简单的 Queue 队列


需求
队列的特性较为单一,基本操作即初始化、获取大小、添加元素、移除元素等。最重要的特性就是满足先进先出。

实现
接下来还是按照以前的套路,一步一步来分析如何利用Go的语法特性实现Queue这种数据结构。

定义
首先定义每个节点 Node 结构体,照例 Value 的值类型可以是任意类型,节点的前后指针域指针类型为 Node

type node struct {
    value interface{}
    prev *node
    next *node
}

继续定义链表结构,定义出头结点和尾节点的指针,同时定义队列大小 size:

type LinkedQueue struct {
    head *node
    tail *node
    size int
}



阅读全文

Go convert "type []string" to string


package main

import (
    "fmt"
    "strings"
)

var naiveList string

func main() {
    naiveArray := []string{"a", "b", "c", "d", "e"}
    for _, v := range naiveArray {
        if naiveList == "" {
        naiveList = v
        } else {
        naiveList = strings.Join([]string{naiveList, v}, ",")
        }
    }
    fmt.Println(naiveList)
}
阅读全文

Go 字符串删除


删除在 s 字符串的头部和尾部中由 cutset 指定的字符, 并返回删除后的字符串

func Trim(s string, cutset string) string

删除首部和尾部的 ! 和空格

// "Achtung! Achtung"
fmt.Printf("%q\n", strings.Trim(" !!! Achtung! Achtung! !!! ", "! "))
// "a lone gopher"
fmt.Printf("%q\n", strings.TrimSpace(" \t\n a lone gopher \n\t\r\n"))
阅读全文

Go 字符串大小写替换


给定字符串转换为英文标题的首字母大写的格式 不能正确处理 unicode 标点

func Title(s string) string

所有字母转换为小写

func ToLower(s string) string

所有字母转换为大写

func ToUpper(s string) string

// Her Royal Highness
fmt.Println(strings.Title("her royal highness"))
// gopher123
fmt.Println(strings.ToLower("Gopher123"))
// GOPHER
fmt.Println(strings.ToUpper("Gopher"))
阅读全文

Go 字符串拼接


1.直接使用运算符

func BenchmarkAddStringWithOperator(b *testing.B) {
    hello := "hello"
    world := "world"
    for i := 0; i < b.N; i++ {
        _ = hello + "," + world
    }
}

golang 里面的字符串都是不可变的,每次运算都会产生一个新的字符串,所以会产生很多临时的无用的字符串,不仅没有用,还会给 gc 带来额外的负担,所以性能比较差。

2.fmt.Sprintf()

func BenchmarkAddStringWithSprintf(b *testing.B) {
    hello := "hello"
    world := "world"
    for i := 0; i < b.N; i++ {
        _ = fmt.Sprintf("%s,%s", hello, world)
    }
}

内部使用 []byte 实现,不像直接运算符这种会产生很多临时的字符串,但是内部的逻辑比较复杂,有很多额外的判断,还用到了 interface,所以性能也不是很。。

阅读全文