Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

golang的基本语言知识 #62

Open
BruceChen7 opened this issue Apr 9, 2023 · 0 comments
Open

golang的基本语言知识 #62

BruceChen7 opened this issue Apr 9, 2023 · 0 comments

Comments

@BruceChen7
Copy link
Owner

BruceChen7 commented Apr 9, 2023

参考资料

(usage:: 默认 nil)

bool      -> false
numbers -> 0
string    -> ""

pointers -> nil
slices -> nil
maps -> nil
channels -> nil
functions -> nil
interfaces -> nil


type Person struct {
  AgeYears int
  Name string
  Friends []Person
}

var p Person // Person{0, "", nil}

var p *int
p == nil    // true
*p          // panic: invalid memory address or nil pointer dereference

slice 与 nil

// nil slices
var s []slice
len(s)  // 0
cap(s)  // 0
for range s  // iterates zero times
s[i]  // panic: index out of range

map 与 nil

// nil maps
var m map[t]u
len(m)  // 0
for range m // iterates zero times
v, ok := m[i] // zero(u), false
m[i] = x // panic: assignment to entry in nil map
  • 对于 nil 的 map,能简单把它看成是一个只读的 map,不能进行写
    func NewGet(url string, headers map[string]string) (*http.Request, error) {
      req, err := http.NewRequest(http.MethodGet, url, nil)
      if err != nil {
        return nil, err
      }
    
      for k, v := range headers {
        req.Header.Set(k, v)
      }
      return req, nil
    }
    
    NewGet("http://google.com", map[string]string{
      "USER_AGENT": "golang/gopher",
    })
    
    // 能这样传入一个 nil
    NewGet("http://google.com", map[string]string{})
    // 也能这样
    NewGet("http://google.com", nil)

channel 与 nil

// nil channels
var c chan t
<- c      // blocks forever
c <- x    // blocks forever
close(c)  // panic: close of nil channel

// 假如现在有两个 channel 负责输入,一个 channel 负责汇总,简单的实现代码:
func merge(out chan<- int, a, b <-chan int) {
  for {
    select {
      case v := <-a:
        out <- v
      case v := <- b:
        out <- v
    }
  }
}
// 如果在外部调用中关闭了 a 或者 b,那么就会不断地从 a 或者 b 中读出 0,这和想要的不一样,想关闭 a 和 b 后就停止汇总了,
// 修改一下代码:
func merge(out chan<- int, a, b <-chan int) {
  for a != nil || b != nil {
    select {
      case v, ok := <-a:
          if !ok {
            a = nil
            fmt.Println("a is nil")
            continue
          }
          out <- v
      case v, ok := <-b:
          if !ok {
            b = nil
            fmt.Println("b is nil")
            continue
          }
          out <- v
    }
  }
  fmt.Println("close out")
  close(out)
}
  • channel 关闭后,将 channel 的值设为 nil,就相当于将这个 select case 子句停用了,
  • 因为 nil 的 channel 是永远阻塞的。

nil 与 interface

  • interface 并不是一个指针,它的底层实现由两部分组成,
  • 一个是类型,一个值,也就是类似于:(Type, Value)。
  • 只有当类型和值都是 nil 的时候,才等于 nil。
    func do() error {   // error(*doError, nil)
      var err *doError
      return err  // nil of type *doError
    }
    
    func main() {
      err := do()
      // 输出的是 false
      fmt.Println(err == nil)
    }
  • 输出结果是 false。
  • do 函数声明了一个*doErro 的变量 err,然后返回,返回值是 error 接口,Type 已经变成了(*doError,nil),所以和 nil 肯定是不会相等的。
  • 在写函数的时候,不要声明具体的 error 变量,而是应该直接返回 nil:
    func do() error {
      return nil
    }
    
    func do() *doError {  // nil of type *doError
      return nil
    }
    
    func wrapDo() error { // error (*doError, nil)
      return do()       // nil of type *doError
    }
    
    func main() {
      err := wrapDo()   // error  (*doError, nil)
      fmt.Println(err == nil) // false
    }
  • 最终的输出结果也是 false。
  • 尽管 wrapDo 函数返回的是 error 类型,但是 do 返回的却是*doError 类型,也就是变成了(*doError,nil),也就和 nil 不相等了。
  • 因此,不要返回具体的错误类型
  • 遵从这两条建议,才能放心地使用 if x != nil。

空结构

type Q struct{}
var q struct{}

var q struct {}
fmt.Println(unsafe.Sizeof(q)) // 0

type Q struct {
    A struct{}
    B struct{
        C struct{}
    }
}
var q Q
fmt.Println(unsafe.Sizeof(q)) // 0


var x [1000000000]struct{}
fmt.Println(unsafe.Sizeof(x)) // 0

var y = make([]struct{},1000000000)
fmt.Println(unsafe.Sizeof(x))// 24,背后关联数组为 0
  • 使用 chan struct{} 代替 chan bool 在 goroutines 之间传递信号。
  • 使用 bool 容易让人不理解该值,true or false,但是使用 chan struct{} 就很清楚,不在乎值,只关心发生的事儿,更容易表达清楚。

(usage:: defer)

  • how
    //demo1 defer 使用实例
    func main() {
        fmt.Println("test1")
        defer fmt.Println("defer")
        fmt.Println("test2")
    }
    
    // 输出
    // test1
    // test2
    // defer

使用规则

  • 一个函数中有多个 defer 时的运行顺序
  • defer 语句执行时的拷贝机制
  • defer 如何影响函数返回值
  • defer 语句中的函数会在 return 语句更新返回值变量后再执行,又因为在函数中定义的匿名函数能访问该函数包括返回值变量在内的所有变量
    //demo3 多个 defer 执行顺序
    func main() {
        fmt.Println("test1")
        defer fmt.Println("defer1")
        fmt.Println("test2")
        defer fmt.Println("defer2")
        fmt.Println("test3")
    }
    // test1
    // test2
    // test3
    // defer2
    // defer1
    
    // demo4 defer 函数在 defer 语句执行那一刻就已经确定
    // I am function test1
    func main() {
        test := func() {
            fmt.Println("I am function test1")
        }
        defer test()
        test = func() {
            fmt.Println("I am function test2")
        }
    }
    
    // demo5 defer 函数参数的值在注册那一刻就已经确定
    // 打印 10
    func f5() {
        x := 10
        defer func(a int) {
            fmt.Println(a)
        }(x)
        x++
    }
    
    
    // demo6 defer 函数传递参数为指针传递
    // 11
    func main() {
        x := 10
        defer func(a *int) {
            fmt.Println(*a)
        }(&x)
        x++
    }
    
    // demo7 defer 延迟函数为闭包
    // 闭包里的变量本质上是对上层变量的引用
    // 11
    func main() {
        x := 10
        defer func() {
            fmt.Println(x)
        }()
        x++
    }
    
    // demo10 defer 函数与非命名返回值之间的关系
    // 返回 10
    func f10() int {
        x := 10
        defer func() {
            x++
        }()
        return x
    }
    
    //demo11 defer 函数与非命名返回值之间的关系
    // 返回 11
    func f11() *int {
        a := 10
        b := &a
        defer func() {
            *b++
        }()
        return b
    }
    func main() {
        fmt.Println("f10", f10())
        fmt.Println("f11", *f11())
    }
    
    
    //拆解 demo10_1 defer 函数与非命名返回值之间的关系,return 拆解
    func f10_1() int {
        x := 10
        defer func() {
            x++
        }()
        //return x => 拆解
        _result := x
        return _result //实际返回的是_result 的值,因此 defer 中修改 x 的值对返回值没有影响
    }
    
    //demo11_1 defer 函数与返回值之间的关系,return 拆解
    func f11_1() *int {
        a := 10
        b := &a
        defer func() {
            *b++
        }()
        //return b => 拆解
        _result := b
        return _result //执行 defer 函数调用*b++, 修改了 b 指向的内存空间的值,实际返回的是 result 指针
    }
    
    //demo13 defer 函数与命名返回值之间的关系
    // 11
    func f13() (result int) {
        defer func() {
            result++
        }()
        return 10
    }
    
    // demo14 defer 函数与命名返回值之间的关系
    // 11
    func f14() (result int) {
        result = 10
        defer func() {
            result++
        }()
        return result
    }
    
    //demo15 defer 函数与命名返回值之间的关系
    // 12
    func f15() (b *int) {
        a := 10
        b = &a
        fmt.Println("b", b)
        defer func() {
            c := 12
            b = &c
            fmt.Println("defer", b)
        }()
        return
    }
    
    func main() {
        fmt.Println("f13", f13())
        fmt.Println("f14", f14())
        t := f15()
        fmt.Println("f15", *t, t)
    }

defer 与 recovery

//demo16 defer 与 recover
// catch error: TEST
// I am ok
func f16() {
    defer func() {
        // 捕获异常
        if err := recover(); err != nil {
            fmt.Println("catch err:", err)
        }
    }()
    panic(errors.New("TEST"))
}
func main() {
    f16()
    fmt.Println("I am OK.")
}

清理资源,避免遗忘

f, err := os.Open(filename)
    if err != nil {
        panic(err)
    }
    defer f.Close()  //释放资源
    /*
        读取和处理文件内容
    */

effective go

  • 格式化

    • 格式化,使用 gofmt(go fmt 命令):
    • 缩进使用 tab 而非空格。
    • 行长度不限,能自己缩进换行。
    • 括号用的比较少:if, for, switch。
  • 注释文档 (godoc/go doc)

    • 包 (package) 注释在文档的最上方。
    • 函数文档在函数头部 (函数首字母大写才能被导出)。
  • Name

    • 包 (package) 名:小写,简洁,单词,无下划线。io.Reader/ring.New
    • Getters/Setters:obj.Owner()/obj.SetOwner(x)
    • 函数/变量:首字母大写才可导出 (export) 被外部模块使用。
    • 驼峰命名法:不使用下划线,如 MixedCaps/mixedCaps。
  • defer

    • 函数退出时调用对应的 call。
    • LIFO。
    • 参数是在执行 defer 语句时确定的,而非对应函数执行时。
  • 分配对象

    • new:返回指针,所有内容为零值。
    • make:返回对象,仅适用于 slices, maps, channels。
  • Array

    • 值对象:赋值是拷贝所有元素。
    • 参数传递是值传递,即函数操作的是一份拷贝,不会影响原有对象。
    • 长度是类型的一部分,[10]int 和[20]int 不是同一个类型。
  • Slices

    • 引用对象:赋值其实指向的同一个 array。
    • 尽量使用 slices 替代 array。
  • Maps

    • 引用对象。
    • v := map[key] 不存在的 key 会赋零值。
    • value, ok := m[key] 不存在的 key ok 为 false。
      删除:delete(m, key)。key 不存在时也是安全的。
  • Printing

    • 带 f 的都有格式化参数,不带的按照类型默认的输出格式输出。
    • 自定义类型%v格式化输出需要实现 String() 方法。
    • 实现 String() 方法尽量防止%s格式的隐形转化,可能会死循环。
    • 可变参数:func foo(args …T)。
    • 切片打散:append(slice1, slice2…)。
  • 常量 (Constants)

    • 编译时确定。
    • 右值如果是表达式,那么因子都必须是常量。
    • iota 的使用。
  • init 函数

    • 每个文件都能含有若干个 init 函数,会在第一次引用的时候执行,且只执行一次
    • 一个文件如果有多个 init 函数,则按上下顺序执行
    • 一个 package 里的多个文件会按文件名的字典顺序执行
    • init 函数优先于 main 函数的自动执行,不能被其他的函数调用
    • init 函数没有输入参数,返回值。
    • 程序初始化的顺序 package > const > var > init() > main() 函数
  • 值 (values) 和指针 (pointers)

    • 值方法 (method) 能被值类型或者指针类型调用。
    • 指针方法只能被指针类型调用。
    • 指针方法能改变结构的内容,值方法不行。
    • 优先声明 methods on *T unless you have a strong reason to do otherwise.
  • 接口 (interfaces)

    • 含义:如果 X 能做这件事 (实现了这些方法),那么 X 就能在这用。
    • 几乎所有类型都能增加方法,也即能实现接口,如:内置类型,通道,函数等。
    • 原则:如果一个类型设计上仅需要实现一个接口,´那么不应该暴露自己的类型给外部,而是暴露接口的类型
      • 举例:hash 算法很多,不同的 hash 算法类型构造器应返回接口类型,这样替换 hash 算法时只需要修改一处构造代码)。
  • 类型转换

    • 获得值的类型:typeName := value.(type),常用语 switch 语句。
    • 类型转换:v, ok := value.(typeName)。没有 ok 参数失败会出现运行时错误。
  • 嵌入

    • struct/interface都能使用。
    • 不带有变量名,仅含有类型
    • 初始化:和正常一样初始化。T{v1, v2, v3…}。
    • 引用:直接操作类型 (job.Logger.Printf,其中 Logger 是类型)。
    • 好处:和继承类似,上层类型能直接调用下层类型的方法。
    • 坏处:名字冲突:上层的名字优先级高于下层。
    • 结构体中的嵌入是组合的
      type Point struct{ X, Y float64 }
      
      type ColoredPoint struct {
          Point
          Color color.RGBA
      }
      
      red := color.RGBA{255, 0, 0, 255}
      blue := color.RGBA{0, 0, 255, 255}
      var p = ColoredPoint{Point{1, 1}, red}
      var q = ColoredPoint{Point{5, 4}, blue}
      fmt.Println(p.Distance(q.Point)) // "5"
      p.ScaleBy(2)
      q.ScaleBy(2)
      fmt.Println(p.Distance(q.Point)) // "10"
      p.Distance(q) // compile error: cannot use q (ColoredPoint) as Point
    • ColoredPoint 看作其子类或者继承类,或者将 ColoredPoint 看作"is a" Point 类型,这是错误的

并发

  • 不要通过共享内存来交互通过交互来共享内存。

  • 尽量使用 channel 通信。

  • Goroutines

    • 多个协程共享同一地址空间。
    • 轻量级的,比栈消耗资源多点。
    • 有限使用栈,栈内存会随需要增大,如果有需要则会使用堆。
    • 协程 + 多线程(具体调度较复杂)。
  • Channels

    • make 关键字创建。
    • 引用类型。
    • 第二个整形参数代表 bufferk 大小,默认 0。ci := make(chan int) cj := make(chan int, 3)。
    • 用途:a) 消息队列。b) 协程间数据的同步交互。c) 并发控制。
  • 并行 (Parallelization)

    • CPU 核心数:runtime.NumCPU()
    • go 能使用的最大核心数:runtime.GOMAXPROCS,默认值为 runtime.NumCPU。

Error & Panic

  • Error:可恢复错误,实现 Error 接口。
  • Panic:不可恢复错误。一般初始化失败的时候使用。
  • Panic 发生时会根据调用栈一层层退出,但会执行 defer 调用。

Recover

  • 多协程时,一个协程出现 panic 异常退出希望不会影响到其他的协程。
  • panic 能被捕获,防止整个进程退出。
  • panic 发生时会执行 defer,所以只能在 defer 中去捕获 panic。
  • 捕获 panic 仅仅在 defer 代码中调用 recover() 语句即可。
  • 在非 defer 语句中调用 recover() 会返回 nil。
  • defer 能修改命名返回值,所以能在 defer 中重制命名返回值。

interface

  • why interface
    • writing generic algorithm
    • hiding implementation detail
    • providing interception points
      // 实现泛型编程,第一个优点
      func sayHello(ig IGreeting) {
          ig.sayHello()
      }
      
      golang := Go{}
      php := PHP{}
      
      // golang 与 php 实现了 IGreeting 接口,能直接使用 sayHello 函数,即"泛型编程"
      sayHello(golang)
      sayHello(php)
      
      
      // 通过返回接口,来隐藏实现细节
      func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
          c := newCancelCtx(parent)
          propagateCancel(parent, &c)
          return &c, func() { c.cancel(true, Canceled) }
      }
      func WithCancel(parent Context) (ctx Context, cancel CancelFunc)    //返回 cancelCtx
      func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc) //返回 timerCtx
      func WithValue(parent Context, key, val interface{}) Context    //返回 valueCtx
      
      // 通过 interface,能通过类似装饰器方式实现 dynamic dispatch
      type header struct {
          rt  http.RoundTripper
          v   map[string]string
      }
      
      func (h header) RoundTrip(r *http.Request) *http.Response {
          for k, v := range h.v {
              r.Header.Set(k,v)
          }
          return h.rt.RoundTrip(r)
      }

how interface

  • 接口嵌套
    • 接口类型间能组合使用
    • 接口类型间的嵌入不会涉及方法间的”屏蔽”,只要方法同名就会产生冲突,并无法通过编译
        type Animal interface {
             // ScientificName 用于获取动物的学名。
             ScientificName() string
             // Category 用于获取动物的基本分类。
             Category() string
         }
      
         type Named interface {
             // Name 用于获取名字。
             Name() string
         }
      
         //包含 Animal 接口与 Named 接口
         type Pet interface {
             Animal
             Named
         }

空接口

  • 空接口就是不包含任何方法的接口。因此,所有的类型都实现了空接口
  • []T 不能直接赋值给[]interface{}
    // 所有类型都实现了空接口
    var v1 interface{} = 1
    var v2 interface{} = "abc"
    var v3 interface{} = struct{ X int }{1}
    
    // 如果函数打算接收任何数据类型,则能将参数声明为 interface{}。最典型的例子就是标准库 fmt 包中的 Print 和 Fprint 系列的函数:
    func Fprint(w io.Writer, a ...interface{}) (n int, err error)
    
    // 下述代码在编译时会报错:cannot use t (type []int) as type []interface {} in assignment
    t := []int{1, 2, 3, 4}
    var s []interface{} = t

区分接口存储类型的方法

  • 将函数里 interface{}类型的参数转换为具体的类型的方法有两种:type assert 和 type switch 判断
    Params := make([]interface{}, 3)
    Params[0] = 88                   // 整型
    Params[1] = "go interface type"         // 字符串
    Params[2] = Go{Name: "go"} // 自定义结构体类型
    
    for index, v := range Params {
        if value, ok := v.(int); ok {
        fmt.Printf("[comma_ok]Params[%d] 是 int 类型 ,值:%v\n", index, value)
        }else if value, ok := v.(string); ok{
        fmt.Printf("[comma_ok]Params[%d] 是 string 类型 ,值:%v\n", index, value)
        }else if value, ok := v.(Go); ok{
        fmt.Printf("[comma_ok]Params[%d] 是自定义数据类型 ,值:%v\n", index, value)
        }
    }

接收者为值类型和指针类型

  • 如果实现了接收者是值类型的方法,会隐含地也实现了接收者是指针类型的方法
  • 如果方法的接收者是值类型,无论调用者是对象还是对象指针,修改的都是对象的副本,不影响调用者
  • 如果方法的接收者是指针类型,则调用者修改的是指针指向的对象本身

reflection

时间

  • 一定要使用 time.Timetime.Duration 两个类型
  • 在命令行上,flag 通过 time.ParseDuration 支持了 time.Duration
  • JSon 中的 encoding/json 中也能把time.Time 编码成 RFC 3339 的格式
  • 数据库使用的 database/sql 也支持把 DATATIMETIMESTAMP 类型转成 time.Time
  • YAML 你能使用 gopkg.in/yaml.v2 也支持 time.Timetime.DurationRFC 3339 格式

逃逸

  • If it could possibly be referenced after the function returns
  • When a value is too big for the stack
  • When the compiler doesn’t know the size in compile times
  • 逃逸分析使用下面的
    go build -gcflags="-m -l" program.go
    

性能 tips

  • 如果需要把数字转字符串,使用 strconv.Itoa() 会比 fmt.Sprintf() 要快一倍左右
  • 尽可能地避免把String转成[]Byte 。这个转换会导致性能下降。
  • 如果在 for-loop 里对某个 slice 使用 append()请先把 slice 的容量很扩充到位,这样能避免内存重新分享以及系统自动按 2 的 N 次方幂进行扩展但又用不到,从而浪费内存。
  • 使用StringBuffer 或是StringBuild 来拼接字符串,会比使用 ++= 性能高三到四个数量级。
  • 尽可能的使用并发的 go routine,然后使用 sync.WaitGroup 来同步分片操作
  • 避免在热代码中进行内存分配,这样会导致 gc 很忙。尽可能的使用 sync.Pool 来重用对象。
  • 使用 lock-free 的操作,避免使用 mutex,尽可能使用 sync/Atomic包。
  • 使用 I/O 缓冲,I/O 是个非常非常慢的操作,使用 bufio.NewWrite()bufio.NewReader() 能带来更高的性能。
  • 对于在 for-loop 里的固定的正则表达式,一定要使用 regexp.Compile() 编译正则表达式。性能会得升两个数量级。
  • 如果你需要更高性能的协议,你要考虑使用 protobuf 或 msgp 而不是 JSON,因为 JSON 的序列化和反序列化里使用了反射。
  • 你在使用 map 的时候,使用整型的 key 会比字符串的要快,因为整型比较比字符串比较要快。

builder 模式

  • 例子
    // 使用一个 builder 类来做包装
    type ServerBuilder struct {
      Server
    }
    
    func (sb *ServerBuilder) Create(addr string, port int) *ServerBuilder {
      sb.Server.Addr = addr
      sb.Server.Port = port
      //其它代码设置其它成员的默认值
      return sb
    }
    
    func (sb *ServerBuilder) WithProtocol(protocol string) *ServerBuilder {
      sb.Server.Protocol = protocol
      return sb
    }
    
    func (sb *ServerBuilder) WithMaxConn( maxconn int) \*ServerBuilder {
      sb.Server.MaxConns = maxconn
      return sb
    }
    
    func (sb *ServerBuilder) WithTimeOut( timeout time.Duration) \*ServerBuilder {
      sb.Server.Timeout = timeout
      return sb
    }
    
    func (sb *ServerBuilder) WithTLS( tls \*tls.Config) \*ServerBuilder {
      sb.Server.TLS = tls
      return sb
    }
    
    func (sb *ServerBuilder) Build() (Server) {
      return  sb.Server
    }
  • 使用 functional options
    type Option func(*Server)
    func Protocol(p string) Option {
        return func(s *Server) {
            s.Protocol = p
        }
    }
    func Timeout(timeout time.Duration) Option {
        return func(s *Server) {
            s.Timeout = timeout
        }
    }
    func MaxConns(maxconns int) Option {
        return func(s *Server) {
            s.MaxConns = maxconns
        }
    }
    func TLS(tls *tls.Config) Option {
        return func(s *Server) {
            s.TLS = tls
        }
    }
    func NewServer(addr string, port int, options ...func(\*Server)) (\*Server, error) {
    
      srv := Server{
        Addr:     addr,
        Port:     port,
        Protocol: "tcp",
        Timeout:  30 \* time.Second,
        MaxConns: 1000,
        TLS:      nil,
      }
      for \_, option := range options {
        option(&srv)
      }
      //...
      return &srv, nil
    }

unsafe.Pointer 和 uintptr 类型

  • 类似于 C 语言中的 void* ,定位和读写内存的基础,runtime 大量使用,4 个特点
    • 任何类型的指针都能转换成 Pointer
    • Pointer 能转换成任何类型的指针
    • uintptr 能转换成 Pointer
    • Pointer 能转换成 uinptr 类型
  • 具体见,unsafe package - unsafe - pkg.go.dev
  • Conversion of a *T1 to Pointer to *T2.
    func Float64bits(f float64) uint64 {
        return *(*uint64)(unsafe.Pointer(&f))
    }
  • conversion of a Pointer to a uintptr (but not back to Pointer)
    • Converting a Pointer to a uintptr produces the memory address of the value pointed at, as an integer. The usual use for such a uintptr is to print it.
    • Conversion of a uintptr back to Pointer is not valid in general.
  • Conversion of a Pointer to a uintptr and back, with arithmetic.
    p = unsafe.Pointer(uintptr(p) + offset)
    
    // equivalent to f := unsafe.Pointer(&s.f)
    f := unsafe.Pointer(uintptr(unsafe.Pointer(&s)) + unsafe.Offsetof(s.f))
    
    // equivalent to e := unsafe.Pointer(&x[i])
    e := unsafe.Pointer(uintptr(unsafe.Pointer(&x[0])) + i*unsafe.Sizeof(x[0]))
  • Unlike in C, it is not valid to advance a pointer just beyond the end of its original allocation:
    // INVALID: end points outside allocated space.
    var s thing
    end = unsafe.Pointer(uintptr(unsafe.Pointer(&s)) + unsafe.Sizeof(s))
    
    // INVALID: end points outside allocated space.
    b := make([]byte, n)
    end = unsafe.Pointer(uintptr(unsafe.Pointer(&b[0])) + uintptr(n))
  • Conversion of a Pointer to a uintptr when calling syscall.Syscall.
    // valid
    syscall.Syscall(SYS_READ, uintptr(fd), uintptr(unsafe.Pointer(p)), uintptr(n))
    // not valid
    // INVALID: uintptr cannot be stored in variable
    // before implicit conversion back to Pointer during system call.
    u := uintptr(unsafe.Pointer(p))
    syscall.Syscall(SYS_READ, uintptr(fd), u, uintptr(n))
  • 特点
    • 和普通类型的指针一样,支持比较,但是不支持解引用,支持与 nil 比较。
      //通过将 float64 类型指针转化为 uint64 类型指针,能查看一个浮点数变量的位模式。
      func Float64bits(f float64) uint64 {
         fmt.Println(reflect.TypeOf(unsafe.Pointer(&f)))  //unsafe.Pointer
         fmt.Println(reflect.TypeOf((*uint64)(unsafe.Pointer(&f))))  //*uint64
         return *(*uint64)(unsafe.Pointer(&f))
      }

#type/golang #public

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant