go 程序设计语言6-13
【第6章-方法】
type Point struct{X,Y float64}
// 普通函数
func Distance(p,q Point) float64{
return math.Hypot(q.X - p,X, q.Y-p.Y)
}
// Point 类型方法
func (p Point) Distance(p,q Point) float64{
return math.Hypot(q.X - p,X, q.Y-p.Y)
}
接收者的名称使用类型的首字母 小写,如: Point --> p
p := Point{1,2}
q := Point{4,6}
fmt.Println(Distance(p,q)) // 函数调用
fmt.Println(p.Distance(q)) // 方法调用
同一类型拥有的所有方法名都必须是唯一的,但不同的类型可以有相同的方法名
在包外,使用函数 包名.函数名 使用方法,变量名.方法 --> 这样调用,方法比函数更简短
[指针接收者的方法]
1. 可以修改接收者本身
2. 不允许本身是指针的类型进行方法声明:
type P *int
func (P) F(){...} --> 编译错误:非法的接收者类型
func(p *Point) ScaleBy(factor float64){
...
}
p:=Point{1,2}
p.ScaleBy(2) --> 此处编译器将p 隐式转换为 &p
不能够对一个不能取地址的 Point 接收者参数调用 *Point方法,因为无法获取临时变量的地址
Point{1,2}.ScaleBy(2) --> 编译错误:不能获得Point类型字面量的地址
如果 ScaleBy 函数为值接收者函数,则不会有错
nil 可以匹配 值接收\指针接收 的方法,nil可以认为是空指针,也可以认为是一个值,这个比较全能
引用的方法一般是值类型,方法在引用类型上做的改变,都不会在调用者身上产生作用,这里注意,
比如你的引用类型是 map, 你可以修改他的键值对,但是你设置他指向一个新的 map,这种操作不会修改我当前的变量的
[6.3 通过结构体内嵌组成类型]
type Point struct{X,Y float64}
type ColoredPoint struct{
Point
Color color.RGBA
}
var p = ColoredPoint{Point{1,1},red}
p 调用 Point的方法:
p.ScaleBy(2)
var q = ColoredPoint{Point{1,1},red}
p.Distance(q.Point) --> 当组合类作为参数时,必须明确调用
【规则】组合类型查找方法时,从自身-->子元素-->子子元素 ... 依次去找,如果同级相同的方法,编译器会报告 - 选择子不明确- 的错误
// 方法只能在命名的类型(比如 Point) 和指向它们的指针(*Point)中声明,但内嵌帮助我们能够在 未命名的 结构体类型中声明方法,如下:
var cache = struct{
sync.Mutex
mapping map[string]string
}{
mapping:make(map[string]string),
}
func Lookup(key string)string{
cache.Lock()
v := cache.mapping[key]
cache.Unlock()
return v
}
[方法变量与表达式]
p := Point{1,2}
scaleP := p.ScaleBy --> 方法变量
scaleP(2) <==> p.ScaleBy(2)
scale := (*Point).ScaleBy --> 方法表达式
scale(&p,2)
[封装]
如果变量或者方法不能通过对象访问到,这种称作封装的变量或方法,也叫 数据隐藏
要封装一个对象,必须使用结构体
GO 的可见性使用首字母的大小写决定的,大写包外可见,小写包外不可见,对于包内,任何全局变量、结构体成员 都是可见的
【第七章 接口】
接口类型 没有暴露所含的数据的布局或者内部结构,也没提供具体操作,他只提供方法名,所以它是一种抽象类型
接口赋值原则: 实现该接口的方法,则可把变量赋值给该接口
type IntSet struct{...}
func (*IntSet)String()string
var _= IntSet{}.String()
var s IntSet
var _=s.String() --> ok,s 是一个变量,&s有String方法
var _ fmt.Stringer = &s -->ok
var _ fmt.Stringer = s -->编译错误:IntSet 缺少 String方法
正如信封封装了信件,接口也封装了所对应的类型和数据,只有通过接口暴露的方法才可以调用,类型的其他方法则无法通过接口来调用
os.Stdout.Write([]byte("hello")) --> ok,*os.File 有 Write 方法
os.Stdout.Close()
var w io.Writer
w = os.Stdout
w.Write([]byte("hello")) --> ok: io.Writer 有 Write 方法
w.Close() --> 编译错误,io.Writer缺少 Close 方法
[空接口类型]
interface{} <==> c++ 的 void 类型
var any interface{}
any = true
[接口共性抽取]
如果两个接口有相同的方法,可以定义一个新的接口来呈现他们的共性,而不用修改现有的定义,如下:
type A interface{
a()
b() int
c() string
}
type A interface{
a()
b() int
e() string
}
--> 抽取共性
type C interface{
a()
b() int
}
[使用 flag.Value 来解析参数]
var period = flag.Duration("period", 1*time.Second, "sleep period")
func main(){
flag.Parse()
fmt.Printf("sleeping for %v ...", *period)
time.Sleep(*period)
fmt.Println()
}
[接口值]
接口值有两部分: 1 动态类型 t 2 动态值 v
var w io.Writer // w.t == nil w.v == nil
w = os.Stdout // w.t == *os.File w.v == fd int=1(stdout) fmt.Printf("%T\n",w) --> *os.File
w = new(bytes.Buffer) // w.t == *bytes.Buffer w.v == data []byte
w = nil // w.t == nil w.v == nil
因为接口值是可比较的,所以它们可以作为 map 的键,也可以作为switch语句的操作数
但是如果两个接口值的动态类型一致,但对应的动态值是不可比较的,会崩溃,如下:
var x interface{} = []int{1,2,3}
x==x --> 宕机;类似的 map slice 函数 都会引起崩溃
[含有空指针的非空接口]
1 动态类型存在 2 动态值为 nil
一些接口要求方法接收者不能为空,当使用该类型接口调用函数时,引发崩溃
[7.6 使用 sort.interface 来排序]
go 内置的排序包,只要实现sort.interface接口,就可实现排序
[http.Handler 接口]
net/http 包提供一个全局的ServeMux 实例 DefaultServeMux, 以及包级别的注册函数 http.Handle 和 http.HandleFunc
要让 DefaultServeMux 作为服务器的主处理程序,无须把他传给 ListenAndServer,直接传 nil 即可。
func main(){
db := dtatabase{"A":50, "B":30}
http.HandleFunc("/list",db.list)
http.HandleFunc("/price",db.price)
log.Fatal(http.ListenAndServer("localhost:8000",nil))
[error接口]
type error interface{
Error() string
}
完整的 error 包的代码:
package errors
func New(text string) error {return &errorString{text}}
type errorString struct{text string}
func (e *errorString) Error() string {return e.text}
// error 格式化函数
func Errorf(format string, args ...interface{}) error{
return errors.New(Sprintf(format, args...))
}
[7.10 类型断言]
类型断言 是作用在 接口值上的操作 --> x.(T),如下:
var w io.Writer = os.Stdout
f,ok := w.(*os.File) // 成功: ok, f == os.Stdout
b,ok := w.(*bytes.Buffer) // 失败: !ok,b == nil
{注意:}返回值的名字与操作数变量名字一样,原有的值就会被 返回值 覆盖,如下:
if w,ok := w.(*os.File);ok{
// ... use w ...
}
[7.11 使用类型断言来识别错误]
package os
type PathError struct{
Op string
Path string
Err error
}
func (e *PathError) Error() string{
return e.Op + " " + e.Path + ": " + e.Err.Error()
}
示例:
import (
"errors"
"syscall"
)
var ErrNotExist = errors.New("file does not exist")
func IsNotExist(err error) bool{
if pe,ok := err.(*PathError);ok{
err = pe.Err
}
return err == syscall.ENOENT || err == ErrNotExist
}
解释: pe.Err 的实际类型是 Errorno 类型, 而 syscall.ENOENT 实际类型也是 Errorno 类型,所以二者可比较
测试代码如下:
_, err := os.Open("/no/such/file")
fmt.Println(os.IsNotExist(err)) --> true 因为 pe.Err 的实际值为 0x2, 而传入参数err 的实际值也是 0x2, 故相等
[7.12 通过接口类型断言来查询特性]
// writeString 将s 写入 w , 如果 w有 WriteStirng 方法,那么将直接调用该方法
func writeString(w io.Writer, s string) (n int, err error){
type stringWriter interface{
WriteSring(string)(n int, err error)
}
if sw,ok := w.(stringWriter); ok{
return sw.WriteSring(s) --> 避免内存复制
}
return w.Write([]byte(s)) --> 分配临时内存
}
[7.13 类型分支]
switch x.(type){
case nil:
case int,uint:
case bool:
case string:
default:
}
default 分支的位置是无关紧要,类型分支中,不允许使用 fallthrough 关键词
[7.15 接口建议]
仅在有两个或者多个具体类型需要按统一的方式处理时才需要接口
特例:
如果接口和类型实现出于依赖的原因不能放在同一个包里,一个接口可对应一个实现,该情况下,接口是解耦两个包的好方式
【第8章 goroutine 和 chan】
chan 支持 通信顺序进程(communicating sequential process, csp), csp 是一个并发的模式,在不同的执行体之间传递值
[8.1 goroutine]
当一个程序启动时,只有一个 goroutine 来调用 main 函数,该goroutine称为主 goroutine
killall 命令是 unix 的一个实用程序,用来终止所有指定名字的进程
func handleConn(c net.Conn){
input := bufio.NewScanner(c)
for input.Scan(){
echo(c, input.Text(), 1*time.Second)
}
// 注意: 忽略 input.Err() 中可能的错误
c.Close()
}
[8.4 通道]
ch := make(chan int) --> ch 的类型是 chan int --> int 称为 通道的元素类型
make 创建的数据类型是引用,通道也是一样,零值为nil
ch <- x --> 发送语句
x =<- ch --> 赋值语句中的接收表达式
<- ch --> 接收语句,丢弃结果
close(ch) --> 关闭通道
ch <- x --> 关闭通道,发送语句崩溃
x =<- ch --> 关闭通道,赋值语句中的接收表达式获取剩余的值,直到通道为空
这时任何接收操作会立即完成,同时获取到一个通道元素类型对应的零值
[无缓冲区通道]
无缓冲区通道 也称为同步通道,当一个值在无缓冲区通道上传递时,接收值后,发送发goroutine才被再次唤醒,
接收时,如果没数据,接收goroutine被阻塞,当chan有数据后,该goroutine才被唤醒
func main(){
conn,err := net.Dial("tcp","localhost:8000")
if err != nil{
log.Fatal(err)
}
done := make(chan struct{})
go func(){
io.Copy(os.Stdout,conn) // 注意:忽略错误
log.Println("done")
done <- struct{}{} // 指示主 goroutine,此处的 chan 作为一个事件 来 使用
}()
mustCopy(conn,os.Stdin)
conn.Close()
<- done
}
[管道]
ch := make(chan int, 100) // 缓冲通道,内部实现
for x:= range ch{
...
}
上面的range 中,只有调用 close(ch) 后,range才退出for...range
close(ch)
chose(ch)--> 试图关闭一个已经关闭的管道会导致宕机
chan <- int 发送管道 <- chan int 接收管道 【记忆点: <-为主体流向,<- 在chan 的右面 data chan<- int,data主体流向chan, 表示数据发送出去,<- 在 chan 的左面 data <-chan int,chan 流向 data主体,表示接收数据 】
单向通道类型一般用作函数的形参
func f(out chan<- int){ // 发送管道
...
}
func f(in <-chan int){ // 接收管道
...
}
ch := make(chan int, 3)
f(ch) --> 编译器隐式转换为单向通道
通道是满的,发送会阻塞 / 通道为空,接收发生阻塞
ch := make(chan int)
ch<- 5 // 发生阻塞
x :<- ch // 接收的数据5
x :<- ch // 通道中无数据,发生阻塞
管道一般不要当做队列来使用,单goroutine中使用,数据填充满,goroutine发生阻塞,就卡死在那里了
goroutine 内部,使用slice 作为队列即可
[并行循环]
参考 https://zhuanlan.zhihu.com/p/75441551
var wg sync.WaitGroup sync.WaitGroup可以解决同步阻塞等待的问题。一个人等待一堆人干完活的问题得到优雅解决。
workers := 3
ch := make(chan struct{})
worker := func() {
// 干活干活干活
ch <- struct{}{} // 通知管理者
}
leader := func() {
cnt := 0
for range ch {
cnt++
if cnt == workers {
break
}
}
close(ch)
// 检查工作成果
}
go leader()
for i := 0; i < workers; i++ {
go worker()
}
<======>
改成sync.Waitgroup实现同样的功能就成这样子
wg := sync.WaitGroup{}
workers := 3
wg.Add(workers)
worker := func() {
defer wg.Done()
// 干活干活干活
}
leader := func() {
wg.Wait()
// 检查工作成果
}
go leader()
for i := 0; i < workers; i++ {
go worker()
}
[使用 select 多路复用]
ch := make(chan int, 1)
for i:=0; i < 10; i++{
select{
case x:= <- ch:
fmt.Println(x)
case ch <- i:
dootherthing()
//default:
// default来制定在没有其他的通信发生时可以立即执行的动作,该动作称为-----> 对通道轮询
}
}
【注意】因为是有缓冲区的通道,会有多个情况同时满足,select 随机选择一个,这样保证每一个通道有相同的机会被选中,
上面的例子中,通道即不空也不满的情况下,相当于select 语句在扔硬币做选择。
[time.Tick函数的使用]
ticker := time.NewTicker(1 * time.Second)
<- ticker.C // 从 ticker 的通道接收
ticker.Stop() // 造成ticker的goroutine终止
【第9章 使用共享变量实现并发】
go 箴言--> 不要通过共享内存来通讯,而应该通过通信来共享内存
9.2 互斥锁: sync.Mutex
sync.Mutex 一个计数上线为1的信号量,称为二进制信号量
在处理并发程序时,永远应当优先考虑清晰度,并且拒绝过早优化,在可以使用的地方,就尽量使用 defer 来让临界区域扩展到函数结尾处
9.3 读写互斥锁 sync.RWMutex
sync.RWMutex 也称为多读单写锁
读锁/共享锁 RLock / RUnlock 获取、释放
写锁/互斥锁 Lock / Unlock 获取、释放
9.4 内存同步/内存屏障
在可能的情况下,把变量限制到单个 goroutine 中,对于其他变量,使用互斥锁
9.5 延迟初始化 sync.Once
var loadIconOnce sync.Once
var icons map[string]image.Image
func Icon(name string)image.Image{
loadIconsOnce.Do(loadIcons)
return icons[name]
}
func loadIcons(){
icons = map[string]image.Image{
"a.png":loadIcon("a.png"),
"b.png":loadIcon("b.png"),
"b.png":loadIcon("b.png"),
"b.png":loadIcon("b.png"),
}
}
9.6 竞态检测器
把 -race 命令行 参数加到 go build、 go run、 go test 命令里边,程序运行时,自带 竞态检测器
【补】channel 要点:
1. 值为nil 的var ch chan --> close(ch) 引起 panic
2. close 已经close 的chan --> 引起 panic
3. 向已经 close 的chan 发送数据,引起 panic
4. 对于不再使用的通道不必显式关闭,如果没有goroutine引用这个通道,这个通道就会被垃圾回收
5. 通道和select搭配最佳
6. 如果在两个goroutine 中需要一个双向沟通,考虑使用两个单独的单向通道,这样两个通道就可以通过调用close的方法来告知对方通讯终止
7. nil 通道发送/接收 数据,会一直阻塞
8. 从关闭的通道上读取数据,会立刻返回0值
9. select 从来不会选择阻塞的case,如果select 的所有case都阻塞了,那么当前的goroutine就会阻塞在select 上
9.8 goroutine 与线程
go 调度器使用 GOMAXPROCS参数来确定需要使用多少个OS线程来同时执行go代码:
GOMAXPROCS=1 go run a.go
9.8.4 gotoutine 没有标识
goroutine 不像 os线程那样,没有独特的标识
goroutine 能影响一个函数行为的参数应当是显示指定的
【第10章 包和go工具】
10.1 包的依赖性形成有向无环图,因为没有环,所以包可以独立甚至并行编译
10.3 包声明的3个例外:
不管包的导入路径是什么,如果该包定义一条命令(可执行的go程序),那么它总是使用名称main
目录中可能有一些文件名字以_test.go结尾,包名中会出现以_test结尾。这样一个目录中有两个包,一个普通包,一个外部测试包
有后缀的包名,去掉后缀,"gopkg.in/yaml.v2",实际包名为yaml
10.4 导入声明:
重命名导入:
import(
"crypto/rand"
mrand "math/rand" // 通过指定一个不同的名称 mrand来避免冲突
)
10.5 空导入
import _ "image/png"
用来实现一个编译时的机制,使用空白引用导入额外的包,来开启主程序中可选的特性
10.6 包及其命名
包名尽量短,但不要短到看不出含义来
10.7.1 工作空间的组织
export GOPATH=$HOME/gobook
go get gop.io/...
结构: GOPATH/ src/ gop1.io/ .git ch1/ helloworld/ main.go dup/ main.go golang.org/x/net/ .git/ html/ parse.go node.go ... bin/ helloworld dup pkg/ darwin_amd64/ ...
GOROOT,指定GO发行版的根目录,其中提供所有标准库的包。
GOOS指定目标的操作系统,android、linux、darwin、windows
GOARCH指定目标处理器架构,amd64、386、arm
go env 命令可以查看 GOPATH GOROOT 等环境变量的设置
10.7.2 包的下载
go get 获得包,它支持多个流行的代码托管站点,如 GitHub、Bitbucket、Launchpad
执行 go help importpath 获取go get的更多细节
go get -u 命令获取每个包的最新版
发布精准版本时,需要加一层vendor 目录,构建一个关于所有必需依赖的本地副本,然后非常小心的更新这个副本
10.7.3 包的构建
以下三种形式等价:
1.
cd $GOPATH/src/gop1.io/ch1/helloworld
go build
2.
cd anywhere
go build gop1.io/ch1/helloworld
3.
cd $GOPATH
go build ./src/gop1.io/ch1/helloworld
go build / go install
相同点
都能生成可执行文件
不同点
go build 不能生成包文件, go install 可以生成包文件
go build 生成可执行文件在当前目录下, go install 生成可执行文件在bin目录下($GOPATH/bin)
go build 经常用于编译测试.go install主要用于生产库和工具.
一般构建时使用 go build -i 或者 go install -v
go install 将可执行程序放在 GOBIN【go env 能看到】路径下
[构建标签的特殊注释]
// +build linux darwin
go build 只会在构建Linux 或 Mac OS X 系统应用的时候才会对它进行编译。
// +build ignore
任何时候都不要编译该文件
更多细节可以在 build constraints 节找到
go doc go/build
10.7 包的文档化
[go doc 命令]
go doc time // 查看 time 包
go doc time.Since // 查看 time 包的一个成员
go doc time.Duration.Seconds // 查看time包的一个方法
[godoc 开启服务]
godoc -http:8000
使用浏览器去访问 http://127.0.0.1:8000/pkg 即可查看go文档
加上 -analysis=type 和 -analysis=pointer 标记使文档内容丰富,同时提供源代码的高级静态分析结果
10.7.5 内部包
Go中命名为internal的package,只有该package的父级package才可以访问该package的内容。
两点需要注意:
只有直接父级package可以访问,其他的都不行,再往上的祖先package也不行
父级package也只能访问internal package使用大写暴露出的内容,小写的不行
10.6 包的查询:
go list ...
枚举一个Go工作空间中的所有包或者一个指定的子树中的所有包
root@kong-virtual-machine:~/go/bin# go list github.com/go-delve/delve/pkg/dwarf/reader
github.com/go-delve/delve/pkg/dwarf/reader
root@kong-virtual-machine:~/go/bin# go list github.com/go-delve/delve/pkg/dwarf/reader1
can't load package: package github.com/go-delve/delve/pkg/dwarf/reader1: cannot find package "github.com/go-delve/delve/pkg/dwarf/reader1" in any of:
/usr/local/go/src/github.com/go-delve/delve/pkg/dwarf/reader1 (from $GOROOT)
/root/go/src/github.com/go-delve/delve/pkg/dwarf/reader1 (from $GOPATH)
go list -json xxx 以json格式输出每一个包的完整记录
【第11章 测试】
在一个包目录中,以_test.go 结尾的文件不是 go build 命令编译的目标,而是 go test 编译的目标
在 *_test.go 文件中,特殊函数: 功能测试函数、基准测试函数、示例函数
功能函数 以Test前缀命名的函数,用来检测一些逻辑程序的正确性,结果 PASS 或者 FAIL
基准测试函数 名称以 Benchmark开头,用来测试某些操作的性能,结果为 操作的平均执行时间
示例函数,以Example开头,用来提供机器检查过的文档
11.2 Test函数
func TestSin(t *testing.T){/*...*/}
go test 命令执行测试
-v 可以输出包中每个测试用例的名称和执行时间
-run 的参数时一个正则表达式,它可以使得 go test 只运行那些测试函数名称 匹配给定模式 的 函数
go test -v -run="French|Canal" // page238
11.2.3 白盒测试 -- clear box
不想进行测试的代码,可以放到一个不能导出的函数变量中,测试函数如果不测试该代码,则自己写一个类似的匿名函数替换掉,使用完再替换回来,如下 notifyUser函数变量 :
saved := notifyUser
defer func() { notifyUser = saved }()
notifyUser = func(user, msg string) {
notifiedUser, notifiedMsg = user, msg
}
11.2.4 外部测试包
net/http 包依赖于 低级的 net/url包,如果测试net/url包,测试函数使用了net/http 包,这样就引入了 包循环引用的问题,
解决方法:
将测试文件放到net 下, 包声明为url_test, net/url_test 称为外部测试包
外部测试包需要使用非导出函数,可以这样做:
在包内测试文件 _test.go 中添加一些函数声明,将包内部的功能暴露给外部测试。这些文件也因此为测试提供了包的一个后门,该测试文件命名为 export_test.go
fmt 包中文件 export_test.go的内容如下:
package fmt
var IsSpace=isSpace
该文件的作用只是导出fmt.IsSpace这个函数符号
11.3 测试覆盖率
先生成c.out
root@kong-virtual-machine:~/go/src/gopl.io-master/ch7/eval# go test -run=Coverage -coverprofile=c.out ./
ok gopl.io-master/ch7/eval 0.003s coverage: 63.8% of statements
再生成html文件
go tool cover -html=c.out
11.4 Benchmark函数
go test -bench=.
以上命令测试性能
参数介绍
1. 参数-bench,它指明要测试的函数;点字符意思是测试当前所有以Benchmark为前缀函数
2. 参数-benchmem,性能测试的时候显示测试函数的内存分配大小,内存分配次数的统计信息
3. 参数-count n,运行测试和性能多少此,默认一次
func BenchmarkIsPalindrome(b *testing.B) {
for i := 0; i < b.N; i++ {
IsPalindrome("A man, a plan, a canal: Panama")
}
}
https://blog.csdn.net/yzf279533105/article/details/94016601
b.N 从 1 开始,如果基准测试函数在1秒内就完成 (默认值),则 b.N 增加,并再次运行基准测试函数。b.N 在近似这样的序列中不断增加;1, 2, 3, 5, 10, 20, 30, 50, 100,1000,20000 等等。 基准框架试图变得聪明,如果它看到当b.N较小而且测试很快就完成的时候,它将让序列增加地更快
11.5 性能剖析 252
profile
go test -cpuprofile=cpu.out
go test -blockprofile=block.out
go test -memprofile=mem.out
使用 pprof 工具来分析文件
go tool pprof cpu.out
pprof 可以生成火焰图,需要下载 graphviz 插件
网站搜索【Go pprof和火焰图】https://www.cnblogs.com/linguoguo/p/10375224.html
11.6 Example 函数
func ExampleIsPalindrome(){
fmt.Println(IsPalindrome("abcba"))
fmt.Println((IsPalindrome("abc"))
// 输出:
// true
// false
}
Example函数即 示例函数
作用:
1. 作为文档
ExampleIsPalindrome 与 IsPalindrome 函数的文档显示在一起,同时如果有一个示例函数叫 Example,它就和当前包word 关联在一起
2. 通过 go test 运行的可执行测试,如果一个示例函数最后包含一个类似这样的注释 // 输出: , 测试驱动程序将执行这个函数并且检查输出到终端的内容匹配这个注释中的文本
3.提供手动实验代码
可以直接在网页上上运行go代码??? 这句没理解
【第12章 反射】
reflection
12.2 reflect.Type 和 reflect.Value
反射功能由 reflect 包提供,它定义了两个重要的类型: Type 和 Value
t := reflect.TypeOf(3)
fmt.Println(t.String()) // "int"
fmt.Println(t) // "int"
接口值: 动态类型 + 动态值
即使非导出字段在反射下也是可见的
从一个可寻址的 reflect.Value() 获取变量需要三步:
1. 调用 Addr(),返回一个Value,其中包含一个指向变量的指针
2. 在这个Value上调用Interface(),返回一个包含这个指针的interface{}的值
3. 如果知道变量的类型,使用类型断言把接口内容转换为一个普通指针
之后,就可以通过这个指针来更新变量
x:=2
d := reflect.Valueof(&x).Elem() // d代表变量x
px := d.Addr().Interface().(*int) // 使用了接口的断言函数,直接转换为int型的指针 px := &x
*px = 3
fmt.Println(x) // "3"
还可以直接通过可寻址的 reflect.Value 来更新变量
x:=2
d := reflect.Valueof(&x).Elem() // d代表变量x
d.Set(reflect.Valueof(4))
fmt.Println(x) // "4"
d.Set(reflect.Valueof(nt64(4))) // 崩溃: int64 不可赋值给int
Set()函数的变种:
SetInt、 SetUint、SetSring、SetFloat
var y interface{}
ry :=reflect.Valueof(&y).Elem()
ry.SetInt(2) // 崩溃:在指向接口的Value 上调用SetInt
ry.Set(reflect.ValueOf(3)) // OK y = int(3)
ry.SetString("hello")// 崩溃:在指向接口的Value 上调用SetString
ry.Set((reflect.ValueOf("hello")) // ok, y="hello"
反射可以读取到未导出的结构体字段值,但是不能修改这些值
stdout:=reflect.ValueOf(os.Stdout).Elem() // *os.Stdout, 一个os.File变量
fmt.Println(stdout.Type())
fd := stdout.FieldByName("fd")
fmt.Println(fd.Int()) // "1"
fd.SetInt(2) // 崩溃:未导出字段
反射函数:
CanAddr() 判断字段可寻址
CanSet() 判断字段值可修改
[访问结构体字段标签]
func Unpack(req *http.Request, ptr interface{}) error {
if err := req.ParseForm(); err != nil {
return err
}
// Build map of fields keyed by effective name.
fields := make(map[string]reflect.Value)
v := reflect.ValueOf(ptr).Elem() // the struct variable
for i := 0; i < v.NumField(); i++ {
fieldInfo := v.Type().Field(i) // a reflect.StructField
tag := fieldInfo.Tag // a reflect.StructTag
name := tag.Get("http")
if name == "" {
name = strings.ToLower(fieldInfo.Name)
}
fields[name] = v.Field(i)
}
// Update struct field for each parameter in the request.
for name, values := range req.Form {
f := fields[name]
if !f.IsValid() {
continue // ignore unrecognized HTTP parameters
}
for _, value := range values {
if f.Kind() == reflect.Slice {
elem := reflect.New(f.Type().Elem()).Elem()
if err := populate(elem, value); err != nil {
return fmt.Errorf("%s: %v", name, err)
}
f.Set(reflect.Append(f, elem))
} else {
if err := populate(f, value); err != nil {
return fmt.Errorf("%s: %v", name, err)
}
}
}
}
return nil
}
// search implements the /search URL endpoint.
func search(resp http.ResponseWriter, req *http.Request) {
var data struct {
Labels []string `http:"l"`
MaxResults int `http:"max"`
Exact bool `http:"x"`
}
data.MaxResults = 10 // set default
if err := params.Unpack(req, &data); err != nil {
http.Error(resp, err.Error(), http.StatusBadRequest) // 400
return
}
// ...rest of handler...
fmt.Fprintf(resp, "Search: %+v\n", data)
}
注意: 结构体字段需要大写字母。否则params.Unpack 不成功。
[reflect.Type 来显示一个任意值的类型]
[慎用反射]
1. 反射的代码很脆弱,编译器无法检测出错误,只有运行时才去检测,容易造成崩溃
2. 反射操作无法做静态类型检查,代码难于理解
3. 反射函数会比特定类型优化的函数慢一两个数量级
【注】
reflect.Type 和 reflect.Value 都有一个叫作Method的方法。每个t.method(i)(从 reflect.Type 调用)都会返回一个 reflect.method类型的实例
这个结构类型描述了这个方法的名称和类型。
而每个v.Method(i)(从reflect.Value调用)都会返回一个 reflect.Value, 代表一个方法变量,即一个已经绑定接收者的方法。使用reflect.Value.Call方法可以调用Func类型的方法变量。
// 函数原型
func (v Value) Call(in []Value) []Value
// 函数示例
func prints(i int) string {
fmt.Println("i =", i)
return strconv.Itoa(i)
}
func main() {
fv := reflect.ValueOf(prints)
params := make([]reflect.Value, 1) // 参数
params[0] = reflect.ValueOf(20) // 参数设置为20
rs := fv.Call(params) // rs作为结果接受函数的返回值
fmt.Println("result:", rs[0].Interface().(string)) // 当然也可以直接是 rs[0].Interface()
}
prints:
i = 20
result: 20
【第 13 章 低级编程】
使用 unsafe 包中的内容也无法保证和go 未来的发布版兼容,因为无论是无意还是有意,这个包里面的内容都会依赖一些未知的实现细节,
而它们可能发生未知的变化。
unsafe.Sizeof <==> sizeof
func Alignof(x ArbitraryType) uintptr
Alignof返回一个类型的对齐值,也可以叫做对齐系数或者对齐倍数
https://www.cnblogs.com/-wenli/p/12681044.html
Offsetof函数只适用于struct结构体中的字段相对于结构体的内存位置偏移量。结构体的第一个字段的偏移量都是0
unsafe.Pointer类型与 c的指针类型一样,unsafe.Pointer类型的指针是可比较的并且可以和nil做比较,nil是指针类型的零值。
深度比较:
1.bytes.Equal(a []byte,b []byte) bool
对比a和b的长度和所包含的字节是否相同,一个nil参数与一个空的slice相同。
2.reflect.DeepEqual(x,y interface{}) bool
DeepEqual反馈x和y是否是深等价。具体依据如下
x 和 y 同nil 或者同non-nil
x 和 y 具有相同的长度
x 和 y 指向同一个底层数组所初始化的实体对象。(&x[0] == &y[0])
注意:一个non-nil的空切片和一个nil的切片不是深等价。例如([]byte{} 和[]byte{nil})是非等价的。
其他值:numbers,bools,strings和channels 如果他们使用“==”相等则是深等价的。
【使用cgo 调用 C代码】
与cgo 类似的工具 swig,它提供了更加复杂的特性用来集成 C++的类
如果性能不是很关键,使用go 的包 os/exec 以辅助子进程的方式来调用C程序
如果性能重要,则使用cgo来调用c语言的动态库
// Package bzip provides a writer that uses bzip2 compression (bzip.org).
package bzip
/*
#cgo CFLAGS: -I/usr/include
#cgo LDFLAGS: -L/usr/lib -lbz2
#include <bzlib.h>
#include <stdlib.h>
bz_stream* bz2alloc() { return calloc(1, sizeof(bz_stream)); }
int bz2compress(bz_stream *s, int action,
char *in, unsigned *inlen, char *out, unsigned *outlen);
void bz2free(bz_stream* s) { free(s); }
*/
import "C"
import (
"io"
"unsafe"
)
type writer struct {
w io.Writer // underlying output stream
stream *C.bz_stream
outbuf [64 * 1024]byte
}
// NewWriter returns a writer for bzip2-compressed streams.
func NewWriter(out io.Writer) io.WriteCloser {
const blockSize = 9
const verbosity = 0
const workFactor = 30
w := &writer{w: out, stream: C.bz2alloc()}
C.BZ2_bzCompressInit(w.stream, blockSize, verbosity, workFactor)
return w
}
生如蝼蚁当立鸿鹄之志
命薄似纸应有不屈之心