From 141501aa3740abca9107b20d7f52f731ea2a3d9e Mon Sep 17 00:00:00 2001 From: Oling Cat Date: Thu, 20 Sep 2012 22:09:31 +0800 Subject: [PATCH] Modified some sentence. --- 2.1.md | 28 +++--- 2.2.md | 264 +++++++++++++++++++++++++++++---------------------------- 2.3.md | 216 +++++++++++++++++++++++----------------------- 2.md | 4 +- 4 files changed, 257 insertions(+), 255 deletions(-) diff --git a/2.1.md b/2.1.md index e935fece..9ec9c741 100644 --- a/2.1.md +++ b/2.1.md @@ -1,50 +1,50 @@ #2.1 你好,Go -在我们开始用Go写应用之前,我们先从最基本的程序开始。就像你造房子之前你不知道什么是地基一样,因此,在本小节中,我们要学习用最基本的语法让Go程序运行起来。 +在开始编写应用之前,我们先从最基本的程序开始。就像你造房子之前不知道什么是地基一样,编写程序也不知道如何开始。因此,在本节中,我们要学习用最基本的语法让Go程序运行起来。 ##程序 -这就像一个传统,学习所有的语言,你应该学习编写的第一个程序就是如何输出`hello world` +这就像一个传统,在学习大部分语言之前,你先学会如何编写一个可以输出`hello world`的程序。 -准备好了吗?Let's Go +准备好了吗?Let's Go! package main import "fmt" func main() { - fmt.Printf("Hello, world; καλημ ́ρα κóσμ or こんにちは世界\n") + fmt.Printf("Hello, world or καλημ ́ρα κóσμ or こんにちは世界\n") } 输出如下: - Hello, world; καλημ ́ρα κóσμ or こんにちは世界 + Hello, world or καλημ ́ρα κóσμ or こんにちは世界 ##详解 首先我们要了解一个概念,Go程序是通过`package`来组织的 -`package `(在我们的例子里面,``是`main`)这一句话是告诉我们当前这个文件属于哪个包,而`main`包是告诉我们当前这个包是一个可独立运行的包,编译之后是可执行文件。除了`main`之外,其它的包最后生成的都是放在`$GOPATH/pkg/$GOOS_$GOARCH`(以Mac为例:darwin_amd64)下面的.a文件(也就是包文件)。 +`package `(在我们的例子中是`package main`)这一行告诉我们当前文件属于哪个包,而包名`main`则告诉我们它是一个可独立运行的包,它在编译后会产生可执行文件。除了`main`包之外,其它的包最后都会生成`*.a`文件(也就是包文件)并放置在`$GOPATH/pkg/$GOOS_$GOARCH`中(以Mac为例就是`$GOPATH/pkg/darwin_amd64`)。 ->每一个可独立运行的Go程序,必定包含一个`package main`,这个`main`包里面必定包含一个入口函数`main`函数,而这个函数没有任何参数,也没有返回值。 +>每一个可独立运行的Go程序,必定包含一个`package main`,在这个`main`包中必定包含一个入口函数`main`,而这个函数既没有参数,也没有返回值。 -为了打印“Hello, world...”,我们调用了一个函数`Printf`,这个函数来自于`fmt`这个包,所以我们在第三行里面导入了系统级别的`fmt`包,`import "fmt"`。 +为了打印`Hello, world...`,我们调用了一个函数`Printf`,这个函数来自于`fmt`包,所以我们在第三行中导入了系统级别的`fmt`包:`import "fmt"`。 -包的概念和Python的module相同,它们都有一些特别的好处:模块化(能够把你的程序分成多个模块)和可重用性(每个模块都能被其它应用程序反复使用)。这里我们只是先了解一下包的概念,后面我们将会开始编写自己的包。 +包的概念和Python中的module相同,它们都有一些特别的好处:模块化(能够把你的程序分成多个模块)和可重用性(每个模块都能被其它应用程序反复使用)。我们在这里只是先了解一下包的概念,后面我们将会编写自己的包。 -在第五行中,我们通过关键字`func`定义了一个`main`函数,函数体被放在`{}`(大括号)中,就像我们平时写的C、C++或Java一样。 +在第五行中,我们通过关键字`func`定义了一个`main`函数,函数体被放在`{}`(大括号)中,就像我们平时写C、C++或Java时一样。 大家可以看到`main`函数是没有任何的参数的,我们接下来就学习如何编写带参数的、返回0个或多个值的函数。 -第六行,我们调用了`fmt`包里面定义的函数`Printf`。大家可以看到,这个函数是通过`.`的方式调用的,这个和Python十分相似。 +第六行,我们调用了`fmt`包里面定义的函数`Printf`。大家可以看到,这个函数是通过`.`的方式调用的,这一点和Python十分相似。 ->前面提到过,包名和包所在文件夹名可以是不同的,此处的package name即为通过`package `声明的包名,而非文件夹名。 +>前面提到过,包名和包所在的文件夹名可以是不同的,此处的``即为通过`package `声明的包名,而非文件夹名。 -最后大家可以看到我们输出的内容里面包含了很多非ASCII码的字符。实际上,Go是天生支持UTF-8的,任何字符都可以直接输出,你甚至可以用UTF-8中的任何字符作为标识符。 +最后大家可以看到我们输出的内容里面包含了很多非ASCII码字符。实际上,Go是天生支持UTF-8的,任何字符都可以直接输出,你甚至可以用UTF-8中的任何字符作为标识符。 ##结论 -Go使用package(和Python的模块类似)来组织代码。`main.main()`函数(这个函数主要位于主包)是每一个独立的可运行程序的入口点。Go使用UTF-8字符串和标识符(因为UTF-8的发明者也就是Go的发明者),所以它天生就具有多语言的支持。 +Go使用`package`(和Python的模块类似)来组织代码。`main.main()`函数(这个函数主要位于主包)是每一个独立的可运行程序的入口点。Go使用UTF-8字符串和标识符(因为UTF-8的发明者也就是Go的发明者),所以它天生就具有多语言的支持。 ## links * [目录]() diff --git a/2.2.md b/2.2.md index 0a0a999d..5205dcb3 100644 --- a/2.2.md +++ b/2.2.md @@ -1,10 +1,10 @@ #2.2 Go基础 -这小节我们将要介绍如何定义变量、常量、Go内置类型以及一些Go设计中的技巧。 +这小节我们将要介绍如何定义变量、常量、Go内置类型以及一些Go程序设计中的技巧。 ##定义变量 -Go语言里面定义变量有好几种方式。 +Go语言里面定义变量有多种方式。 最基本的定义变量如下,Go变量定义,它的类型是跟在变量后面的,而不是像C一样放前面: @@ -16,7 +16,7 @@ Go语言里面定义变量有好几种方式。 //定义三个类型都是“type”的三个变量 var vname1, vname2, vname3 type -定义变量并且带有初始化的值 +定义变量并初始化值 //初始化“variableName”的变量为“value”值,类型是“type” var variableName type = value @@ -24,36 +24,36 @@ Go语言里面定义变量有好几种方式。 同时初始化多个变量 /* - 定义三个类型都是"type"的三个变量,并且它们分别初始化相应的值 - vname1为v1,vname2为v2,vname3为v3 + 定义三个类型都是"type"的三个变量,并且它们分别初始化相应的值 + vname1为v1,vname2为v2,vname3为v3 */ var vname1, vname2, vname3 type= v1, v2, v3 -你是不是觉得上面这样的定义有点复杂,没关系,因为Go语言的设计者也发现这样复杂了,我们来让它变得简单一点。我们可以直接忽略类型声明,那么上面的东西变成了如下 +你是不是觉得上面这样的定义有点复杂?没关系,因为Go语言的设计者也发现了,我们来让它变得简单一点。我们可以直接忽略类型声明,那么上面的东西变成这样了: /* - 定义三个变量,它们分别初始化相应的值 - vname1为v1,vname2为v2,vname3为v3 - 然后它们的类型自动根据初始化的值来确定相应的类型,Go会帮你做这件事 + 定义三个变量,它们分别初始化相应的值 + vname1为v1,vname2为v2,vname3为v3 + 然后Go会根据其相应值的类型来帮你初始化它们 */ var vname1, vname2, vname3 = v1, v2, v3 -你还是觉得上面的复杂?好吧,我也觉得是,让我们继续简化 +你还是觉得上面的有些复杂?好吧,我觉得也是。让我们继续简化: /* - 定义三个变量,它们分别初始化相应的值 - vname1为v1,vname2为v2,vname3为v3 - 编译器会根据初始化的值自动推导出相应的类型 + 定义三个变量,它们分别初始化相应的值 + vname1为v1,vname2为v2,vname3为v3 + 编译器会根据初始化的值自动推导出相应的类型 */ vname1, vname2, vname3 := v1, v2, v3 -现在是不是看上去非常的简单了?`:=`这个定义直接替代了`var`和`type`,这样的代码是不是很简洁?但是`:=`有一个限制,那就是它只能用在函数内部,在函数外部使用则不能编译通过,比如用以定义全局变量。 +现在是不是看上去非常的简单了?`:=`这个符号直接取代了`var`和`type`,这样的代码是不是很简洁?这种形式叫做简短声明。不过它有一个限制,那就是它只能用在函数内部;在函数外部使用则会无法编译通过,比如说用它来定义全局变量。 -一个特殊的变量名是`_`(下划线),任何赋给它的值都会被丢弃。在这个例子中,将值`35`赋予`b`,同时丢弃`34`。 +`_`(下划线)是个特殊的变量名,任何赋予它的值都会被丢弃。在这个例子中,我们将值`35`赋予`b`,并同时丢弃`34`: _, b := 34, 35 -Go的编译器对声明却未使用的变量会在编译阶段报错。下面的代码会产生一个错误:声明了i却未使用 +Go对于已声明但未使用的变量会在编译阶段报错,比如下面的代码就会产生一个错误:声明了`i`但未使用。 package main @@ -63,82 +63,82 @@ Go的编译器对声明却未使用的变量会在编译阶段报错。下面的 ##常量 -所谓常量,也就是在编译阶段就确定下来的值,程序运行时无法改变该值,Go程序里面,常量定义可以是数值、布尔或字符串等类型。 +所谓常量,也就是在编译阶段就确定下来的值,而程序在运行时则无法改变该值。在Go程序中,常量可定义为数值、布尔值或字符串等类型。 -它的语法如下 +它的语法如下: const constantName = value -下面是一些声明的例子 +下面是一些常量声明的例子: - const Pi = 3.1415927 + const Pi = 3.1415926 const i = 10000 const MaxThread = 10 const prefix = 'astaxie_' 当然如果需要,也可以明确指定常量的类型: - const PI float32 = 3.1415926 + const Pi float32 = 3.1415926 ##内置基础类型 ###Boolean -对于布尔值,在Go中,它的类型是`bool`,可用的值是`true`或`false`,默认为`false`。 +在Go中,布尔值的类型为`bool`,可用的值是`true`或`false`,默认为`false`。 //示例代码 - var isactive bool - var enabled, disabled = true, false //忽略类型的声明 - func test(){ - var available bool //一般的声明 - valid := false //忽略var和type的声明 - available = true //赋值操作 + var isActive bool // 全局变量声明 + var enabled, disabled = true, false // 忽略类型的声明 + func test() { + var available bool // 一般声明 + valid := false // 简短声明 + available = true // 赋值操作 } -###数字类型 +###数值类型 -对于整数类型,有无符号和带符号两种。Go同时支持`int`和`uint`,这两种类型的长度相同,但具体长度取决于编译器的实现。当前的gc和gccgo编译器在32位和64位平台上都使用32位来表示`int`和`uint`,但未来在64位平台上可能增加到64位。Go里面也有直接定义好位数的类型`rune`, `int8`, `int16`, `int32`, `int64`和`byte`, `uint8`, `uint16`, `uint32`, `uint64`。`rune`是`int32`的别称,`byte`是`uint8`的别称。 +整数类型有无符号和带符号两种。Go同时支持`int`和`uint`,这两种类型的长度相同,但具体长度取决于编译器的实现。当前的gc和gccgo编译器在32位和64位平台上都使用32位来表示`int`和`uint`,但未来在64位平台上可能增加到64位。Go里面也有直接定义好位数的类型:`rune`, `int8`, `int16`, `int32`, `int64`和`byte`, `uint8`, `uint16`, `uint32`, `uint64`。`rune`是`int32`的别称,`byte`是`uint8`的别称。 ->注意一点就是这些类型之间的变量不允许相互之间赋值、操作,不然会引起编译器的错误。 +>需要注意的一点是,这些类型的变量之间不允许互相赋值或操作,不然会在编译时引起编译器报错。 > ->如下的代码会出现错误 +>如下的代码会产生错误 > > var a int8 > var b int32 > c:=a + b -浮点类型的值有`float32`和`float64`(没有`float`类型)。 +浮点数的类型有`float32`和`float64`两种(没有`float`类型),默认是`float64`。 -这就是全部吗?No!Go支持复数。它的变量类型是`complex128`(64位实数,64位虚数)。如果需要小一些的,还有`complex64`(32位实数,32位虚数)。复数写为`re + imi`,`re`是实数部分,`im`是虚数部分,而`i`是标记。使用复数的一个例子: +这就是全部吗?No!Go还支持复数。它的默认类型是`complex128`(64位实数+64位虚数)。如果需要小一些的,也有`complex64`(32位实数+32位虚数)。复数的形式为`re + imi`,其中`re`是实数部分,`im`是虚数部分,而最后的`i`是虚数单位。下面是一个使用复数的例子: var c complex64 = 5+5i; fmt.Printf("Value is: %v", c) -将会打印:(5+5i) +它会打印:(5+5i) ###字符串 -前面一节里面说过,字符串都是UTF-8类型的。字符串通过一对双引号("")或反引号(` `` `)来定义,它的类型是`string`。 +我们在上一节中讲过,Go中的字符串都是用`UTF-8`的形式编码的。字符串通过用一对双引号(`""`)或反引号(` `` `)括起来定义,它的类型是`string`。 //示例代码 - var frenchHello string //声明变量为字符串的一般方法 - var emptyString string = "" // 声明了一个字符串变量,初始化为空值 - func test(){ - no, yes, maybe := "no", "yes", "maybe" //忽略var和type的声明,同时声明多个变量 - japaneseHello := "Ohaiou" //同上 - frenchHello = "Bonjour" //常规赋值 + var frenchHello string // 声明变量为字符串的一般方法 + var emptyString string = "" // 声明了一个字符串变量,初始化为空字符串 + func test() { + no, yes, maybe := "no", "yes", "maybe" // 简短声明,同时声明多个变量 + japaneseHello := "Ohaiou" // 同上 + frenchHello = "Bonjour" // 常规赋值 } -在Go中字符串是不可变的,例如如下的代码编译时会报错: +在Go中字符串是不可变的,例如下面的代码编译时会报错: var s string = "hello" s[0] = 'c' -那么如果真的想要修改怎么办呢?如下的代码可以实现: +但如果真的想要修改怎么办呢?下面的代码可以实现: s := "hello" c := []byte(s) // 将字符串 s 转换为 []byte 类型 @@ -154,7 +154,7 @@ Go中可以使用`+`来链接两个字符串: a := s + m fmt.Printf("%s\n", a) -修改字符串也可写为 +修改字符串也可写为: s := "hello" s = "c" + s[1:] // 字符串虽不能更改,但可进行切片操作 @@ -165,29 +165,29 @@ Go中可以使用`+`来链接两个字符串: m := `hello world` -`` ` `` 括起的字符串为Raw字符串,即字符串在代码中的形式就是打印时的形式,没有字符转义,换行也将原样输出。 +`` ` `` 括起的字符串为Raw字符串,即字符串在代码中的形式就是打印时的形式,它没有字符转义,换行也将原样输出。 ###错误类型 Go内置有一个`error`类型,专门用来处理错误信息,Go的`package`里面还专门有一个包`errors`来处理错误: err := errors.New("emit macho dwarf: elf header corrupted") if err != nil { - fmt.Print(err) + fmt.Print(err) } ###Go数据底层的存储 -下面这张图来源于 Russ Cox blog中的一篇介绍Go数据结构的文章,大家可以看到这些基础类型底层都是开辟了一块内存,然后存了相应的值。 +下面这张图来源于[Russ Cox Blog](http://research.swtch.com/)中一篇介绍[Go数据结构](http://research.swtch.com/godata)的文章,大家可以看到这些基础类型底层都是分配了一块内存,然后存储了相应的值。 ![](images/2.2.basic.png?raw=true) ##一些技巧 -###分组定义 +###分组声明 -Go语言里面针对多个同时声明变量、常量或者`import`多个包的时候可以采用分组的方式来声明 +在Go语言中,同时声明多个常量、变量,或者导入多个包时,可采用分组的方式进行声明。 -如下的代码 +例如下面的代码: import "fmt" import "os" @@ -200,73 +200,75 @@ Go语言里面针对多个同时声明变量、常量或者`import`多个包的 var pi float32 var prefix string -可以通过分组的方式写成如下 +可以分组写成如下形式: import( - "fmt" - "os" + "fmt" + "os" ) const( - i = 100 - pi = 3.1415 - prefix = "Go_" + i = 100 + pi = 3.1415 + prefix = "Go_" ) var( - i int - pi float32 - prefix string + i int + pi float32 + prefix string ) ->除非被显式设置为其它值或`iota`,每个`const`分组的第一个常量被默认设置为它的0值,第二及后续的常量被默认设置为它前面那个常量的值,如果前面那个常量的值是`iota`,则它也被设置为`iota` +>除非被显式设置为其它值或`iota`,每个`const`分组的第一个常量被默认设置为它的0值,第二及后续的常量被默认设置为它前面那个常量的值,如果前面那个常量的值是`iota`,则它也被设置为`iota`。 ###iota枚举 -Go里面有一个关键字`iota`,这个关键字用来声明`enum`的时候采用,它默认开始值是0,每调用一次加1 +Go里面有一个关键字`iota`,这个关键字用来声明`enum`的时候采用,它默认开始值是0,每调用一次加1: const( - x = iota //x == 0 - y = iota //y == 1 - z = iota //z == 2 - w // 常量声明省略值时,默认和之前一个值的字面相同。这里隐式地说w = iota,因此w == 3。其实上面y和z可以同样不用"= iota" + x = iota // x == 0 + y = iota // y == 1 + z = iota // z == 2 + w // 常量声明省略值时,默认和之前一个值的字面相同。这里隐式地说w = iota,因此w == 3。其实上面y和z可同样不用"= iota" ) - const v = iota // 每遇到一个const关键字,iota被重置,此时 v==0 + const v = iota // 每遇到一个const关键字,iota就会重置,此时v == 0 -###Go设计的一些规则 -Go之所以会那么简洁,是因为它有一些默认的行为 -- 大写字母开头的变量是导出的,也就是其它包可以读取的,类似类中的public的概念 -- 大写字母开头的函数也是一样,相当于public的函数,小写的就是类似private +###Go程序设计的一些规则 +Go之所以会那么简洁,是因为它有一些默认的行为: +- 大写字母开头的变量是已导出的,也就是其它包可以读取的,类似`class`中`public`的概念;小写字母开头的就是未导出的 +- 大写字母开头的函数也是一样,相当于`public`的函数;小写字母开头的就是类似`private` ##array、slice、map ###array -`array`就是数组,它的定义方式如下。在`[n]type`中,`n`表示数组的长度,`type`表示存储内容的类型 +`array`就是数组,它的定义方式如下: -对数组的操作和其它语言类似,都是通过`[]`来进行读取和赋值 + var arr [n]type - var arr [10]int //声明了一个int类型的数组 - arr[0] = 42 //数组下标是从0开始的 - arr[1] = 13 //赋值操作 - fmt.Printf("The first element is %d\n", arr[0]) //获取数据 +在`[n]type`中,`n`表示数组的长度,`type`表示存储元素的类型。对数组的操作和其它语言类似,都是通过`[]`来进行读取或赋值: -由于数组的长度也是类型的一部分,比如`[3]int`与`[4]int`是不同的类型,所以数组是不能改变长度的,而且数组之间的赋值是值赋值,即当把一个数组作为一个参数传入函数的时候,是这个数组的副本,而不是该数组的指针。如果要使用指针,那么就需要用到下面介绍的`slice`。 + var arr [10]int // 声明了一个int类型的数组 + arr[0] = 42 // 数组下标是从0开始的 + arr[1] = 13 // 赋值操作 + fmt.Printf("The first element is %d\n", arr[0]) // 获取数据 -数组声明可以使用另一种`:=`来声明 +由于长度也是数组类型的一部分,因此`[3]int`与`[4]int`是不同的类型,数组也就不能改变长度。数组之间的赋值是值的赋值,即当把一个数组作为参数传入函数的时候,传入的其实是该数组的副本,而不是它的指针。如果要使用指针,那么就需要用到后面介绍的`slice`类型了。 - a := [3]int{1,2,3} //声明一个长度为3的数组 +数组可以使用另一种`:=`来声明 - b := [10]int{1,2,3} //声明了一个长度为10的数组,其中前面三个元素初始化为1、2、3,其它默认为0 + a := [3]int{1,2,3} // 声明了一个长度为3的int数组 - c := [...]int{4,5,6} //可以省略长度,采用“...”,Go会自动计算长度 + b := [10]int{1,2,3} // 声明了一个长度为10的int数组,其中前三个元素初始化为1、2、3,其它默认为0 -也许你会说,我想数组里面还是数组,能实现吗?当然咯,Go支持嵌套数组,即多维数组,如下代码声明了一个二维数组 + c := [...]int{4,5,6} // 可以省略长度而采用`...`的方式,Go会自动根据元素个数来计算长度 - //声明了一个二维数组,该数组是一个两个元素的数组,然后每个元素里面是4个int的元素 +也许你会说,我想数组里面还是数组,能实现吗?当然咯,Go支持嵌套数组,即多维数组。比如下面的代码就声明了一个二维数组: + + // 声明了一个二维数组,该数组以两个数组作为元素,其中每个数组中又有4个int类型的元素 doubleArray := [2][4]int {[4]int{1,2,3,4}, [4]int{5,6,7,8}} - //如果内部的元素和外部的一样,那么上面的声明可以简化,直接忽略内部的类型 + // 如果内部的元素和外部的一样,那么上面的声明可以简化,直接忽略内部的类型 easyArray :=[2][4]int{{1,2,3,4}, {5,6,7,8}} 数组的分配如下所示: @@ -276,34 +278,34 @@ Go之所以会那么简洁,是因为它有一些默认的行为 ###slice -在很多的应用场景里面,数组不能满足我们的需求。因为在刚开始的时候,我们不知道到底需要多大的数组合适,所以我们需要动态数组。在Go里面这种数据结构叫`slice` +在很多应用场景中,数组并不能满足我们的需求。在刚开始时,我们并不知道需要多大的数组,因此我们就需要“动态数组”。在Go里面这种数据结构叫`slice` -`slice`并不是真正意义上的动态数组,而是一个引用类型,`slice`总是指向底层的一个`array`。`slice`的声明也可以像`array`一样,只要省略size。 +`slice`并不是真正意义上的动态数组,而是一个引用类型。`slice`总是指向底层的一个`array`,`slice`的声明也可以像`array`一样,只是不需要长度。 - //和声明array一样,只是少了长度 + // 和声明array一样,只是少了长度 var fslice []int 接下来我们可以声明一个`slice`,并初始化数据,如下所示: slice := []byte {'a', 'b', 'c', 'd'} -`slice`可以从一个数组或者一个已经存在的`slice`里面再次声明,`slice`通过`array[i:j]`来获取,`i`是数组的开始位置,`j`是结束位置,但不包含`array[j]`,它的长度是`j-i`。 +`slice`可以从一个数组或一个已经存在的`slice`中再次声明。`slice`通过`array[i:j]`来获取,其中`i`是数组的开始位置,`j`是结束位置,但不包含`array[j]`,它的长度是`j-i`。 - //声明一个含有十个字符元素的数组 + // 声明一个含有10个元素元素类型为byte的数组 var ar = [10]byte {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'} - //声明两个含有byte的slice + // 声明两个含有byte的slice var a,b[]byte - //a指向数组的第3个元素开始,并到第五个元素结束, + // a指向数组的第3个元素开始,并到第五个元素结束, a = ar[2:5] - //现在a含有的元素: ar[2], ar[3] and ar[4] + //现在a含有的元素: ar[2]、ar[3]和ar[4] - // b是数组ar的另一个slice. + // b是数组ar的另一个slice b = ar[3:5] - // b的元素是: ar[3], ar[4] + // b的元素是:ar[3]和ar[4] -注意`slice`和数组声明时的区别:声明数组时,方括号内写明了数组的长度或使用`...`自动计算长度,而声明``slice``时,方括号内没有任何字符 +>注意`slice`和数组在声明时的区别:声明数组时,方括号内写明了数组的长度或使用`...`自动计算长度,而声明``slice``时,方括号内没有任何字符。 它们的数据结构如下所示 @@ -315,30 +317,30 @@ slice有一些简便的操作 - `slice`的第二个序列默认是数组的长度,`ar[n:]`等价于`ar[n:len(ar)]` - `slice`如果从一个数组里面直接获取,可以这样`ar[:]`,因为默认第一个序列是0,第二个是数组的长度,即等价于`ar[0,len(ar)]` -下面这个例子展示更多关于`slice`的操作 +下面这个例子展示了更多关于`slice`的操作: // 声明一个数组 var array = [10]byte {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'} - //声明两个slice + // 声明两个slice var aSlice,bSlice // 演示一些简便操作 - aSlice = array[:3] // 等价于aSlice = array[0:3] aSlice包含元素: a,b,c - aSlice = array[5:] // 等价于aSlice = array[5:9] aSlice包含元素: f,g,h,i,j - aSlice = array[:] // 等价于aSlice = array[0:9] 这样aSlice包含了全部的元素 + aSlice = array[:3] // 等价于aSlice = array[0:3] aSlice包含元素: a,b,c + aSlice = array[5:] // 等价于aSlice = array[5:9] aSlice包含元素: f,g,h,i,j + aSlice = array[:] // 等价于aSlice = array[0:9] 这样aSlice包含了全部的元素 - // 从slice获取slice - aSlice = array[3:7] // aSlice包含元素: d,e,f,g,len=4,cap=8 - bSlice = aSlice[1:3] // bSlice 包含aSlice[1], aSlice[2] 也就是含有: e,f - bSlice = aSlice[:3] // bSlice 包含 aSlice[0], aSlice[1], aSlice[2] 也就是含有: d,e,f - bSlice = aSlice[0:5] // 对slice的slice可以在cap范围内扩展,此时bSlice包含:c,d,e,f,g - bSlice : aSlice[:] // bSlice包含所有aSlice的元素: d,e,f,g + // 从slice中获取slice + aSlice = array[3:7] // aSlice包含元素: d,e,f,g,len=4,cap=8 + bSlice = aSlice[1:3] // bSlice 包含aSlice[1], aSlice[2] 也就是含有: e,f + bSlice = aSlice[:3] // bSlice 包含 aSlice[0], aSlice[1], aSlice[2] 也就是含有: d,e,f + bSlice = aSlice[0:5] // 对slice的slice可以在cap范围内扩展,此时bSlice包含:c,d,e,f,g + bSlice : aSlice[:] // bSlice包含所有aSlice的元素: d,e,f,g -`slice`是引用类型,所以当引用改变其中项目的值的时候,那么其它的所有引用都会改变该值,例如上面的`aSlice`和`bSlice`,如果修改了`aSlice`中项目的值,那么`bSlice`相对应的值也会改变。 +`slice`是引用类型,所以当引用改变其中元素的值时,其它的所有引用都会改变该值,例如上面的`aSlice`和`bSlice`,如果修改了`aSlice`中元素的值,那么`bSlice`相对应的值也会改变。 从概念上面来说`slice`像一个结构体,这个结构体包含了三个元素: - 一个指针,指向数组中`slice`指定的开始位置 -- 长度,`slice`的长度 +- 长度,即`slice`的长度 - 最大长度,也就是`slice`开始位置到数组的最后位置的长度 array := [10]byte {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'} @@ -348,57 +350,57 @@ slice有一些简便的操作 ![](images/2.2.slice2.png?raw=true) -`slice`下面有几个有用的内置函数 +对于`slice`有几个有用的内置函数: -- `len` 获取`slice`的长度 -- `cap` 获取`slice`的最大容量 +- `len` 获取`slice`的长度 +- `cap` 获取`slice`的最大容量 - `append` 向`slice`里面追加一个或者多个元素,然后返回一个和`slice`一样类型的`slice` -- ``copy` 函数`copy`从源`slice`的`src`中复制元素到目标`dst`,并且返回复制的元素的个数 +- `copy` 函数`copy`从源`slice`的`src`中复制元素到目标`dst`,并且返回复制的元素的个数 注:`append`函数会改变`slice`所引用的数组的内容,从而影响到引用同一数组的其它`slice`。 -但当`slice`中没有剩余空间(即`(cap-len) == 0`)时,此时将动态分配新的数组空间。返回的`slice`数组指针将指向这个空间,而原数组的内容将保持不变,其它引用此数组的`slice`不受影响。 +但当`slice`中没有剩余空间(即`(cap-len) == 0`)时,此时将动态分配新的数组空间。返回的`slice`数组指针将指向这个空间,而原数组的内容将保持不变;其它引用此数组的`slice`则不受影响。 ###map -`map`也就是Python中字典的概念,它的格式`map[keyType]valueType` +`map`也就是Python中字典的概念,它的格式为`map[keyType]valueType` 我们看下面的代码,`map`的读取和设置也类似`slice`一样,通过`key`来操作,只是`slice`只有`int`的`key`,而`map`多了很多类型,可以是`int`,可以是`string`及所有完全定义了`==`与`!=`操作的类型。 // 声明一个key是字符串,值为int的字典 var numbers map[string] int - //另一种map的声明方式 + // 另一种map的声明方式 numbers := make(map[string]int) - numbers["one"] = 1 //赋值 + numbers["one"] = 1 //赋值 numbers["ten"] = 10 //赋值 numbers["three"] = 3 - fmt.Println("第三个数字是: ", numbers["three"]) //读取数据 + fmt.Println("第三个数字是: ", numbers["three"]) // 读取数据 // 打印出来如下: - //第三个数字是: 3 + // 第三个数字是: 3 这个`map`就像我们平常看到的表格一样,左边列是`key`,右边列是值 使用map过程中需要注意的几点: -- map是无序的,每次打印出来的`map`都会不一样,它不能通过`index`获取,而必须通过`key`获取 -- map的长度是不固定的,也就是和`slice`一样,也是一种引用类型 -- 内置的len函数同样适用于`map`,返回`map`拥有的`key`的数量 -- map的值可以很方便的修改,通过`numbers["one"]=11`可以很容易的把key为`one`的字典值改为`11` +- `map`是无序的,每次打印出来的`map`都会不一样,它不能通过`index`获取,而必须通过`key`获取 +- `map`的长度是不固定的,也就是和`slice`一样,也是一种引用类型 +- 内置的`len`函数同样适用于`map`,返回`map`拥有的`key`的数量 +- `map`的值可以很方便的修改,通过`numbers["one"]=11`可以很容易的把key为`one`的字典值改为`11` `map`的初始化可以通过`key:val`的方式初始化值,同时`map`内置有判断是否存在`key`的方式 通过`delete`删除`map`的元素: - //初始化一个字典 + // 初始化一个字典 rating := map[string]float32 {"C":5, "Go":4.5, "Python":4.5, "C++":2 } - //map可以有两个返回值,第二个返回值,如果不存在key,那么ok为false,如果存在ok为true - csharp_rating, ok := rating["C#"] + // map有两个返回值,第二个返回值,如果不存在key,那么ok为false,如果存在ok为true + csharpRating, ok := rating["C#"] if ok { - fmt.Println("C# is in the map and its rating is ", csharp_rating) + fmt.Println("C# is in the map and its rating is ", csharp_rating) } else { - fmt.Println("We have no rating associated with C# in the map") + fmt.Println("We have no rating associated with C# in the map") } - delete(rating, "C") //删除key为C的元素 + delete(rating, "C") // 删除key为C的元素 上面说过了,`map`也是一种引用类型,如果两个`map`同时指向一个底层,那么一个改变,另一个也相应的改变: @@ -406,7 +408,7 @@ slice有一些简便的操作 m = make(map[string]string) m["Hello"] = "Bonjour" m1 = m - m1["Hello"] = "Salut" //现在m["hello"]的值已经是Salut了 + m1["Hello"] = "Salut" // 现在m["hello"]的值已经是Salut了 ###make、new操作 @@ -417,7 +419,7 @@ slice有一些简便的操作 内建函数`make(T, args)`与`new(T)`有着不同的功能,它只能创建`slice`、`map`和`channel`,并且返回一个有初始值(非零)的`T`类型,而不是`*T`。本质来讲,导致这三个类型有所不同的原因是指向数据结构的引用在使用前必须被初始化。例如,一个`slice`,是一个包含指向数据(内部`array`)的指针、长度和容量的三项描述符;在这些项目被初始化之前,`slice`为`nil`。对于`slice`、`map`和`channel`来说,`make`初始化了内部的数据结构,填充适当的值。`make`返回初始化后的(非零)值。 -下面这个图详细的解释了`new`和`make`之间的区别 +下面这个图详细的解释了`new`和`make`之间的区别。 ![](images/2.2.makenew.png?raw=true) diff --git a/2.3.md b/2.3.md index be09ab2e..8065f08a 100644 --- a/2.3.md +++ b/2.3.md @@ -8,18 +8,18 @@ if语法也许是所有语言里面最常见的一种语法了,它的语法概 Go里面`if`条件语法中不需要括号,如下代码所示 if x > 10 { - fmt.Println("x is greater than 10") + fmt.Println("x is greater than 10") } else { - fmt.Println("x is less than 10") + fmt.Println("x is less than 10") } Go的`if`还有一个强大的地方就是条件里面允许声明一个变量,这个变量的作用域只能在该条件中,出了这个条件就不起作用了,如下所示 // 计算获取值x,然后根据x返回的大小,判断是否大于10. if x := computedValue(); x > 10 { - fmt.Println("x is greater than 10") + fmt.Println("x is greater than 10") } else { - fmt.Println("x is less than 10") + fmt.Println("x is less than 10") } //这个地方如果这样调用就编译出错了,因为x是条件里面的变量 @@ -30,9 +30,9 @@ Go的`if`还有一个强大的地方就是条件里面允许声明一个变量 if integer == 3 { fmt.Println("The integer is equal to 3") } else if integer < 3 { - fmt.Println("The integer is less than 3") + fmt.Println("The integer is less than 3") } else { - fmt.Println("The integer is greater than 3") + fmt.Println("The integer is greater than 3") } ###goto @@ -53,7 +53,7 @@ Go有`goto`语句——请明智地使用它。用`goto`跳转到一定是当前 Go里面最强大的一个控制逻辑就是`for`,它即可以用来循环读取数据,又可以当作`while`来控制逻辑,还能迭代操作。它的语法如下 for expression1; expression2; expression3 { - ... + ... } `expression1`、`expression2`和`expression3`都是表达式,其中`expression1`和`expression3`是变量声明或者函数调用返回值之类的,`expression2`是条件判断,`expression1`在循环开始之前调用,`expression3`在每轮循环结束之时调用。 @@ -64,11 +64,11 @@ Go里面最强大的一个控制逻辑就是`for`,它即可以用来循环读 import "fmt" func main(){ - sum := 0; - for index:=0; index < 10 ; index++ { - sum += index - } - fmt.Println("sum is equal to ", sum) + sum := 0; + for index:=0; index < 10 ; index++ { + sum += index + } + fmt.Println("sum is equal to ", sum) } //输出:sum is equal to 45 @@ -79,23 +79,23 @@ Go里面最强大的一个控制逻辑就是`for`,它即可以用来循环读 sum := 1 for ; sum < 1000; { - sum += sum + sum += sum } 其中`;`也可以省略,那么就变成如下的代码了,是不是似曾相识,对,这就是`while`的功能 sum := 1 for sum < 1000 { - sum += sum + sum += sum } 在循环里面有两个关键操作`break`和`continue` ,`break`操作是跳出当前循环,`continue`是跳出本次循环,当嵌套过深的时候,`break`可以配合标签使用,即跳出标签所指定的循环,详细参考如下例子 for index := 10; index>0; index-- { - if index == 5{ - break或者continue - } - fmt.Println(index) + if index == 5{ + break或者continue + } + fmt.Println(index) } //break打印出来10、9、8、7、6 //continue打印出来10、9、8、7、6、4、3、2、1 @@ -116,51 +116,51 @@ Go里面最强大的一个控制逻辑就是`for`,它即可以用来循环读 有些时候你需要写很多的`if/else`来实现一些逻辑处理,这个时候代码看上去就很丑很冗长,而且也不易于以后的维护,这个时候`switch`就能很好的解决这个问题,它的语法如下 switch sExpr { - case expr1: - some instructions - case expr2: - some other instructions - case expr3: - some other instructions - default: - other code + case expr1: + some instructions + case expr2: + some other instructions + case expr3: + some other instructions + default: + other code } `sExpr`和`expr1`、`expr2`、`expr3`的类型必须一致。Go的`switch`非常灵活。表达式不必是常量或整数,执行的过程从上至下,直到找到匹配项,而如果`switch`没有表达式,它会匹配`true`。 i := 10 switch i { - case 1: - fmt.Println("i is equal to 1") - case 2, 3, 4: - fmt.Println("i is equal to 2, 3 or 4") - case 10: - fmt.Println("i is equal to 10") - default: - fmt.Println("All I know is that i is an integer") + case 1: + fmt.Println("i is equal to 1") + case 2, 3, 4: + fmt.Println("i is equal to 2, 3 or 4") + case 10: + fmt.Println("i is equal to 10") + default: + fmt.Println("All I know is that i is an integer") } -我们看第六行,我们把很多值聚合在了一个`case`里面,同时,Go里面`switch`默认相当于每个`case`后面带有`break`,匹配成功后不会自动向下尝试,而是跳出整个`switch`了,但是可以使用`fallthrough`使其这样做。 +在第5行中,我们把很多值聚合在了一个`case`里面,同时,Go里面`switch`默认相当于每个`case`后面带有`break`,匹配成功后不会自动向下尝试,而是跳出整个`switch`了,但是可以使用`fallthrough`使其这样做。 integer := 6 switch integer { case 4: - fmt.Println("The integer was <= 4") - fallthrough + fmt.Println("The integer was <= 4") + fallthrough case 5: - fmt.Println("The integer was <= 5") - fallthrough + fmt.Println("The integer was <= 5") + fallthrough case 6: - fmt.Println("The integer was <= 6") - fallthrough + fmt.Println("The integer was <= 6") + fallthrough case 7: - fmt.Println("The integer was <= 7") - fallthrough + fmt.Println("The integer was <= 7") + fallthrough case 8: - fmt.Println("The integer was <= 8") - fallthrough + fmt.Println("The integer was <= 8") + fallthrough default: - fmt.Println("default case") + fmt.Println("default case") } 上面的程序将输出 @@ -175,9 +175,9 @@ Go里面最强大的一个控制逻辑就是`for`,它即可以用来循环读 函数是Go里面的核心设计,它通过关键字`func`来声明,它的格式如下 func funcName(input1 type1, input2 type2) (output1 type1, output2 type2) { - //这里是处理逻辑代码 - //返回多个值 - return value1, value2 + //这里是处理逻辑代码 + //返回多个值 + return value1, value2 } 上面的代码我们看出 @@ -196,23 +196,23 @@ Go里面最强大的一个控制逻辑就是`for`,它即可以用来循环读 //返回a、b中最大值. func max(a, b int) int { - if a > b { - return a - } - return b + if a > b { + return a + } + return b } func main() { - x := 3 - y := 4 - z := 5 + x := 3 + y := 4 + z := 5 - max_xy := max(x, y) //调用函数max(x, y) - max_xz := max(x, z) //调用函数max(x, z) + max_xy := max(x, y) //调用函数max(x, y) + max_xz := max(x, z) //调用函数max(x, z) - fmt.Printf("max(%d, %d) = %d\n", x, y, max_xy) - fmt.Printf("max(%d, %d) = %d\n", x, z, max_xz) - fmt.Printf("max(%d, %d) = %d\n", y, z, max(y,z)) //just call it here + fmt.Printf("max(%d, %d) = %d\n", x, y, max_xy) + fmt.Printf("max(%d, %d) = %d\n", x, z, max_xz) + fmt.Printf("max(%d, %d) = %d\n", y, z, max(y,z)) //just call it here } 上面这个里面我们可以看到`max`函数有两个参数,它们的类型都是`int`,那么第一个变量的类型可以省略,默认为离它最近的类型,同理多于2个同类型的变量或者返回值。同时我们注意到它的返回值就是一个类型,这个就是省略写法。 @@ -227,23 +227,23 @@ Go语言和C相比,更先进的地方,其中一点就是能够返回多个 //返回 A+B 和 A*B func SumAndProduct(A, B int) (int, int) { - return A+B, A*B + return A+B, A*B } func main() { - x := 3 - y := 4 + x := 3 + y := 4 - xPLUSy, xTIMESy := SumAndProduct(x, y) + xPLUSy, xTIMESy := SumAndProduct(x, y) - fmt.Printf("%d + %d = %d\n", x, y, xPLUSy) - fmt.Printf("%d * %d = %d\n", x, y, xTIMESy) + fmt.Printf("%d + %d = %d\n", x, y, xPLUSy) + fmt.Printf("%d * %d = %d\n", x, y, xTIMESy) } 上面的例子我们可以看到直接返回了两个参数,当然我们也可以命名返回参数的变量,这个例子里面只是用了两个类型,我们也可以改成如下这样的定义,然后返回的时候不用带上变量名,因为直接在函数里面初始化了。但是当你的函数如果是导出的(首字母大写),官方建议,不要命名返回值名称,因为这样会造成生成的文档不易读。 func SumAndProduct(A, B int) (add int, Multiplied int) { - add = A+B + add = A+B Multiplied = A*B return } @@ -268,19 +268,19 @@ Go函数支持变参。接受变参的函数是有着不定数量的参数的。 //简单的一个函数,实现了参数+1的操作 func add1(a int) int { - a = a+1 // 我们改变了a的值 - return a //返回一个新值 + a = a+1 // 我们改变了a的值 + return a //返回一个新值 } func main() { - x := 3 + x := 3 - fmt.Println("x = ", x) // 应该输出 "x = 3" + fmt.Println("x = ", x) // 应该输出 "x = 3" - x1 := add1(x) //调用add1(x) + x1 := add1(x) //调用add1(x) - fmt.Println("x+1 = ", x1) // 应该输出"x+1 = 4" - fmt.Println("x = ", x) // 应该输出"x = 3" + fmt.Println("x+1 = ", x1) // 应该输出"x+1 = 4" + fmt.Println("x = ", x) // 应该输出"x = 3" } 看到了吗?虽然我们调用了`add1`函数,并且在`add1`中执行`a=a+1`操作,但是上面例子中`x`变量的值没有发生变化 @@ -296,19 +296,19 @@ Go函数支持变参。接受变参的函数是有着不定数量的参数的。 //简单的一个函数,实现了参数+1的操作 func add1(a *int) int { // 请注意, - *a = *a+1 // 修改了a的值 - return *a // 返回新值 + *a = *a+1 // 修改了a的值 + return *a // 返回新值 } func main() { - x := 3 + x := 3 - fmt.Println("x = ", x) // 应该输出 "x = 3" + fmt.Println("x = ", x) // 应该输出 "x = 3" - x1 := add1(&x) // 调用 add1(&x) 传x的地址 + x1 := add1(&x) // 调用 add1(&x) 传x的地址 - fmt.Println("x+1 = ", x1) // 应该输出 "x+1 = 4" - fmt.Println("x = ", x) // 应该输出 "x = 4" + fmt.Println("x+1 = ", x1) // 应该输出 "x+1 = 4" + fmt.Println("x = ", x) // 应该输出 "x = 4" } 这样,我们就达到了修改`x`的目的。那么到底传指针有什么好处呢? @@ -354,7 +354,7 @@ Go里面有一个不错的设计,就是回调函数,有点类似面向对象 如果有很多调用`defer`,那么`defer`是采用后进先出模式,所以如下代码会输出`4 3 2 1 0` for i := 0; i < 5; i++ { - defer fmt.Printf("%d ", i) + defer fmt.Printf("%d ", i) } ###函数作为值、类型 @@ -371,37 +371,37 @@ Go里面有一个不错的设计,就是回调函数,有点类似面向对象 type testInt func(int) bool //声明了一个函数类型 func isOdd(integer int) bool { - if integer%2 == 0 { - return false - } - return true + if integer%2 == 0 { + return false + } + return true } func isEven(integer int) bool { - if integer%2 == 0 { - return true - } - return false + if integer%2 == 0 { + return true + } + return false } - //声明的函数类型在这个地方当做了一个参数 + //声明的函数类型在这个地方当做了一个参数 func filter(slice []int, f test_int) []int { - var result []int - for _, value := range slice { - if f(value) { - result = append(result, value) - } - } - return result + var result []int + for _, value := range slice { + if f(value) { + result = append(result, value) + } + } + return result } func main(){ - slice := []int {1, 2, 3, 4, 5, 7} - fmt.Println("slice = ", slice) - odd := filter(slice, isOdd) //函数当做值来传递了 - fmt.Println("Odd elements of slice are: ", odd) - even := filter(slice, isEven)//函数当做值来传递了 - fmt.Println("Even elements of slice are: ", even) + slice := []int {1, 2, 3, 4, 5, 7} + fmt.Println("slice = ", slice) + odd := filter(slice, isOdd) //函数当做值来传递了 + fmt.Println("Odd elements of slice are: ", odd) + even := filter(slice, isEven)//函数当做值来传递了 + fmt.Println("Even elements of slice are: ", even) } 函数当做值和类型在我们写一些通用接口的时候非常有用,通过上面例子我们看到`testInt`这个类型是一个函数类型,然后两个`filter`函数的参数和返回值与`testInt`类型是一样的,但是我们可以实现很多种的逻辑,这样使得我们的程序变得非常的灵活。 @@ -421,9 +421,9 @@ Recover var user = os.Getenv("USER") func init() { - if user == "" { - panic("no value for $USER") - } + if user == "" { + panic("no value for $USER") + } } 下面这个函数检查作为其参数的函数在执行时是否会产生`panic`: diff --git a/2.md b/2.md index 46a9747b..c6e28b1e 100644 --- a/2.md +++ b/2.md @@ -10,7 +10,7 @@ * 7. [并发](2.7.md) * 8. [小结](2.8.md) -Go是一门类似C的编译型语言,但是它的编译速度非常快。这门语言的关键字加起来也就二十五个,比英文字母的二十六还少一个,这对于我们来说学习就变得简单了很多。先让我们看一眼这些关键字都长成怎么样: +Go是一门类似C的编译型语言,但是它的编译速度非常快。这门语言的关键字总共也就二十五个,比英文字母还少一个,这对于我们的学习来说就简单了很多。先让我们看一眼这些关键字都长什么样: break default func interface select case defer go map struct @@ -18,7 +18,7 @@ Go是一门类似C的编译型语言,但是它的编译速度非常快。这 const fallthrough if range type continue for import return var -在接下来的这一章里面,我将带领你去了解这门语言的基础,通过每个小节的介绍,你将会了解到Go的世界是那么的简洁,设计是如此的美妙,编写Go将会是一件愉快的事情,回过头来你就会发现上面这二十五个关键字是那么地亲切。 +在接下来的这一章中,我将带领你去学习这门语言的基础。通过每一小节的介绍,你将发现,Go的世界是那么地简洁,设计是如此地美妙,编写Go将会是一件愉快的事情。等回过头来,你就会发现这二十五个关键字是多么地亲切。 ## links