#443 Go 运算符

2020-12-09
  • 算术运算符 Arithmetic Operators
  • + - * / %
  • ++ --
  • 比较运算符 Comparison Operators
  • > < == != >= <=
  • 逻辑运算符 Logical Operators
  • && || !
  • 按位运算符 Bitwise Operators
  • & 按位与 bitwise AND
  • | 按位或 bitwise OR
  • ^ 按位取反 bitwise NOT / 按位异或 bitwise XOR
  • 注意:其他很多语言都是采用 ~ 表示取反
  • &^ bit clear (AND NOT)
  • << 左移,>> 右移
  • 赋值运算符 Assignment Operators
  • = 以及算术运算赋值,位运算赋值
  • 其他运算符 Misc Operators
  • * 指针,& 取地址,<- 通道接收

Operator precedence

| 优先级 | 运算符 |
| ------ | ------------------------------ | ----- | --- |
| 5 | * / % << >> & &^ |
| 4 | + - | ^ |
| 3 | 关系运算符 |
| 2 | && |
| 1 | | | |

示例

package main

import "fmt"

func main() {
    var a uint8 = 0b_0011_1100 // 60
    var b uint8 = 0b_0000_1111 // 15

    fmt.Printf("      a %08b (%d)\n", a, a)
    fmt.Printf("      b %08b (%d)\n", b, b)
    fmt.Println()

    fmt.Printf("    NOT %08b 非(NOT a)\n", (^a))
    fmt.Println()

    fmt.Printf("    AND %08b 与\n", (a & b))
    fmt.Printf("     OR %08b 或\n", (a | b))
    fmt.Printf("    XOR %08b 异或\n", (a ^ b))
    fmt.Println()

    fmt.Printf("   NAND %08b 与非\n", ^(a & b))
    fmt.Printf("    NOR %08b 或非\n", ^(a | b))
    fmt.Printf("   XNOR %08b 同或(异或非)\n", ^(a ^ b))
    fmt.Println()

    fmt.Printf("AND NOT %08b BitClear\n", (a &^ b))
    // fmt.Printf("AND NOT %08b BitClear\n", (a & (^b)))

    // 格式化工具会将 ^ 和右操作数放在一起
    // 这一点和 &^ 不同,应该是由于 Golang Spec 中,将 &^ 当成一个操作符
    // 本质上,没有什么区别,都是先按位取反,再按位与或
    fmt.Printf(" OR NOT %08b Useless\n", (a | ^b))
    // fmt.Printf(" OR NOT %08b Useless\n", (a | (^b)))
}
//       a 00111100 (60)
//       b 00001111 (15)

//     NOT 11000011 非(NOT a)

//     AND 00001100 与
//      OR 00111111 或
//     XOR 00110011 异或

//    NAND 11110011 与非
//     NOR 11000000 或非
//    XNOR 11001100 同或(异或非)

// AND NOT 00110000 BitClear
//  OR NOT 11111100 Useless

参考资料与拓展阅读

#442 Golang strings.Builder

2020-12-08

https://github.com/golang/go/blob/master/src/strings/builder.go

主要的作用是频繁拼接字符串,相比字符串拼接操作符(+)或者 fmt.SPrintf 效率更高。

实例

package main

import (
    "fmt"
    "strings"
)

func main() {
    var builder strings.Builder
    builder.WriteString("Hello")
    builder.WriteString(", ")
    builder.WriteString("World!")
    result := builder.String()
    fmt.Println(result)
}

方法

  • func (b *Builder) String() string
  • func (b *Builder) Len() int { return len(b.buf)
  • func (b *Builder) Cap() int { return cap(b.buf)
  • func (b *Builder) Reset() 内容重置
  • func (b *Builder) Grow(n int) 预分配空间,正确使用可以提升程序执行效率
  • func (b *Builder) Write(p []byte) (int, error)
  • func (b *Builder) WriteByte(c byte) error
  • func (b *Builder) WriteRune(r rune) (int, error)
  • func (b *Builder) WriteString(s string) (int, error)

源码

实际上就是针对 []byte 的一个封装。

// A Builder is used to efficiently build a string using Write methods.
// It minimizes memory copying. The zero value is ready to use.
// Do not copy a non-zero Builder.
type Builder struct {
    addr *Builder // of receiver, to detect copies by value
    buf  []byte
}

func (b *Builder) copyCheck() {
    if b.addr == nil {
        // This hack works around a failing of Go's escape analysis
        // that was causing b to escape and be heap allocated.
        // See issue 23382.
        // TODO: once issue 7921 is fixed, this should be reverted to
        // just "b.addr = b".
        b.addr = (*Builder)(noescape(unsafe.Pointer(b)))
    } else if b.addr != b {
        panic("strings: illegal use of non-zero Builder copied by value")
    }
}

// WriteString appends the contents of s to b's buffer.
// It returns the length of s and a nil error.
func (b *Builder) WriteString(s string) (int, error) {
    b.copyCheck()
    b.buf = append(b.buf, s...)
    return len(s), nil
}
  • addr 指向自己的指针,不知道干什么用
  • buf 是 buffer,缓冲区,用于存储 build 出来的字符串
  • 通过缓冲区做存储,避免频繁创建字符串对象,减少内存分配和垃圾回收

性能测试

BenchmarkStrPlus-20             100000000               11.11 ns/op            0 B/op          0 allocs/op
BenchmarkSprintf-20             12129922               101.5 ns/op            48 B/op          3 allocs/op
BenchmarkStrJoin-20             45257742                24.47 ns/op           16 B/op          1 allocs/op
BenchmarkStrBuilder-20          38868284                36.31 ns/op           24 B/op          2 allocs/op
BenchmarkByteBuf-20             22016934                53.34 ns/op           80 B/op          2 allocs/op
BenchmarkByteSlice-20           31495318                36.13 ns/op           24 B/op          2 allocs/op
BenchmarkByteSlice2-20          39563612                32.18 ns/op           24 B/op          2 allocs/op

BenchmarkStrPlusBatch-20         7331364               170.0 ns/op           112 B/op          6 allocs/op
BenchmarkSprintfBatch-20         1481492               823.2 ns/op           328 B/op         20 allocs/op
BenchmarkStrJoinBatch-20        19598844                59.78 ns/op           32 B/op          1 allocs/op
BenchmarkStrBuilderBatch-20     13560961                87.39 ns/op           56 B/op          3 allocs/op
BenchmarkByteBufBatch-20        10693280                97.61 ns/op           96 B/op          2 allocs/op
BenchmarkByteSliceBatch-20      12115166                95.27 ns/op           56 B/op          3 allocs/op
BenchmarkByteSlice2Batch-20     13136418                90.53 ns/op           56 B/op          3 allocs/op

fmt.Sprintf 用来做字符串拼接的话,性能总是最差。
此外,如果只是简单的两个字符串相加,直接加号拼接性能最好。
否则就根据情况,选用其他方案比较好。

#441 Golang Bytes 相关操作

2020-12-08

byte 实际上是 uint8 的别名,[]byte 也就是 uint8 切片。

常见操作

package main

import (
    "bytes"
    "encoding/hex"
    "fmt"
    "strconv"
)

func main() {
    // 1. 转换
    str1 := "HELLO WORLD"
    bytes0 := []byte(str1) // string to bytes
    str2 := string(bytes0) // bytes to string
    fmt.Printf("%#v\n", bytes0)
    // []byte{0x48, 0x45, 0x4c, 0x4c, 0x4f, 0x20, 0x57, 0x4f, 0x52, 0x4c, 0x44}
    fmt.Println(str1, bytes0, str2)
    // HELLO WORLD [72 69 76 76 79 32 87 79 82 76 68] HELLO WORLD

    // 2. 连接
    bytes1 := []byte("HELLO ")
    bytes2 := []byte("WORLD")
    bytes3 := append(bytes1, bytes2...)
    fmt.Println(bytes3)
    // [72 69 76 76 79 32 87 79 82 76 68]

    // 3. 拷贝
    bytes4 := make([]byte, len(bytes3))
    copy(bytes4, bytes3)
    fmt.Println(bytes4)
    // [72 69 76 76 79 32 87 79 82 76 68]

    // 4. 长度和容量
    fmt.Println(len(bytes4), cap(bytes4)) // 11 11

    // 5. 截取
    bytes5 := bytes4[7:10]
    fmt.Println(bytes5) // [79 82 76]

    // 6. 比较
    bytes6 := []byte("HELLO WORLD")
    fmt.Println(bytes.Equal(bytes4, bytes6)) // true

    // 7. 遍历
    for i := 0; i < len(bytes6); i++ {
        fmt.Printf("%d ", bytes6[i])
    }
    fmt.Println()
    // 72 69 76 76 79 32 87 79 82 76 68

    // 8. 转化为数字
    bytes7 := []byte("12345")
    num, _ := strconv.Atoi(string(bytes7))
    fmt.Println(num) // 12345

    // 9. 转化为16进制字符串
    bytes8 := []byte("HELLO WORLD")
    hexStr := hex.EncodeToString(bytes8)
    fmt.Println(hexStr) // 48454c4c4f20574f524c44
}

输出

package main

import (
    "bytes"
    "fmt"
    "io"
    "os"
)

func main() {
    buf := new(bytes.Buffer) // *bytes.Buffer
    // func (b *Buffer) WriteString(s string) (n int, err error)
    buf.WriteString("HELLO ")
    buf.WriteString("WORLD")

    fmt.Println(string(buf.Bytes()))
    fmt.Println(buf.String()) // 相同

    // ========================================

    w := os.Stdout // *os.File
    // func (f *File) Write(b []byte) (n int, err error)
    // func (f *File) WriteString(s string) (n int, err error)
    w.WriteString("HELLO ")

    n, err := io.WriteString(w, "WORLD\n")
    if err != nil {
        panic(err)
    }
    fmt.Printf("n: %d\n", n)
}

利用 reflect 实现 bytes / string 转换

参考:https://www.sobyte.net/post/2022-01/go-string-bytes/

func String2Bytes(s string) []byte {
    sh := (*reflect.StringHeader)(unsafe.Pointer(&s))
    bh := reflect.SliceHeader{
        Data: sh.Data,
        Len:  sh.Len,
        Cap:  sh.Len,
    }
    return *(*[]byte)(unsafe.Pointer(&bh))
}

func Bytes2String(b []byte) string {
    return *(*string)(unsafe.Pointer(&b))
}

#440 Golang 字符串 string

2020-12-08
package main

func main() {
    a := "hello 世界"
    fmt.Printf("%#v (%d)\n", a, len(a))
    // "hello 世界" (12)

    // 按 rune ForRange 循环
    for k, v := range a {
        fmt.Printf("%#4v. %#6v (%T)\n", k, v, v)
    }
    // 按 byte 索引取值
    fmt.Printf("a[0]: %#6v (%T)\n", a[0], a[0])
    fmt.Printf("a[6]: %#6v (%T)\n", a[6], a[6])
    //    0.    104 (int32)
    //    1.    101 (int32)
    //    2.    108 (int32)
    //    3.    108 (int32)
    //    4.    111 (int32)
    //    5.     32 (int32)
    //    6.  19990 (int32)
    //    9.  30028 (int32)
    // a[0]:   0x68 (uint8)
    // a[6]:   0xe4 (uint8)

    b := []byte(a)
    fmt.Printf("%#v\n", b)
    // []byte{0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0xe4, 0xb8, 0x96, 0xe7, 0x95, 0x8c}

    c := []rune(a)
    fmt.Printf("%#v\n", c)
    // []int32{104, 101, 108, 108, 111, 32, 19990, 30028}
}
  • len(s)
  • strings.Join(s1, s2) string 连接字符串
  • strings.ContainsRune(s, rune) bool
  • strings.Contains(s, substr) bool 子串包含判断
    注意:任何字符串都包含空字符串
  • strings.ContainsAny(s, chars string) bool 字符包含判断
  • strings.Index(s, substr) int 定位(Rabin-Karp 算法)
  • strings.Count(s, substr) int 字符计算(Rabin-Karp 算法)
    注意:任何字符串包含 n + 1 个空字符串
  • strings.HasPrefix(s, substr) bool startswith
  • strings.HasSuffix(s, substr) bool endswith
  • strings.Compare(s1, s2) 大于 1 / 小于 -1 / 等于 0
  • strings.Repeat(s, num) string 替换子串
  • strings.Replace(s, substr, replacement, num) string
  • strings.EqualFold(s1, s2) bool 忽略大小写等于判断
  • strings.Fields(s) 用空白字符(空格、换行、tab)将字符串切割成字串
  • strings.Split(s, sep string) []string 切割字符串
  • strings.SplitAfter(s, sep string) []string 切割字符串(包含分隔符)
  • strings.SplitAfterN(s, sep string, n int) []string
  • strings.SplitN(s, sep string, n int) []string

参考 Golang strings

字符串连接:

  • +, +=
  • fmt.Sprintf
  • strings.Join
  • bytes.Buffer
var b bytes.Buffer
b.WriteString("hello")
b.WriteString(" world")
fmt.Println(b.String())
  • strings.Builder
var sb strings.Builder
sb.WriteString("hello")
sb.WriteString(" world")
fmt.Println(sb.String())

#439 Golang: 正则表达式

2020-12-06

Compile 系列

func Compile(expr string) (*Regexp, error)
func CompilePOSIX(expr string) (*Regexp, error)
func MustCompile(str string) *Regexp
func MustCompilePOSIX(str string) *Regexp

函数名称中的 Must 表示:如果正则错误,直接 panic

Match 系列

func (re *Regexp) Match(b []byte) bool
func (re *Regexp) MatchReader(r io.RuneReader) bool
func (re *Regexp) MatchString(s string) bool

Find 系列

func (re *Regexp) Find(b []byte) []byte
func (re *Regexp) FindAll(b []byte, n int) [][]byte
func (re *Regexp) FindAllIndex(b []byte, n int) [][]int
func (re *Regexp) FindAllString(s string, n int) []string
func (re *Regexp) FindAllStringIndex(s string, n int) [][]int
func (re *Regexp) FindAllStringSubmatch(s string, n int) [][]string
func (re *Regexp) FindAllStringSubmatchIndex(s string, n int) [][]int
func (re *Regexp) FindAllSubmatch(b []byte, n int) [][][]byte
func (re *Regexp) FindAllSubmatchIndex(b []byte, n int) [][]int
func (re *Regexp) FindIndex(b []byte) (loc []int)
func (re *Regexp) FindReaderIndex(r io.RuneReader) (loc []int)
func (re *Regexp) FindReaderSubmatchIndex(r io.RuneReader) []int
func (re *Regexp) FindString(s string) string
func (re *Regexp) FindStringIndex(s string) (loc []int)
func (re *Regexp) FindStringSubmatch(s string) []string
func (re *Regexp) FindStringSubmatchIndex(s string) []int
func (re *Regexp) FindSubmatch(b []byte) [][]byte
func (re *Regexp) FindSubmatchIndex(b []byte) []int

其实好记,Find(All)?(String)?(Submatch)?(Index)? 一组合就有 16 种了,再加上两个 FindReader 方法。

Replace 系列

func (re *Regexp) ReplaceAll(src, repl []byte) []byte
func (re *Regexp) ReplaceAllFunc(src []byte, repl func([]byte) []byte) []byte
func (re *Regexp) ReplaceAllLiteral(src, repl []byte) []byte
func (re *Regexp) ReplaceAllLiteralString(src, repl string) string
func (re *Regexp) ReplaceAllString(src, repl string) string
func (re *Regexp) ReplaceAllStringFunc(src string, repl func(string) string) string

其他

func (re *Regexp) Copy() *Regexp // DEPRECATED
func (re *Regexp) Expand(dst []byte, template []byte, src []byte, match []int) []byte
func (re *Regexp) ExpandString(dst []byte, template string, src string, match []int) []byte
func (re *Regexp) LiteralPrefix() (prefix string, complete bool)
func (re *Regexp) Longest()
func (re *Regexp) NumSubexp() int
func (re *Regexp) Split(s string, n int) []string
func (re *Regexp) String() string
func (re *Regexp) SubexpIndex(name string) int
func (re *Regexp) SubexpNames() []string

四个封装方法

func Match(pattern string, b []byte) (matched bool, err error)
func MatchReader(pattern string, r io.RuneReader) (matched bool, err error)
func MatchString(pattern string, s string) (matched bool, err error)
func QuoteMeta(s string) string

示例

func main() {
    text := "Hello, 2021! The year 2020 was great, but 2021 will be even better."

    pattern := `\b\d{4}\b` // 匹配四个数字的单词

    regex, err := regexp.Compile(pattern)
    if err != nil {
        fmt.Println("Error compiling regex:", err)
        return
    }

    matches := regex.FindAllString(text, -1)
    for _, match := range matches {
        fmt.Println(match)
    }
}

Output:

2021
2020
2021

#437 Golang:newmake

2020-12-05
$ go doc builtin.new
package builtin // import "builtin"

func new(Type) *Type
    The new built-in function allocates memory. The first argument is a type,
    not a value, and the value returned is a pointer to a newly allocated zero
    value of that type.

$ go doc builtin.make
package builtin // import "builtin"

func make(t Type, size ...IntegerType) Type
    The make built-in function allocates and initializes an object of type
    slice, map, or chan (only). Like new, the first argument is a type, not a
    value. Unlike new, make's return type is the same as the type of its
    argument, not a pointer to it. The specification of the result depends on
    the type:

        Slice: The size specifies the length. The capacity of the slice is
        equal to its length. A second integer argument may be provided to
        specify a different capacity; it must be no smaller than the
        length. For example, make([]int, 0, 10) allocates an underlying array
        of size 10 and returns a slice of length 0 and capacity 10 that is
        backed by this underlying array.
        Map: An empty map is allocated with enough space to hold the
        specified number of elements. The size may be omitted, in which case
        a small starting size is allocated.
        Channel: The channel's buffer is initialized with the specified
        buffer capacity. If zero, or the size is omitted, the channel is
        unbuffered.
  • func new(Type) *Type
  • func make(t Type, size ...IntegerType) Type

newmake 的区别

  1. new 没有类型限制,make 只能用来分配和初始化 slice,map,chan。
  2. new 返回指针,make 返回引用(引用类型的值)。
  3. new 会将分配的空间置零(对应类型的零值),make 则可以类型初始化,比如 slice 的长度和容量。
package main

import "fmt"

type User struct {
    Name string
}

type Addr struct{}

func main() {
    user1 := new(User)
    user2 := new(User)
    fmt.Printf("%#v (%p) %d\n", user1, user1, &user1)
    fmt.Printf("%#v (%p) %d\n", user2, user2, &user2)
    fmt.Printf("user1 == user2 : %#v\n", user1 == user2)

    addr1 := new(Addr)
    addr2 := new(Addr)
    fmt.Printf("%#v (%p) %d\n", addr1, addr1, &addr1)
    fmt.Printf("%#v (%p) %d\n", addr2, addr2, &addr2)
    fmt.Printf("addr1 == addr2 : %#v\n", addr1 == addr2)
}
&main.User{Name:""} (0xc00006a250) 824633745448
&main.User{Name:""} (0xc00006a260) 824633745456
user1 == user2 : false
&main.Addr{} (0xf61438) 824633745472
&main.Addr{} (0xf61438) 824633745480
addr1 == addr2 : true

注意上面这一点,空 struct 多次 new 出的指针完全相同。暂时没有想明白这样设计的好处。

#436 Go 关键字

2020-12-05

25 个关键字

break        default      func         interface    select
case         defer        go           map          struct
chan         else         goto         package      switch
const        fallthrough  if           range        type
continue     for          import       return       var

声明 (4)

  1. var 变量
  2. const 常量
  3. type 类型
  4. func 函数

并发相关 (3)

  1. go 并发
  2. chan 信道
  3. select 分支

类型 (3)

  1. interface 接口
  2. map 映射
  3. struct 结构体

流程控制 (3 + 4 + 6)

  1. defer 延迟执行
  2. goto 跳转
  3. return 返回

循环 (4)

  1. for
  2. continue
  3. break
  4. range 用于读取 slice、map、channel 数据

分支 (6)

  1. if
  2. else
  3. switch
  4. case
  5. default
  6. fallthrough

包 (2)

  1. package
  2. import

39 个预定义标识符

Types:
    any bool byte comparable
    complex64 complex128 error float32 float64
    int int8 int16 int32 int64 rune string
    uint uint8 uint16 uint32 uint64 uintptr

Constants:
    true false iota

Zero value:
    nil

Functions:
    append cap close complex copy delete imag len
    make new panic print println real recover

值 (4)

  1. true
  2. false
  3. iota
  4. nil

类型 (20 + 2)

int (10)

  1. int
  2. int8
  3. int16
  4. int32
  5. int64
  6. uint
  7. uint8
  8. uint16
  9. uint32
  10. uint64

complex (2)

  1. complex64
  2. complex128

float (2)

  1. float32
  2. float64

字符与字符串 (3)

  1. byte => uint8
  2. rune => int32
  3. string

泛型相关 (2) Go1.18+

  1. any
  2. comparable

其他 (3)

  1. bool
  2. uintptr 指针
  3. error 一个内置的 interface

Builtin 函数 (15)

  1. append
  2. delete
  3. close

  4. cap

  5. len

  6. copy

  7. make

  8. new

  9. panic

  10. recover

  11. print

  12. println

  13. real

  14. imag
  15. complex

#435 Go 模板

2020-12-01

fmt.Sprintf 字符串格式化

tpl := `[%s] Your verify code is %s.`
s := fmt.Sprintf(tpl, "Markjour", "1234")
println(s)

os.Expand 变量替换

tpl := `[${sign}] Your verify code is ${code}.`
params := map[string]string{"sign": "Markjour", "code": "1234"}
println(os.Expand(tpl, func(k string) string { return params[k] }))

text/template 和 html/template

这两个就可以处理复杂的情况,嵌套模板,控制语句都支持。

package main

import (
    "os"
    "text/template"
)

func main() {
    tpl := `[{{.sign}}] Your verify code is {{.code}}.`
    t := template.New("just-a-name")
    t, _ = t.Parse(tpl)
    params := map[string]string{"sign": "Markjour", "code": "1234"}
    t.Execute(os.Stdout, params)
}

附:strings.Map / bytes.Map 提供单个字符的替换

func Map(mapping func(rune) rune, s string) string
package main

import (
    "fmt"
    "strings"
)

const A = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~ \n"
const B = "N'|&4:@ j{BI+Y!H/Q_iR\\FM}$moLe?#X\"WCE3S,8(r1f%T.;6DbaG]y`q~ltJxu-k2gA\nvhnd=)*s7Z5p^OK[V0z>9<UcwP"

func main() {
    encrypt := func(r rune) rune {
        if !strings.ContainsRune(A, r) {
            return 0
        }
        return rune(B[strings.IndexRune(A, r)])
    }
    decrypt := func(r rune) rune {
        if !strings.ContainsRune(B, r) {
            return 0
        }
        return rune(A[strings.IndexRune(B, r)])
    }

    raw := "Life was like a box of chocolate, you never know what you're gonna get."
    fmt.Println(raw)

    encrypted := strings.Map(encrypt, raw)
    fmt.Println(encrypted)
    // 3j:4wFN_wIjB4wNw'!Mw!:w| !|!INi4dw}!RwY4\4QwBY!FwF Niw}!RAQ4w@!YYNw@4i)

    decrypted := strings.Map(decrypt, encrypted)
    fmt.Println(decrypted)
}

参考资料与拓展阅读