Go 源码阅读系列是我的源码阅读笔记。因为本人的电脑上 Go 的版本是1.13.4,所以就选择了该版本作为学习的版本。为此我在Github上 Fork 了 Go 的源码,并创建了 study1.13.4 分支,来记录对于源码的个人理解或者说中文注释也行。每当阅读完一个包后都会进行一下小结,就像这篇是对flag包的总结整理。当然在整理的过程中发现Go夜读系列视频,也让我受益颇多。
- 简介
- 文件结构
- 运行测试
- 总结
- 接口转换能实现类似 C++ 中模板的功能
- 函数 vs 方法
href
="ht
tps://zhuanlan.zhihu.com/writenew-vs-make">new vs make- 指针赋值给接口变量
- flag文件夹中有flag_test包
- 作用域
- 后续深入TODO
- 参考文献
简介
flag 包是 Go 里用于解析命令行参数的包。为什么选择它作为第一个阅读的包,因为它的代码量少。其核心代码只有一个 1000 不到的 flag.go 文件。
文件结构
flag 包的文件结构很简单,就一层。一个文件夹里放了 5 个文件,其文件及其作用如下:
- flag.go
flag 的核心包,实现了命令行参数解析的所有功能 * export_test.go
测试的实用工具,定义了所有测试需要的基础变量和函数 * flag_test.go
flag 的测试文件,包含了 17 个测试单元 * example_test.go
flag 的样例文件,介绍了 flag 包的三种常用的用法样例 * example_value_test.go
flag 的样例文件,介绍了一个更复杂的样例
运行测试
我先介绍一下 Go 的运行环境。
通过 brew install go 安装,源码位置为 $GOROOT/srcGOROOT=/usr/local/opt/go/libexec阅读的源码通过 go get -v -d github.com/haojunyu/go 下载,源码位置为 $GOPATH/src/github.comGOPATH=$HOME/go
单独测试 flag 包踩过的坑: 1. 无法针对单个文件进行测试,需要针对包。
这里重点说一下 export_test.go 文件,它是flag包的一部分package flag
,但是它确实专门为测试而存在的,说白了也就一个ResetForTesting
方法,用来清除所有命令参数状态并且直接设置Usage函数。该方法会在测试用例中被频繁使用。所以单独运行以下命令会报错”flag_test.go:30:2: undefined: ResetForTesting”
测试当前目录(报错)gotest-v .测试包gotest-v flag
go test -v flag
测试的源码是$GOROOT/src
下的(以我当前的测试环境)
指定 flag 包后,实际运行的源码是$GOROOT
下的,这个应该和我的安装方式有关系。
总结
接口转换能实现类似 C++ 中模板的功能
flag 包中定义了一个结构体类型叫Flag
,它用来存放一个命令参数,其定义如下。
// A Flag represents the state of a flag.// 结构体Flag表示一个参数的所有信息,包括名称,帮助信息,实际值和默认值typeFlagstruct{Namestring// name as it appears on command line名称Usagestring// help message帮助信息ValueValue// value as set实现了取值/赋值方法的接口DefValuestring// default value (as text); for usage message默认值}
其中命令参数的值是一个Value
接口类型,其定义如下:
// Set is called once, in command line order, for each flag present.// The flag package may call the String method with a zero-valued receiver,// such as a nil pointer.// 接口Value是个接口,在结构体Flag中用来存储每个参数的动态值(参数类型格式各样)typeValueinterface{String()string// 取值方法Set(string)error// 赋值方法}
为什么这么做?因为这样做能够实现类似模板的功能。任何一个类型T
只要实现了Value
接口里的String
和Set
方法,那么该类型T
的变量v
就可以转换成Value
接口类型,并使用String
来取值,使用Set
来赋值。这样就能完美的解决不同类型使用相同的代码操作目的,和 C++ 中的模板有相同的功效。
函数 vs 方法
函数和方法都是一组一起执行一个任务的语句,二者的区别在于调用者不同,函数的调用者是包 package,而方法的调用者是接受者 receiver。在 flag 的源码中,有太多的函数里面只有一行,就是用包里的变量CommandLine
调用同名方法。
// Parsed reports whether f.Parse has been called.// Parsed方法: 命令行参数是否已经解析func(f*FlagSet)Parsed()bool{returnf.parsed}// Parsed reports whether the command-line flags have been parsed.funcParsed()bool{returnCommandLine.Parsed()}
new
vsmake
new
和make
是 Go 语言中两种内存分配原语。二者所做的事情和针对的类型都不一样。new
和其他编程语言中的关键字功能类似,都是向系统申请一段内存空间来存储对应类型的数据,但又有些区别,区别在于它会将该片空间置零。也就是说new(T)
会根据类型T
在堆上 申请一片置零的内存空间,并返回指针*T
。make
只针对切片,映射和信道三种数据类型T
的构建,并返回类型为T
的一个已经初始化(而非零)的值。原因是这三种数据类型都是引用数据类型,在使用前必须初始化。就像切片是一个具有三项内容的描述符,包含一个指向数组的指针,长度和容量。通过make
创建对应类型的变量过程是先分配一段空间,接着根据对应的描述符来创建对应的类型变量。关于make
的细节可以看 draveness 写的Go语言设计与实现。
// Bool defines a bool flag with specified name, default value, and usage string.// The return value is the address of a bool variable that stores the value of the flag.func(f*FlagSet)Bool(namestring,valuebool,usagestring)*bool{p:=new(bool)f.BoolVar(p,name,value,usage)returnp}// sortFlags returns the flags as a slice in lexicographical sorted order.// sortFlags函数:按字典顺序排序命令参数,并返回Flag的切片funcsortFlags(flagsmap[string]*Flag)[]*Flag{result:=make([]*Flag,len(flags))i:=0for_,f:=rangeflags{result[i]=fi++}sort.Slice(result,func(i,jint)bool{returnresult[i].Name<result[j].Name})returnresult}
指针赋值给接口变量
Go 中的接口有两层含义,第一层是一组方法(不是函数)的签名,它需要接受者(具体类型T
或具体类型指针*T
)来实现细节;另一层是一个类型,而该类型能接受所有现实该接受的接受者。深入理解接口的概念可以细读Go语言设计与实现之接口。在 flag 包中的StringVar
方法中newStringValue(value, p)
返回的是*stringValue
类型,而该类型(接受者)实现了Value
接口(String
和Set
方法),此时该类型就可以赋值给Value
接口变量。
// StringVar defines a string flag with specified name, default value, and usage string.// The argument p points to a string variable in which to store the value of the flag.// StringVar方法:将命令行参数的默认值value赋值给变量*p,并生成结构Flag并置于接受者中f.formalfunc(f*FlagSet)StringVar(p*string,namestring,valuestring,usagestring){f.Var(newStringValue(value,p),name,usage)// newStringValue返回值是*stringValue类型,之所以能赋值给Value接口是因为newStringValue实现Value接口时定义的接受者为*stringValue}
flag文件夹中有flag_test
包
flag 文件夹下有flag_test
包,是因为该文件夹下包含了核心代码 flag.go 和测试代码 *_test.go 。这两部分代码并没有通过文件夹来区分。所以该flag_test
包存在的意义是将测试代码与核心代码区分出来。而该包被引用时只会使用到核心代码。
// example_test.gopackageflag_test
作用域
关于作用域Golang变量作用域和GO语言圣经中关于作用域都有了详细的介绍,前者更通俗易懂些,后者更专业些。在 flag 包的TestUsage
测试样例中,因为func(){called=true}
是在函数TestUsage
中定义函数,并且直接作为形参传递给ResetForTesting
函数,所以该函数是和局部变量called
是同级的,当然在该函数中给该变量赋值也是合理的。
// called变量的作用域funcTestUsage(t*testing.T){called:=false// 变量called的作用域ResetForTesting(func(){called=true})ifCommandLine.Parse([]string{"-x"})==nil{t.Error("parse did not fail for unknown flag")}else{t.Error("hahahh")}if!called{t.Error("did not call Usage for unknown flag")}}
后续深入TODO
- [ ] go test 测试原理
- [ ] 接口转换原理
- [ ] 反射
参考文献
- Go 夜读之 flag 包视频
- 实效 Go 编程之内存分配
- Go 语言设计与实现之 make 和 new
- 菜鸟教程之 Go 语言变量作用域
- Go 语言圣经中关于作用域
- Go 语言中值 receiver 和指针 receiver 的对比
- Go CodeReviewComments
- Golang 变量作用域
- Go 语言圣经中关于作用域
- Go 语言设计与实现之接口
如果该文章对您产生了帮助,或者您对技术文章感兴趣,可以关注微信公众号: 技术茶话会, 能够第一时间收到相关的技术文章,谢谢!
本篇文章由一文多发平台ArtiPub自动发布
声明:本文部分素材转载自互联网,如有侵权立即删除 。
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是赞助,收取费用仅维持本站的日常运营所需!
7. 如遇到加密压缩包,请使用WINRAR解压,如遇到无法解压的请联系管理员!
8. 精力有限,不少源码未能详细测试(解密),不能分辨部分源码是病毒还是误报,所以没有进行任何修改,大家使用前请进行甄别
丞旭猿论坛
暂无评论内容