diff --git a/source/c02/c02_01.rst b/source/c02/c02_01.rst index 8335dc842ff4ff5ba3c0b84ef4355c25b2774015..c10805d1436156fdf448adb1ff14c2e48c220751 100644 --- a/source/c02/c02_01.rst +++ b/source/c02/c02_01.rst @@ -6,7 +6,7 @@ 0. 什么是结构体? ----------------- -在之前学过的数据类型中,数组与切片,只能存储同一类型的变量。若要存储多个类型的变量,就需要用到结构体,它是将多个容易类型的命令变量组合在一起的聚合数据类型。 +在之前学过的数据类型中,数组与切片,只能存储同一类型的变量。若要存储多个类型的变量,就需要用到结构体,它是将多个任意类型的变量组合在一起的聚合数据类型。 每个变量都成为该结构体的成员变量。 @@ -102,7 +102,7 @@ self,在方法内可以使用 ``person.属性名`` 的方法来访问实例属 3. 方法的参数传递方式 --------------------- -上面定义方法的方式叫当你想要在方法内改变实例的属性的时候,必须使用指针做为方法的接收者。 +当你想要在方法内改变实例的属性的时候,必须使用指针做为方法的接收者。 .. code:: go @@ -132,7 +132,7 @@ self,在方法内可以使用 ``person.属性名`` 的方法来访问实例属 } 输出结果 如下,可以看到在方法内部对 age 的修改已经生效。你可以尝试去掉 -``*``\ ,使用值做为方法接收者,看看age是否会发生改变。 +``*``\ ,使用值做为方法接收者,看看age是否会发生改变(答案是:不会改变) :: diff --git a/source/c02/c02_10.rst b/source/c02/c02_10.rst index d5a49d818da4518d94cf5ab76b6ad19796852a4e..4db036d19ea48915564ad4d0989d7fd9ad983abf 100644 --- a/source/c02/c02_10.rst +++ b/source/c02/c02_10.rst @@ -50,12 +50,14 @@ 在官方文档中,make 函数的描述如下 - //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. +:: - 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. + + func make(t Type, size ...IntegerType) Type 翻译一下注释内容 @@ -64,7 +66,7 @@ 2. make 返回类型的本身而不是指针,而返回值也依赖于具体传入的类型,因为这三种类型就是引用类型,所以就没有必要返回他们的指针了 -注意,因为这三种类型是引用类型,所以必须得初始化(size和cap),但是不是置为零值,这个和new是不一样的。 +注意,因为这三种类型是引用类型,所以必须得初始化(size和cap),但不是置为零值,这个和new是不一样的。 举几个例子 diff --git a/source/c04/c04_03.md b/source/c04/c04_03.md index 82833b108cc1cd47d01e734e9489210b67cf16af..7a71807421dc5ebeaa94e2797acd08968a583405 100644 --- a/source/c04/c04_03.md +++ b/source/c04/c04_03.md @@ -172,7 +172,7 @@ func main() { fmt.Printf("接收到的数据是: %d", num) }() // 主函数sleep,使得上面两个goroutine有机会执行 - time.Sleep(1) + time.Sleep(time.Second) } ``` @@ -248,7 +248,7 @@ func main() { fmt.Printf("接收到的数据是: %d", num) }() // 主函数sleep,使得上面两个goroutine有机会执行 - time.Sleep(1) + time.Sleep(time.Second) } ``` @@ -297,10 +297,10 @@ func main() { ```go package main -import { +import ( "fmt" "time" -} +) // 由于 x=x+1 不是原子操作 // 所以应避免多个协程对x进行操作 @@ -322,7 +322,7 @@ func main() { // 确保所有的协程都已完成 // 以后会介绍一种更合适的方法(Mutex),这里暂时使用sleep - time.Sleep(3) + time.Sleep(time.Second) fmt.Println("x 的值:", x) } ``` diff --git a/source/c04/c04_03.rst b/source/c04/c04_03.rst index 9dac2707d4a20f2155ff960019a028916d17a29f..4c827d2462ea555d8ca8f45edec63d7b09a28342 100644 --- a/source/c04/c04_03.rst +++ b/source/c04/c04_03.rst @@ -173,7 +173,7 @@ int 等等) fmt.Printf("接收到的数据是: %d", num) }() // 主函数sleep,使得上面两个goroutine有机会执行 - time.Sleep(1) + time.Sleep(time.Second) } **单向信道** @@ -244,7 +244,7 @@ int 等等) fmt.Printf("接收到的数据是: %d", num) }() // 主函数sleep,使得上面两个goroutine有机会执行 - time.Sleep(1) + time.Sleep(time.Second) } 5. 遍历信道 @@ -292,10 +292,10 @@ range关键字,在range时,要确保信道是处于关闭状态,否则循 package main - import { + import ( "fmt" "time" - } + ) // 由于 x=x+1 不是原子操作 // 所以应避免多个协程对x进行操作 @@ -317,7 +317,7 @@ range关键字,在range时,要确保信道是处于关闭状态,否则循 // 确保所有的协程都已完成 // 以后会介绍一种更合适的方法(Mutex),这里暂时使用sleep - time.Sleep(3) + time.Sleep(time.Second) fmt.Println("x 的值:", x) } diff --git a/source/c04/c04_05.rst b/source/c04/c04_05.rst index b15bd33ee2c234d69ee76dc22f29d68e34a2ae84..90d0d9fe153bb699958d0e70a419d53307a810dc 100644 --- a/source/c04/c04_05.rst +++ b/source/c04/c04_05.rst @@ -3,8 +3,8 @@ |image0| -在 「\ `19. 学习 Go -协程:详解信道/通道 `__\ 」这一节里我详细地介绍信道的一些用法,要知道的是在 +在 「\ `4.3 学习 Go +协程:详解信道/通道 `__\ 」这一节里我详细地介绍信道的一些用法,要知道的是在 Go 语言中,信道的地位非常高,它是 first class 级别的,面对并发问题,我们始终应该优先考虑使用信道,如果通过信道解决不了的,不得不使用共享内存来实现并发编程的,那 Golang 中的锁机制,就是你绕不过的知识点了。 diff --git a/source/c05/c05_03.md b/source/c05/c05_03.md index a5636804c3d6af71b9f3289b30336debf890361a..780f890d5c42bc04f4f828ff7f6293364049d857 100644 --- a/source/c05/c05_03.md +++ b/source/c05/c05_03.md @@ -2,7 +2,46 @@ ![](http://image.iswbm.com/20200607145423.png) -## 参数种类 +在 Golang 程序中有很多种方法来处理命令行参数。 + +简单的情况下可以不使用任何库,直接使用 `os.Args` + +```go +package main + +import ( + "fmt" + "os" +) + +func main() { + //os.Args是一个[]string + if len(os.Args) > 0 { + for index, arg := range os.Args { + fmt.Printf("args[%d]=%v\n", index, arg) + } + } +} +``` + +试着运行一下,第一个参数是执行文件的路径。 + +```shell +$ go run demo.go hello world hello golang +args[0]=/var/folders/72/lkr7ltfd27lcf36d75jdyjr40000gp/T/go-build187785213/b001/exe/demo +args[1]=hello +args[2]=world +args[3]=hello +args[4]=golang +``` + +从上面你可以看到,`os.Args` 只能处理简单的参数,而且对于参数的位置有严格的要求。对于一些比较复杂的场景,就需要你自己定义解析规则,非常麻烦。 + +如果真的遇上了所谓的复杂场景,那么还可以使用 Golang 的标准库 flag 包来处理命令行参数。 + +本文将介绍 Golang 标准库中 flag 包的用法。 + +## 1. 参数种类 根据参数是否为布尔型,可以分为两种: @@ -14,7 +53,7 @@ - 长参数:比如 `--name jack` 就是一个长参数,参数名前有两个 `-` - 短参数:通常为一个或两个字母(是对应长参数的简写),比如 `-n` ,参数名前只有一个 `-` -## 入门示例 +## 2. 入门示例 我先用一个字符串类型的参数的示例,抛砖引玉 @@ -45,11 +84,14 @@ flag.Parse() // 解析参数 运行以上程序,输出如下 ```go +$ go run demo.go +jack + $ go run demo.go --name wangbm wangbm ``` -## 改进一下 +## 3. 改进一下 如果你的程序只接收很少的几个参数时,上面那样写也没有什么问题。 @@ -77,7 +119,7 @@ func main(){ } ``` -## 参数类型 +## 4. 参数类型 当你在命令行中指定了参数,Go 如何解析这个参数,转化成何种类型,是需要你事先定义的。 @@ -107,6 +149,7 @@ func main(){ ```shell $ go run main.go false + $ go run main.go --debug true ``` @@ -133,6 +176,7 @@ func main(){ ```shell $ go run main.go 18 + $ go run main.go --age 20 20 ``` @@ -161,6 +205,7 @@ func main(){ ```shell $ go run main.go jack + $ go run main.go --name wangbm wangbm ``` @@ -191,9 +236,13 @@ $ go run main.go --interval 2s 2s ``` -## 自定义类型 +## 5. 自定义类型 + +flag 包支持的类型有 Bool、Duration、Float64、Int、Int64、String、Uint、Uint64。 -另外,还可以创建自定义flag,只要 Var 函数的第一个参数对象实现 flag.Value接口即可 +这些类型的参数被封装到其对应的后端类型中,比如 Int 类型的参数被封装为 intValue,String 类型的参数被封装为 stringValue。 + +这些后端的类型都实现了 flag.Value 接口,因此可以把一个命令行参数抽象为一个 Flag 类型的实例。下面是 Value 接口和 Flag 类型的代码: ```go type Value interface { @@ -201,19 +250,49 @@ type Value interface { Set(string) error } +// Flag 类型 +type Flag struct { + Name string // name as it appears on command line + Usage string // help message + Value Value // value as set 是个 interface,因此可以是不同类型的实例。 + DefValue string // default value (as text); for usage message +} + func Var(value Value, name string, usage string) { CommandLine.Var(value, name, usage) } ``` -假如我想实现这样一个效果 +想要实现自定义类型的参数,其实只要 Var 函数的第一个参数对象实现 flag.Value接口即可 + +```go +type sliceValue []string + + +func newSliceValue(vals []string, p *[]string) *sliceValue { + *p = vals + return (*sliceValue)(p) +} + +func (s *sliceValue) Set(val string) error { + // 如何解析参数值 + *s = sliceValue(strings.Split(val, ",")) + return nil +} + +func (s *sliceValue) String() string { + return strings.Join([]string(*s), ",") +} +``` + +比如我想实现如下效果,传入的参数是一个字符串,以逗号分隔,flag 的解析时将其转成 slice。 ```shell $ go run demo.go -members "Jack,Tom" [Jack Tom] ``` -我可以这样子编写代码 +那我可以这样子编写代码 ```go var members []string @@ -248,9 +327,9 @@ func main(){ 有的朋友 可能会对 `(*sliceValue)(p)` 这行代码有所疑问,这是什么意思呢? -关于这个,其实之前在 【http://golang.iswbm.com/en/latest/c02/c02_09.html#id2】有讲过,忘记了可以前往复习。 +关于这个,其实之前在 【[2.9 详细图解:静态类型与动态类型](http://golang.iswbm.com/en/latest/c02/c02_09.html#id2)】有讲过,忘记了可以前往复习。 -## 长短选项 +## 6. 长短选项 flag 包,在使用上,其实并没有没有长短选项之别,你可以看下面这个例子 @@ -302,6 +381,14 @@ exit status 2 +## 7. 总结一下 + +flag 在绝大多数场景下,它是够用的,但如果要支持更多的命令传入格式,flag 可能并不是最好的选择。 + +那些在标准库不能解决的场景,往往会有相应的Go爱好者提供第三方解决方案。我所了解到的 cobra 就是一个非常不错的库。 + +它能够支持 flag 不能支持的功能,比如 **支持短选项**,**支持子命令** 等等,后面找个机会再好好写一下。 + ## flag 的函数 ### Lookup @@ -314,14 +401,6 @@ m := flag.Lookup("members") -## go-flags - -flag 内置库有一些不足的地方 - -- 不显示支持短选项。 -- 选项变量的定义比较繁琐,每个选项都需要根据类型调用对应的`Type`或`TypeVar`函数; -- 默认只支持有限的数据类型,当前只有基本类型`bool/int/uint/string`和`time.Duration`; - ![](http://image.iswbm.com/20200607174235.png) \ No newline at end of file diff --git a/source/c05/c05_03.rst b/source/c05/c05_03.rst index bae22f9a36fab7aa65955f9cc8ccbf3c4f24c45e..a1cdc151a97a0daad5ef7870228e4890428d61b4 100644 --- a/source/c05/c05_03.rst +++ b/source/c05/c05_03.rst @@ -3,8 +3,49 @@ |image0| -参数种类 --------- +在 Golang 程序中有很多种方法来处理命令行参数。 + +简单的情况下可以不使用任何库,直接使用 ``os.Args`` + +.. code:: go + + package main + + import ( + "fmt" + "os" + ) + + func main() { + //os.Args是一个[]string + if len(os.Args) > 0 { + for index, arg := range os.Args { + fmt.Printf("args[%d]=%v\n", index, arg) + } + } + } + +试着运行一下,第一个参数是执行文件的路径。 + +.. code:: shell + + $ go run demo.go hello world hello golang + args[0]=/var/folders/72/lkr7ltfd27lcf36d75jdyjr40000gp/T/go-build187785213/b001/exe/demo + args[1]=hello + args[2]=world + args[3]=hello + args[4]=golang + +从上面你可以看到,\ ``os.Args`` +只能处理简单的参数,而且对于参数的位置有严格的要求。对于一些比较复杂的场景,就需要你自己定义解析规则,非常麻烦。 + +如果真的遇上了所谓的复杂场景,那么还可以使用 Golang 的标准库 flag +包来处理命令行参数。 + +本文将介绍 Golang 标准库中 flag 包的用法。 + +1. 参数种类 +----------- 根据参数是否为布尔型,可以分为两种: @@ -19,8 +60,8 @@ - 短参数:通常为一个或两个字母(是对应长参数的简写),比如 ``-n`` ,参数名前只有一个 ``-`` -入门示例 --------- +2. 入门示例 +----------- 我先用一个字符串类型的参数的示例,抛砖引玉 @@ -52,11 +93,14 @@ .. code:: go + $ go run demo.go + jack + $ go run demo.go --name wangbm wangbm -改进一下 --------- +3. 改进一下 +----------- 如果你的程序只接收很少的几个参数时,上面那样写也没有什么问题。 @@ -86,8 +130,8 @@ fmt.Println(name) } -参数类型 --------- +4. 参数类型 +----------- 当你在命令行中指定了参数,Go 如何解析这个参数,转化成何种类型,是需要你事先定义的。 @@ -121,6 +165,7 @@ false,你一指定 ``--debug``\ ,debug 为赋值为 true。 $ go run main.go false + $ go run main.go --debug true @@ -148,6 +193,7 @@ false,你一指定 ``--debug``\ ,debug 为赋值为 true。 $ go run main.go 18 + $ go run main.go --age 20 20 @@ -178,6 +224,7 @@ UintVar、Float64Var 方法,也是同理,不再赘述。 $ go run main.go jack + $ go run main.go --name wangbm wangbm @@ -208,11 +255,18 @@ UintVar、Float64Var 方法,也是同理,不再赘述。 $ go run main.go --interval 2s 2s -自定义类型 ----------- +5. 自定义类型 +------------- -另外,还可以创建自定义flag,只要 Var 函数的第一个参数对象实现 -flag.Value接口即可 +flag 包支持的类型有 +Bool、Duration、Float64、Int、Int64、String、Uint、Uint64。 + +这些类型的参数被封装到其对应的后端类型中,比如 Int 类型的参数被封装为 +intValue,String 类型的参数被封装为 stringValue。 + +这些后端的类型都实现了 flag.Value +接口,因此可以把一个命令行参数抽象为一个 Flag 类型的实例。下面是 Value +接口和 Flag 类型的代码: .. code:: go @@ -221,18 +275,50 @@ flag.Value接口即可 Set(string) error } + // Flag 类型 + type Flag struct { + Name string // name as it appears on command line + Usage string // help message + Value Value // value as set 是个 interface,因此可以是不同类型的实例。 + DefValue string // default value (as text); for usage message + } + func Var(value Value, name string, usage string) { CommandLine.Var(value, name, usage) } -假如我想实现这样一个效果 +想要实现自定义类型的参数,其实只要 Var 函数的第一个参数对象实现 +flag.Value接口即可 + +.. code:: go + + type sliceValue []string + + + func newSliceValue(vals []string, p *[]string) *sliceValue { + *p = vals + return (*sliceValue)(p) + } + + func (s *sliceValue) Set(val string) error { + // 如何解析参数值 + *s = sliceValue(strings.Split(val, ",")) + return nil + } + + func (s *sliceValue) String() string { + return strings.Join([]string(*s), ",") + } + +比如我想实现如下效果,传入的参数是一个字符串,以逗号分隔,flag +的解析时将其转成 slice。 .. code:: shell $ go run demo.go -members "Jack,Tom" [Jack Tom] -我可以这样子编写代码 +那我可以这样子编写代码 .. code:: go @@ -268,11 +354,11 @@ flag.Value接口即可 有的朋友 可能会对 ``(*sliceValue)(p)`` 这行代码有所疑问,这是什么意思呢? -关于这个,其实之前在 -【http://golang.iswbm.com/en/latest/c02/c02_09.html#id2】有讲过,忘记了可以前往复习。 +关于这个,其实之前在 【\ `2.9 +详细图解:静态类型与动态类型 `__\ 】有讲过,忘记了可以前往复习。 -长短选项 --------- +6. 长短选项 +----------- flag 包,在使用上,其实并没有没有长短选项之别,你可以看下面这个例子 @@ -322,6 +408,18 @@ flag 包,在使用上,其实并没有没有长短选项之别,你可以看 你的名字 (default "明哥") exit status 2 +7. 总结一下 +----------- + +flag 在绝大多数场景下,它是够用的,但如果要支持更多的命令传入格式,flag +可能并不是最好的选择。 + +那些在标准库不能解决的场景,往往会有相应的Go爱好者提供第三方解决方案。我所了解到的 +cobra 就是一个非常不错的库。 + +它能够支持 flag 不能支持的功能,比如 **支持短选项**\ ,\ **支持子命令** +等等,后面找个机会再好好写一下。 + flag 的函数 ----------- @@ -334,15 +432,6 @@ Lookup m := flag.Lookup("members") -go-flags --------- - -flag 内置库有一些不足的地方 - -- 不显示支持短选项。 -- 选项变量的定义比较繁琐,每个选项都需要根据类型调用对应的\ ``Type``\ 或\ ``TypeVar``\ 函数; -- 默认只支持有限的数据类型,当前只有基本类型\ ``bool/int/uint/string``\ 和\ ``time.Duration``\ ; - |image1| .. |image0| image:: http://image.iswbm.com/20200607145423.png