#428 Golang 数组操作

2020-12-15

声明

# array
[length]Type
[length]Type{v1, v2, ..., vn}
[...]Type{v1, v2, ..., vn}

# slice
[]Type
[]Type{v1, v2, ..., vn}
make([]Type, length)
make([]Type, length, capacity)

我想尝试动态创建数组(通过 arrLen 变量),没有找到方法,可能这就是静态类型语言吧。
所以,数组必须在写代码的时候就明确长度。否则就应该用切片。

此外,数组应该算是值类型。赋值的时候,会完全复制一份,传参也是一样。

Slice 等于数组指针 + 长度 + 容量。

package main

import "fmt"

func main() {
    var a []int
    fmt.Printf("%#v \t%d %d\n", a, len(a), cap(a)) // []int(nil) 0 0

    b := []int{1, 2, 3}
    fmt.Printf("%#v \t%d %d\n", b, len(b), cap(b)) // []int{1, 2, 3} 3 3

    var c [3]int
    fmt.Printf("%#v \t%d %d\n", c, len(c), cap(c)) // [3]int{0, 0, 0} 3 3

    d := [...]int{1, 2, 3}
    fmt.Printf("%#v \t%d %d\n", d, len(d), cap(d)) // [3]int{1, 2, 3} 3 3

    d2 := [...]int{3: 100}
    fmt.Printf("%#v \t%d %d\n", d2, len(d2), cap(d2)) // [4]int{0, 0, 0, 100} 4 4

    e := d[:]
    fmt.Printf("%#v \t%d %d\n", e, len(e), cap(e)) // []int{1, 2, 3} 3 3

    // 动态创建切片
    f := make([]int, 3, 10)
    fmt.Printf("%#v \t%d %d\n", f, len(f), cap(f)) // []int{0, 0, 0} 3 10

    // new 返回指针
    g := new([]int)
    fmt.Printf("%#v \t%d %d\n", g, len(*g), cap(*g)) // &[]int(nil) 0 0

    h := new([3]int)
    fmt.Printf("%#v \t%d %d\n", h, len(*h), cap(*h)) // &[]int{0, 0, 0} 3 3

    type User struct {
        Username string
        Password string
    }
    users := []User{
        {"a", "pass1"},
        {"b", "pass2"},  // 这样分行写的话,不能省略这个逗号
    }
    fmt.Printf("%+v \t%d %d\n", users, len(users), cap(users))
    // [{Username:a Password:pass1} {Username:b Password:pass2}] 2 2
}

索引

a[n]
a[n:m]
a[n:]
a[:m]
a[:]

// 索引为负数
// invalid argument: index -1 (constant of type int) must not be negative

// 索引越界
// invalid argument: array index 10 out of bounds [0:5]

遍历

for i := 0; i < len(arr); i++ {
    val := arr[i]
    // ...
}

for i, val := range arrayVar {
    // ...
}

// 如果只有一个变量接收遍历的值,那么这个变量将是 index
// 这个地方总是容易写错,要小心
for i := range arrayVar {
    val := arrayVar[i]
    // ...
}

复制

s1 := []int{1, 3, 6}
s2 := append([]int{}, s1...)
fmt.Printf("%#v %p\n", s1, s1)
fmt.Printf("%#v %p\n", s2, s2)

s3 := make([]int, len(s1))
copy(s3, s1)
fmt.Printf("%#v %p\n", s3, s3)

注意:copy 复制内容的长度以 src,dst 中较小的长度为准。

删除

a := []int{1, 2, 3, 4, 5, 6, 7}
// del a[3]
a = append(a[:3], a[4:]...)

插入

// 在尾部插入一个
a = append(a, ele)

// 在尾部插入一个数组或切片
a = append(a, arr...)

// copy 也是一种方法
a := [10]int{1, 2, 3}
b := []int{4, 5, 6}
copy(a[3:], b)

// WRONG!
a := []int{1, 2, 3, 5, 6, 7}
tmp := a[:3]
tmp = append(tmp, 4)
fmt.Println(a)  // [1 2 3 4 6 7]

func insert(slice []int, index int, num int) []int {
    rest_len := len(slice) - index
    tmp := make([]int, rest_len, len(slice)+1)
    copy(tmp, slice[:index])
    tmp[index] = num
    tmp = append(tmp, slice[index:]...)
    return tmp
}

弹出

数组长度固定无法执行“弹出”操作,但是可以删除其中的一个节点。

nums := []int{1, 2, 3, 4}
first := nums[0]
nums = nums[1:]
last := nums[len(nums) - 1]
nums := nums[:len(nums) - 1]

合并

package main

import "fmt"

func main() {
    a := []int{1, 2, 3}
    b := []int{1, 2, 3}
    c := [3]int{4, 5, 6}
    d := [3]int{4, 5, 6}

    // 切片合并
    e := append(a, b...)
    fmt.Printf("%#v, %#v, %#v\n", a, b, e)

    f := append(c[:], d[:]...)
    fmt.Printf("%#v, %#v, %#v\n", c, d, f)

    g := [6]int{}
    for i, v := range a {
        g[i] = v
    }
    length := len(a)
    for i, v := range a {
        g[i+length] = v
    }
    fmt.Printf("%#v\n", g)

    h := [6]int{}
    // func copy(dst, src []Type) int
    copy(h[:], a)
    fmt.Printf("%#v\n", h)
    copy(h[3:], a)
    fmt.Printf("%#v\n", h)
}

排序

package main

import (
    "fmt"
    "sort"
)

func main() {
    nums := []int{9, 5, 2, 7, 1}
    sort.Ints(nums)
    fmt.Println(nums)
}
  • sort.Strings
  • sort.Float64s

#427 godoc 命令

2020-12-11
godoc -http=:6060 -index -play -timestamps -v

# 如果在 go 模块目录执行,会列出本地依赖包的文档
# using module mode; GOMOD=xxx.mod

go doc fmt
go doc fmt.Println
go doc fmt Println
go doc -src fmt Println

# 查看本地包文档
go doc github.com/astaxie/beego
go doc github.com/astaxie/beego Listen
go doc -all github.com/astaxie/bee

PS: 关于 Go 项目文档

  1. 紧挨者 package, const, type, func 等关键字的注释

问题

C:\Users\admin>godoc -http=:6060 -index -play -timestamps -v
'godoc' 不是内部或外部命令,也不是可运行的程序
或批处理文件。

新版本不再自带 godoc 工具,需要另外安装:

go install golang.org/x/tools/cmd/godoc@latest

#425 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

参考资料与拓展阅读

#424 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 用来做字符串拼接的话,性能总是最差。
此外,如果只是简单的两个字符串相加,直接加号拼接性能最好。
否则就根据情况,选用其他方案比较好。

#423 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))
}

#422 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())

#421 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

#419 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 出的指针完全相同。暂时没有想明白这样设计的好处。