Merge pull request #751 from vCaesar/h-pr

Add 02.x.md syntax highlighting  and Fix SQL Error
This commit is contained in:
astaxie
2016-12-17 04:35:31 +08:00
committed by GitHub
11 changed files with 255 additions and 112 deletions

View File

@@ -7,6 +7,7 @@
这就像一个传统,在学习大部分语言之前,你先学会如何编写一个可以输出`hello world`的程序。 这就像一个传统,在学习大部分语言之前,你先学会如何编写一个可以输出`hello world`的程序。
准备好了吗Let's Go! 准备好了吗Let's Go!
```Go
package main package main
@@ -15,7 +16,7 @@
func main() { func main() {
fmt.Printf("Hello, world or 你好,世界 or καλημ ́ρα κóσμ or こんにちはせかい\n") fmt.Printf("Hello, world or 你好,世界 or καλημ ́ρα κóσμ or こんにちはせかい\n")
} }
```
输出如下: 输出如下:
Hello, world or 你好,世界 or καλημ ́ρα κóσμ or こんにちはせかい Hello, world or 你好,世界 or καλημ ́ρα κóσμ or こんにちはせかい

View File

@@ -7,29 +7,34 @@
Go语言里面定义变量有多种方式。 Go语言里面定义变量有多种方式。
使用`var`关键字是Go最基本的定义变量方式与C语言不同的是Go把变量类型放在变量名后面 使用`var`关键字是Go最基本的定义变量方式与C语言不同的是Go把变量类型放在变量名后面
```Go
//定义一个名称为“variableName”类型为"type"的变量 //定义一个名称为“variableName”类型为"type"的变量
var variableName type var variableName type
```
定义多个变量 定义多个变量
```Go
//定义三个类型都是“type”的变量 //定义三个类型都是“type”的变量
var vname1, vname2, vname3 type var vname1, vname2, vname3 type
```
定义变量并初始化值 定义变量并初始化值
```Go
//初始化“variableName”的变量为“value”值类型是“type” //初始化“variableName”的变量为“value”值类型是“type”
var variableName type = value var variableName type = value
```
同时初始化多个变量 同时初始化多个变量
```Go
/* /*
定义三个类型都是"type"的变量,并且分别初始化为相应的值 定义三个类型都是"type"的变量,并且分别初始化为相应的值
vname1为v1vname2为v2vname3为v3 vname1为v1vname2为v2vname3为v3
*/ */
var vname1, vname2, vname3 type= v1, v2, v3 var vname1, vname2, vname3 type= v1, v2, v3
```
你是不是觉得上面这样的定义有点繁琐没关系因为Go语言的设计者也发现了有一种写法可以让它变得简单一点。我们可以直接忽略类型声明那么上面的代码变成这样了 你是不是觉得上面这样的定义有点繁琐没关系因为Go语言的设计者也发现了有一种写法可以让它变得简单一点。我们可以直接忽略类型声明那么上面的代码变成这样了
```Go
/* /*
定义三个变量,它们分别初始化为相应的值 定义三个变量,它们分别初始化为相应的值
@@ -37,8 +42,9 @@ Go语言里面定义变量有多种方式。
然后Go会根据其相应值的类型来帮你初始化它们 然后Go会根据其相应值的类型来帮你初始化它们
*/ */
var vname1, vname2, vname3 = v1, v2, v3 var vname1, vname2, vname3 = v1, v2, v3
```
你觉得上面的还是有些繁琐?好吧,我也觉得。让我们继续简化: 你觉得上面的还是有些繁琐?好吧,我也觉得。让我们继续简化:
```Go
/* /*
定义三个变量,它们分别初始化为相应的值 定义三个变量,它们分别初始化为相应的值
@@ -46,7 +52,7 @@ Go语言里面定义变量有多种方式。
编译器会根据初始化的值自动推导出相应的类型 编译器会根据初始化的值自动推导出相应的类型
*/ */
vname1, vname2, vname3 := v1, v2, v3 vname1, vname2, vname3 := v1, v2, v3
```
现在是不是看上去非常简洁了?`:=`这个符号直接取代了`var``type`,这种形式叫做简短声明。不过它有一个限制,那就是它只能用在函数内部;在函数外部使用则会无法编译通过,所以一般用`var`方式来定义全局变量。 现在是不是看上去非常简洁了?`:=`这个符号直接取代了`var``type`,这种形式叫做简短声明。不过它有一个限制,那就是它只能用在函数内部;在函数外部使用则会无法编译通过,所以一般用`var`方式来定义全局变量。
`_`(下划线)是个特殊的变量名,任何赋予它的值都会被丢弃。在这个例子中,我们将值`35`赋予`b`,并同时丢弃`34` `_`(下划线)是个特殊的变量名,任何赋予它的值都会被丢弃。在这个例子中,我们将值`35`赋予`b`,并同时丢弃`34`
@@ -54,30 +60,33 @@ Go语言里面定义变量有多种方式。
_, b := 34, 35 _, b := 34, 35
Go对于已声明但未使用的变量会在编译阶段报错比如下面的代码就会产生一个错误声明了`i`但未使用。 Go对于已声明但未使用的变量会在编译阶段报错比如下面的代码就会产生一个错误声明了`i`但未使用。
```Go
package main package main
func main() { func main() {
var i int var i int
} }
```
## 常量 ## 常量
所谓常量也就是在程序编译阶段就确定下来的值而程序在运行时无法改变该值。在Go程序中常量可定义为数值、布尔值或字符串等类型。 所谓常量也就是在程序编译阶段就确定下来的值而程序在运行时无法改变该值。在Go程序中常量可定义为数值、布尔值或字符串等类型。
它的语法如下: 它的语法如下:
```Go
const constantName = value const constantName = value
//如果需要,也可以明确指定常量的类型: //如果需要,也可以明确指定常量的类型:
const Pi float32 = 3.1415926 const Pi float32 = 3.1415926
```
下面是一些常量声明的例子: 下面是一些常量声明的例子:
```Go
const Pi = 3.1415926 const Pi = 3.1415926
const i = 10000 const i = 10000
const MaxThread = 10 const MaxThread = 10
const prefix = "astaxie_" const prefix = "astaxie_"
```
Go 常量和一般程序语言不同的是,可以指定相当多的小数位数(例如200位) Go 常量和一般程序语言不同的是,可以指定相当多的小数位数(例如200位)
若指定給float32自动缩短为32bit指定给float64自动缩短为64bit详情参考[链接](http://golang.org/ref/spec#Constants) 若指定給float32自动缩短为32bit指定给float64自动缩短为64bit详情参考[链接](http://golang.org/ref/spec#Constants)
@@ -86,6 +95,7 @@ Go 常量和一般程序语言不同的是,可以指定相当多的小数位
### Boolean ### Boolean
在Go中布尔值的类型为`bool`,值是`true``false`,默认为`false` 在Go中布尔值的类型为`bool`,值是`true``false`,默认为`false`
```Go
//示例代码 //示例代码
var isActive bool // 全局变量声明 var isActive bool // 全局变量声明
@@ -95,7 +105,7 @@ Go 常量和一般程序语言不同的是,可以指定相当多的小数位
valid := false // 简短声明 valid := false // 简短声明
available = true // 赋值操作 available = true // 赋值操作
} }
```
### 数值类型 ### 数值类型
@@ -116,15 +126,17 @@ Go 常量和一般程序语言不同的是,可以指定相当多的小数位
浮点数的类型有`float32``float64`两种(没有`float`类型),默认是`float64` 浮点数的类型有`float32``float64`两种(没有`float`类型),默认是`float64`
这就是全部吗NoGo还支持复数。它的默认类型是`complex128`64位实数+64位虚数。如果需要小一些的也有`complex64`(32位实数+32位虚数)。复数的形式为`RE + IMi`,其中`RE`是实数部分,`IM`是虚数部分,而最后的`i`是虚数单位。下面是一个使用复数的例子: 这就是全部吗NoGo还支持复数。它的默认类型是`complex128`64位实数+64位虚数。如果需要小一些的也有`complex64`(32位实数+32位虚数)。复数的形式为`RE + IMi`,其中`RE`是实数部分,`IM`是虚数部分,而最后的`i`是虚数单位。下面是一个使用复数的例子:
```Go
var c complex64 = 5+5i var c complex64 = 5+5i
//output: (5+5i) //output: (5+5i)
fmt.Printf("Value is: %v", c) fmt.Printf("Value is: %v", c)
```
### 字符串 ### 字符串
我们在上一节中讲过Go中的字符串都是采用`UTF-8`字符集编码。字符串是用一对双引号(`""`)或反引号(`` ` `` `` ` ``)括起来定义,它的类型是`string`。 我们在上一节中讲过Go中的字符串都是采用`UTF-8`字符集编码。字符串是用一对双引号(`""`)或反引号(`` ` `` `` ` ``)括起来定义,它的类型是`string`。
```Go
//示例代码 //示例代码
var frenchHello string // 声明变量为字符串的一般方法 var frenchHello string // 声明变量为字符串的一般方法
@@ -134,35 +146,39 @@ Go 常量和一般程序语言不同的是,可以指定相当多的小数位
japaneseHello := "Konichiwa" // 同上 japaneseHello := "Konichiwa" // 同上
frenchHello = "Bonjour" // 常规赋值 frenchHello = "Bonjour" // 常规赋值
} }
```
在Go中字符串是不可变的例如下面的代码编译时会报错cannot assign to s[0] 在Go中字符串是不可变的例如下面的代码编译时会报错cannot assign to s[0]
```Go
var s string = "hello" var s string = "hello"
s[0] = 'c' s[0] = 'c'
```
但如果真的想要修改怎么办呢?下面的代码可以实现: 但如果真的想要修改怎么办呢?下面的代码可以实现:
```Go
s := "hello" s := "hello"
c := []byte(s) // 将字符串 s 转换为 []byte 类型 c := []byte(s) // 将字符串 s 转换为 []byte 类型
c[0] = 'c' c[0] = 'c'
s2 := string(c) // 再转换回 string 类型 s2 := string(c) // 再转换回 string 类型
fmt.Printf("%s\n", s2) fmt.Printf("%s\n", s2)
```
Go中可以使用`+`操作符来连接两个字符串: Go中可以使用`+`操作符来连接两个字符串:
```Go
s := "hello," s := "hello,"
m := " world" m := " world"
a := s + m a := s + m
fmt.Printf("%s\n", a) fmt.Printf("%s\n", a)
```
修改字符串也可写为: 修改字符串也可写为:
```Go
s := "hello" s := "hello"
s = "c" + s[1:] // 字符串虽不能更改,但可进行切片操作 s = "c" + s[1:] // 字符串虽不能更改,但可进行切片操作
fmt.Printf("%s\n", s) fmt.Printf("%s\n", s)
```
如果要声明一个多行的字符串怎么办?可以通过`` ` ``来声明: 如果要声明一个多行的字符串怎么办?可以通过`` ` ``来声明:
m := `hello m := `hello
@@ -175,12 +191,13 @@ Go中可以使用`+`操作符来连接两个字符串:
### 错误类型 ### 错误类型
Go内置有一个`error`类型专门用来处理错误信息Go的`package`里面还专门有一个包`errors`来处理错误: Go内置有一个`error`类型专门用来处理错误信息Go的`package`里面还专门有一个包`errors`来处理错误:
```Go
err := errors.New("emit macho dwarf: elf header corrupted") err := errors.New("emit macho dwarf: elf header corrupted")
if err != nil { if err != nil {
fmt.Print(err) fmt.Print(err)
} }
```
### Go数据底层的存储 ### Go数据底层的存储
下面这张图来源于[Russ Cox Blog](http://research.swtch.com/)中一篇介绍[Go数据结构](http://research.swtch.com/godata)的文章,大家可以看到这些基础类型底层都是分配了一块内存,然后存储了相应的值。 下面这张图来源于[Russ Cox Blog](http://research.swtch.com/)中一篇介绍[Go数据结构](http://research.swtch.com/godata)的文章,大家可以看到这些基础类型底层都是分配了一块内存,然后存储了相应的值。
@@ -196,6 +213,7 @@ Go内置有一个`error`类型专门用来处理错误信息Go的`package`
在Go语言中同时声明多个常量、变量或者导入多个包时可采用分组的方式进行声明。 在Go语言中同时声明多个常量、变量或者导入多个包时可采用分组的方式进行声明。
例如下面的代码: 例如下面的代码:
```Go
import "fmt" import "fmt"
import "os" import "os"
@@ -207,8 +225,9 @@ Go内置有一个`error`类型专门用来处理错误信息Go的`package`
var i int var i int
var pi float32 var pi float32
var prefix string var prefix string
```
可以分组写成如下形式: 可以分组写成如下形式:
```Go
import( import(
"fmt" "fmt"
@@ -226,10 +245,11 @@ Go内置有一个`error`类型专门用来处理错误信息Go的`package`
pi float32 pi float32
prefix string prefix string
) )
```
### iota枚举 ### iota枚举
Go里面有一个关键字`iota`,这个关键字用来声明`enum`的时候采用它默认开始值是0const中每增加一行加1 Go里面有一个关键字`iota`,这个关键字用来声明`enum`的时候采用它默认开始值是0const中每增加一行加1
```Go
const( const(
x = iota // x == 0 x = iota // x == 0
@@ -251,7 +271,7 @@ Go里面有一个关键字`iota`,这个关键字用来声明`enum`的时候采
d,e,f = iota,iota,iota //d=3,e=3,f=3 d,e,f = iota,iota,iota //d=3,e=3,f=3
g //g = 4 g //g = 4
```
>除非被显式设置为其它值或`iota`,每个`const`分组的第一个常量被默认设置为它的0值第二及后续的常量被默认设置为它前面那个常量的值如果前面那个常量的值是`iota`,则它也被设置为`iota`。 >除非被显式设置为其它值或`iota`,每个`const`分组的第一个常量被默认设置为它的0值第二及后续的常量被默认设置为它前面那个常量的值如果前面那个常量的值是`iota`,则它也被设置为`iota`。
### Go程序设计的一些规则 ### Go程序设计的一些规则
@@ -263,35 +283,39 @@ Go之所以会那么简洁是因为它有一些默认的行为
### array ### array
`array`就是数组,它的定义方式如下: `array`就是数组,它的定义方式如下:
```Go
var arr [n]type var arr [n]type
```
在`[n]type`中,`n`表示数组的长度,`type`表示存储元素的类型。对数组的操作和其它语言类似,都是通过`[]`来进行读取或赋值: 在`[n]type`中,`n`表示数组的长度,`type`表示存储元素的类型。对数组的操作和其它语言类似,都是通过`[]`来进行读取或赋值:
```Go
var arr [10]int // 声明了一个int类型的数组 var arr [10]int // 声明了一个int类型的数组
arr[0] = 42 // 数组下标是从0开始的 arr[0] = 42 // 数组下标是从0开始的
arr[1] = 13 // 赋值操作 arr[1] = 13 // 赋值操作
fmt.Printf("The first element is %d\n", arr[0]) // 获取数据返回42 fmt.Printf("The first element is %d\n", arr[0]) // 获取数据返回42
fmt.Printf("The last element is %d\n", arr[9]) //返回未赋值的最后一个元素默认返回0 fmt.Printf("The last element is %d\n", arr[9]) //返回未赋值的最后一个元素默认返回0
```
由于长度也是数组类型的一部分,因此`[3]int`与`[4]int`是不同的类型,数组也就不能改变长度。数组之间的赋值是值的赋值,即当把一个数组作为参数传入函数的时候,传入的其实是该数组的副本,而不是它的指针。如果要使用指针,那么就需要用到后面介绍的`slice`类型了。 由于长度也是数组类型的一部分,因此`[3]int`与`[4]int`是不同的类型,数组也就不能改变长度。数组之间的赋值是值的赋值,即当把一个数组作为参数传入函数的时候,传入的其实是该数组的副本,而不是它的指针。如果要使用指针,那么就需要用到后面介绍的`slice`类型了。
数组可以使用另一种`:=`来声明 数组可以使用另一种`:=`来声明
```Go
a := [3]int{1, 2, 3} // 声明了一个长度为3的int数组 a := [3]int{1, 2, 3} // 声明了一个长度为3的int数组
b := [10]int{1, 2, 3} // 声明了一个长度为10的int数组其中前三个元素初始化为1、2、3其它默认为0 b := [10]int{1, 2, 3} // 声明了一个长度为10的int数组其中前三个元素初始化为1、2、3其它默认为0
c := [...]int{4, 5, 6} // 可以省略长度而采用`...`的方式Go会自动根据元素个数来计算长度 c := [...]int{4, 5, 6} // 可以省略长度而采用`...`的方式Go会自动根据元素个数来计算长度
```
也许你会说我想数组里面的值还是数组能实现吗当然咯Go支持嵌套数组即多维数组。比如下面的代码就声明了一个二维数组 也许你会说我想数组里面的值还是数组能实现吗当然咯Go支持嵌套数组即多维数组。比如下面的代码就声明了一个二维数组
```Go
// 声明了一个二维数组该数组以两个数组作为元素其中每个数组中又有4个int类型的元素 // 声明了一个二维数组该数组以两个数组作为元素其中每个数组中又有4个int类型的元素
doubleArray := [2][4]int{[4]int{1, 2, 3, 4}, [4]int{5, 6, 7, 8}} 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}} easyArray := [2][4]int{{1, 2, 3, 4}, {5, 6, 7, 8}}
```
数组的分配如下所示: 数组的分配如下所示:
![](images/2.2.array.png?raw=true) ![](images/2.2.array.png?raw=true)
@@ -304,15 +328,18 @@ Go之所以会那么简洁是因为它有一些默认的行为
在很多应用场景中数组并不能满足我们的需求。在初始定义数组时我们并不知道需要多大的数组因此我们就需要“动态数组”。在Go里面这种数据结构叫`slice` 在很多应用场景中数组并不能满足我们的需求。在初始定义数组时我们并不知道需要多大的数组因此我们就需要“动态数组”。在Go里面这种数据结构叫`slice`
`slice`并不是真正意义上的动态数组,而是一个引用类型。`slice`总是指向一个底层`array``slice`的声明也可以像`array`一样,只是不需要长度。 `slice`并不是真正意义上的动态数组,而是一个引用类型。`slice`总是指向一个底层`array``slice`的声明也可以像`array`一样,只是不需要长度。
```Go
// 和声明array一样只是少了长度 // 和声明array一样只是少了长度
var fslice []int var fslice []int
```
接下来我们可以声明一个`slice`,并初始化数据,如下所示: 接下来我们可以声明一个`slice`,并初始化数据,如下所示:
```Go
slice := []byte {'a', 'b', 'c', 'd'} 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`。
```Go
// 声明一个含有10个元素元素类型为byte的数组 // 声明一个含有10个元素元素类型为byte的数组
var ar = [10]byte {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'} var ar = [10]byte {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'}
@@ -327,7 +354,7 @@ Go之所以会那么简洁是因为它有一些默认的行为
// b是数组ar的另一个slice // b是数组ar的另一个slice
b = ar[3:5] b = ar[3:5]
// b的元素是ar[3]和ar[4] // b的元素是ar[3]和ar[4]
```
>注意`slice`和数组在声明时的区别:声明数组时,方括号内写明了数组的长度或使用`...`自动计算长度,而声明`slice`时,方括号内没有任何字符。 >注意`slice`和数组在声明时的区别:声明数组时,方括号内写明了数组的长度或使用`...`自动计算长度,而声明`slice`时,方括号内没有任何字符。
它们的数据结构如下所示 它们的数据结构如下所示
@@ -343,6 +370,7 @@ slice有一些简便的操作
- 如果从一个数组里面直接获取`slice`,可以这样`ar[:]`因为默认第一个序列是0第二个是数组的长度即等价于`ar[0:len(ar)]` - 如果从一个数组里面直接获取`slice`,可以这样`ar[:]`因为默认第一个序列是0第二个是数组的长度即等价于`ar[0:len(ar)]`
下面这个例子展示了更多关于`slice`的操作: 下面这个例子展示了更多关于`slice`的操作:
```Go
// 声明一个数组 // 声明一个数组
var array = [10]byte{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'} var array = [10]byte{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'}
@@ -360,17 +388,18 @@ slice有一些简便的操作
bSlice = aSlice[:3] // bSlice 包含 aSlice[0], aSlice[1], aSlice[2] 也就是含有: d,e,f bSlice = aSlice[:3] // bSlice 包含 aSlice[0], aSlice[1], aSlice[2] 也就是含有: d,e,f
bSlice = aSlice[0:5] // 对slice的slice可以在cap范围内扩展此时bSlice包含d,e,f,g,h bSlice = aSlice[0:5] // 对slice的slice可以在cap范围内扩展此时bSlice包含d,e,f,g,h
bSlice = aSlice[:] // bSlice包含所有aSlice的元素: 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`的长度 - 长度,即`slice`的长度
- 最大长度,也就是`slice`开始位置到数组的最后位置的长度 - 最大长度,也就是`slice`开始位置到数组的最后位置的长度
```Go
Array_a := [10]byte{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'} Array_a := [10]byte{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'}
Slice_a := Array_a[2:5] Slice_a := Array_a[2:5]
```
上面代码的真正存储结构如下图所示 上面代码的真正存储结构如下图所示
![](images/2.2.slice2.png?raw=true) ![](images/2.2.slice2.png?raw=true)
@@ -388,10 +417,11 @@ slice有一些简便的操作
但当`slice`中没有剩余空间(即`(cap-len) == 0`)时,此时将动态分配新的数组空间。返回的`slice`数组指针将指向这个空间,而原数组的内容将保持不变;其它引用此数组的`slice`则不受影响。 但当`slice`中没有剩余空间(即`(cap-len) == 0`)时,此时将动态分配新的数组空间。返回的`slice`数组指针将指向这个空间,而原数组的内容将保持不变;其它引用此数组的`slice`则不受影响。
从Go1.2开始slice支持了三个参数的slice之前我们一直采用这种方式在slice或者array基础上来获取一个slice 从Go1.2开始slice支持了三个参数的slice之前我们一直采用这种方式在slice或者array基础上来获取一个slice
```Go
var array [10]int var array [10]int
slice := array[2:4] slice := array[2:4]
```
这个例子里面slice的容量是8新版本里面可以指定这个容量 这个例子里面slice的容量是8新版本里面可以指定这个容量
slice = array[2:4:7] slice = array[2:4:7]
@@ -405,6 +435,7 @@ slice有一些简便的操作
`map`也就是Python中字典的概念它的格式为`map[keyType]valueType` `map`也就是Python中字典的概念它的格式为`map[keyType]valueType`
我们看下面的代码,`map`的读取和设置也类似`slice`一样,通过`key`来操作,只是`slice`的`index`只能是int类型而`map`多了很多类型,可以是`int`,可以是`string`及所有完全定义了`==`与`!=`操作的类型。 我们看下面的代码,`map`的读取和设置也类似`slice`一样,通过`key`来操作,只是`slice`的`index`只能是int类型而`map`多了很多类型,可以是`int`,可以是`string`及所有完全定义了`==`与`!=`操作的类型。
```Go
// 声明一个key是字符串值为int的字典,这种方式的声明需要在使用之前使用make初始化 // 声明一个key是字符串值为int的字典,这种方式的声明需要在使用之前使用make初始化
var numbers map[string]int var numbers map[string]int
@@ -416,6 +447,7 @@ slice有一些简便的操作
fmt.Println("第三个数字是: ", numbers["three"]) // 读取数据 fmt.Println("第三个数字是: ", numbers["three"]) // 读取数据
// 打印出来如:第三个数字是: 3 // 打印出来如:第三个数字是: 3
```
这个`map`就像我们平常看到的表格一样,左边列是`key`,右边列是值 这个`map`就像我们平常看到的表格一样,左边列是`key`,右边列是值
@@ -430,6 +462,8 @@ slice有一些简便的操作
通过`delete`删除`map`的元素: 通过`delete`删除`map`的元素:
```Go
// 初始化一个字典 // 初始化一个字典
rating := map[string]float32{"C":5, "Go":4.5, "Python":4.5, "C++":2 } rating := map[string]float32{"C":5, "Go":4.5, "Python":4.5, "C++":2 }
// map有两个返回值第二个返回值如果不存在key那么ok为false如果存在ok为true // map有两个返回值第二个返回值如果不存在key那么ok为false如果存在ok为true
@@ -442,15 +476,16 @@ slice有一些简便的操作
delete(rating, "C") // 删除key为C的元素 delete(rating, "C") // 删除key为C的元素
```
上面说过了,`map`也是一种引用类型,如果两个`map`同时指向一个底层,那么一个改变,另一个也相应的改变: 上面说过了,`map`也是一种引用类型,如果两个`map`同时指向一个底层,那么一个改变,另一个也相应的改变:
```Go
m := make(map[string]string) m := make(map[string]string)
m["Hello"] = "Bonjour" m["Hello"] = "Bonjour"
m1 := m m1 := m
m1["Hello"] = "Salut" // 现在m["hello"]的值已经是Salut了 m1["Hello"] = "Salut" // 现在m["hello"]的值已经是Salut了
```
### make、new操作 ### make、new操作
`make`用于内建类型(`map`、`slice` 和`channel`)的内存分配。`new`用于各种类型的内存分配。 `make`用于内建类型(`map`、`slice` 和`channel`)的内存分配。`new`用于各种类型的内存分配。
@@ -473,6 +508,7 @@ slice有一些简便的操作
## 零值 ## 零值
关于“零值”所指并非是空值而是一种“变量未填充前”的默认值通常为0。 关于“零值”所指并非是空值而是一种“变量未填充前”的默认值通常为0。
此处罗列 部分类型 的 “零值” 此处罗列 部分类型 的 “零值”
```Go
int 0 int 0
int8 0 int8 0
@@ -486,6 +522,7 @@ slice有一些简便的操作
bool false bool false
string "" string ""
```
## links ## links
* [目录](<preface.md>) * [目录](<preface.md>)
* 上一章: [你好,Go](<02.1.md>) * 上一章: [你好,Go](<02.1.md>)

View File

@@ -6,14 +6,16 @@
`if`也许是各种编程语言中最常见的了,它的语法概括起来就是:如果满足条件就做某事,否则做另一件事。 `if`也许是各种编程语言中最常见的了,它的语法概括起来就是:如果满足条件就做某事,否则做另一件事。
Go里面`if`条件判断语句中不需要括号,如下代码所示 Go里面`if`条件判断语句中不需要括号,如下代码所示
```Go
if x > 10 { if x > 10 {
fmt.Println("x is greater than 10") fmt.Println("x is greater than 10")
} else { } else {
fmt.Println("x is less than 10") fmt.Println("x is less than 10")
} }
```
Go的`if`还有一个强大的地方就是条件判断语句里面允许声明一个变量,这个变量的作用域只能在该条件逻辑块内,其他地方就不起作用了,如下所示 Go的`if`还有一个强大的地方就是条件判断语句里面允许声明一个变量,这个变量的作用域只能在该条件逻辑块内,其他地方就不起作用了,如下所示
```Go
// 计算获取值x,然后根据x返回的大小判断是否大于10。 // 计算获取值x,然后根据x返回的大小判断是否大于10。
if x := computedValue(); x > 10 { if x := computedValue(); x > 10 {
@@ -24,8 +26,9 @@ Go的`if`还有一个强大的地方就是条件判断语句里面允许声明
//这个地方如果这样调用就编译出错了因为x是条件里面的变量 //这个地方如果这样调用就编译出错了因为x是条件里面的变量
fmt.Println(x) fmt.Println(x)
```
多个条件的时候如下所示: 多个条件的时候如下所示:
```Go
if integer == 3 { if integer == 3 {
fmt.Println("The integer is equal to 3") fmt.Println("The integer is equal to 3")
@@ -34,10 +37,11 @@ Go的`if`还有一个强大的地方就是条件判断语句里面允许声明
} else { } else {
fmt.Println("The integer is greater than 3") fmt.Println("The integer is greater than 3")
} }
```
### goto ### goto
Go有`goto`语句——请明智地使用它。用`goto`跳转到必须在当前函数内定义的标签。例如假设这样一个循环: Go有`goto`语句——请明智地使用它。用`goto`跳转到必须在当前函数内定义的标签。例如假设这样一个循环:
```Go
func myFunc() { func myFunc() {
i := 0 i := 0
@@ -46,21 +50,24 @@ Go有`goto`语句——请明智地使用它。用`goto`跳转到必须在当前
i++ i++
goto Here //跳转到Here去 goto Here //跳转到Here去
} }
```
>标签名是大小写敏感的。 >标签名是大小写敏感的。
### for ### for
Go里面最强大的一个控制逻辑就是`for`,它即可以用来循环读取数据,又可以当作`while`来控制逻辑,还能迭代操作。它的语法如下: Go里面最强大的一个控制逻辑就是`for`,它即可以用来循环读取数据,又可以当作`while`来控制逻辑,还能迭代操作。它的语法如下:
```Go
for expression1; expression2; expression3 { for expression1; expression2; expression3 {
//... //...
} }
```
`expression1``expression2``expression3`都是表达式,其中`expression1``expression3`是变量声明或者函数调用返回值之类的,`expression2`是用来条件判断,`expression1`在循环开始之前调用,`expression3`在每轮循环结束之时调用。 `expression1``expression2``expression3`都是表达式,其中`expression1``expression3`是变量声明或者函数调用返回值之类的,`expression2`是用来条件判断,`expression1`在循环开始之前调用,`expression3`在每轮循环结束之时调用。
一个例子比上面讲那么多更有用,那么我们看看下面的例子吧: 一个例子比上面讲那么多更有用,那么我们看看下面的例子吧:
```Go
package main package main
import "fmt" import "fmt"
func main(){ func main(){
@@ -71,25 +78,28 @@ Go里面最强大的一个控制逻辑就是`for`,它即可以用来循环读
fmt.Println("sum is equal to ", sum) fmt.Println("sum is equal to ", sum)
} }
// 输出sum is equal to 45 // 输出sum is equal to 45
```
有些时候需要进行多个赋值操作由于Go里面没有`,`操作符,那么可以使用平行赋值`i, j = i+1, j-1` 有些时候需要进行多个赋值操作由于Go里面没有`,`操作符,那么可以使用平行赋值`i, j = i+1, j-1`
有些时候如果我们忽略`expression1``expression3` 有些时候如果我们忽略`expression1``expression3`
```Go
sum := 1 sum := 1
for ; sum < 1000; { for ; sum < 1000; {
sum += sum sum += sum
} }
```
其中`;`也可以省略,那么就变成如下的代码了,是不是似曾相识?对,这就是`while`的功能。 其中`;`也可以省略,那么就变成如下的代码了,是不是似曾相识?对,这就是`while`的功能。
```Go
sum := 1 sum := 1
for sum < 1000 { for sum < 1000 {
sum += sum sum += sum
} }
```
在循环里面有两个关键操作`break``continue` ,`break`操作是跳出当前循环,`continue`是跳过本次循环。当嵌套过深的时候,`break`可以配合标签使用,即跳转至标签所指定的位置,详细参考如下例子: 在循环里面有两个关键操作`break``continue` ,`break`操作是跳出当前循环,`continue`是跳过本次循环。当嵌套过深的时候,`break`可以配合标签使用,即跳转至标签所指定的位置,详细参考如下例子:
```Go
for index := 10; index>0; index-- { for index := 10; index>0; index-- {
if index == 5{ if index == 5{
@@ -99,26 +109,29 @@ Go里面最强大的一个控制逻辑就是`for`,它即可以用来循环读
} }
// break打印出来10、9、8、7、6 // break打印出来10、9、8、7、6
// continue打印出来10、9、8、7、6、4、3、2、1 // continue打印出来10、9、8、7、6、4、3、2、1
```
`break``continue`还可以跟着标号,用来跳到多重循环中的外层循环 `break``continue`还可以跟着标号,用来跳到多重循环中的外层循环
`for`配合`range`可以用于读取`slice``map`的数据: `for`配合`range`可以用于读取`slice``map`的数据:
```Go
for k,v:=range map { for k,v:=range map {
fmt.Println("map's key:",k) fmt.Println("map's key:",k)
fmt.Println("map's val:",v) fmt.Println("map's val:",v)
} }
```
由于 Go 支持 “多值返回”, 而对于“声明而未被调用”的变量, 编译器会报错, 在这种情况下, 可以使用`_`来丢弃不需要的返回值 由于 Go 支持 “多值返回”, 而对于“声明而未被调用”的变量, 编译器会报错, 在这种情况下, 可以使用`_`来丢弃不需要的返回值
例如 例如
```Go
for _, v := range map{ for _, v := range map{
fmt.Println("map's val:", v) fmt.Println("map's val:", v)
} }
```
### switch ### switch
有些时候你需要写很多的`if-else`来实现一些逻辑处理,这个时候代码看上去就很丑很冗长,而且也不易于以后的维护,这个时候`switch`就能很好的解决这个问题。它的语法如下 有些时候你需要写很多的`if-else`来实现一些逻辑处理,这个时候代码看上去就很丑很冗长,而且也不易于以后的维护,这个时候`switch`就能很好的解决这个问题。它的语法如下
```Go
switch sExpr { switch sExpr {
case expr1: case expr1:
@@ -130,8 +143,9 @@ Go里面最强大的一个控制逻辑就是`for`,它即可以用来循环读
default: default:
other code other code
} }
```
`sExpr``expr1``expr2``expr3`的类型必须一致。Go的`switch`非常灵活,表达式不必是常量或整数,执行的过程从上至下,直到找到匹配项;而如果`switch`没有表达式,它会匹配`true` `sExpr``expr1``expr2``expr3`的类型必须一致。Go的`switch`非常灵活,表达式不必是常量或整数,执行的过程从上至下,直到找到匹配项;而如果`switch`没有表达式,它会匹配`true`
```Go
i := 10 i := 10
switch i { switch i {
@@ -144,8 +158,9 @@ Go里面最强大的一个控制逻辑就是`for`,它即可以用来循环读
default: default:
fmt.Println("All I know is that i is an integer") fmt.Println("All I know is that i is an integer")
} }
```
在第5行中我们把很多值聚合在了一个`case`里面同时Go里面`switch`默认相当于每个`case`最后带有`break`匹配成功后不会自动向下执行其他case而是跳出整个`switch`, 但是可以使用`fallthrough`强制执行后面的case代码。 在第5行中我们把很多值聚合在了一个`case`里面同时Go里面`switch`默认相当于每个`case`最后带有`break`匹配成功后不会自动向下执行其他case而是跳出整个`switch`, 但是可以使用`fallthrough`强制执行后面的case代码。
```Go
integer := 6 integer := 6
switch integer { switch integer {
@@ -167,24 +182,26 @@ Go里面最强大的一个控制逻辑就是`for`,它即可以用来循环读
default: default:
fmt.Println("default case") fmt.Println("default case")
} }
```
上面的程序将输出 上面的程序将输出
```Go
The integer was <= 6 The integer was <= 6
The integer was <= 7 The integer was <= 7
The integer was <= 8 The integer was <= 8
default case default case
```
## 函数 ## 函数
函数是Go里面的核心设计它通过关键字`func`来声明,它的格式如下: 函数是Go里面的核心设计它通过关键字`func`来声明,它的格式如下:
```Go
func funcName(input1 type1, input2 type2) (output1 type1, output2 type2) { func funcName(input1 type1, input2 type2) (output1 type1, output2 type2) {
//这里是处理逻辑代码 //这里是处理逻辑代码
//返回多个值 //返回多个值
return value1, value2 return value1, value2
} }
```
上面的代码我们看出 上面的代码我们看出
- 关键字`func`用来声明一个函数`funcName` - 关键字`func`用来声明一个函数`funcName`
@@ -196,8 +213,10 @@ Go里面最强大的一个控制逻辑就是`for`,它即可以用来循环读
- 如果有返回值, 那么必须在函数的外层添加return语句 - 如果有返回值, 那么必须在函数的外层添加return语句
下面我们来看一个实际应用函数的例子用来计算Max值 下面我们来看一个实际应用函数的例子用来计算Max值
```Go
package main package main
import "fmt" import "fmt"
// 返回a、b中最大值. // 返回a、b中最大值.
@@ -220,15 +239,17 @@ Go里面最强大的一个控制逻辑就是`for`,它即可以用来循环读
fmt.Printf("max(%d, %d) = %d\n", x, z, max_xz) fmt.Printf("max(%d, %d) = %d\n", x, z, max_xz)
fmt.Printf("max(%d, %d) = %d\n", y, z, max(y,z)) // 也可在这直接调用它 fmt.Printf("max(%d, %d) = %d\n", y, z, max(y,z)) // 也可在这直接调用它
} }
```
上面这个里面我们可以看到`max`函数有两个参数,它们的类型都是`int`,那么第一个变量的类型可以省略(即 a,b int,而非 a int, b int)默认为离它最近的类型同理多于2个同类型的变量或者返回值。同时我们注意到它的返回值就是一个类型这个就是省略写法。 上面这个里面我们可以看到`max`函数有两个参数,它们的类型都是`int`,那么第一个变量的类型可以省略(即 a,b int,而非 a int, b int)默认为离它最近的类型同理多于2个同类型的变量或者返回值。同时我们注意到它的返回值就是一个类型这个就是省略写法。
### 多个返回值 ### 多个返回值
Go语言比C更先进的特性其中一点就是函数能够返回多个值。 Go语言比C更先进的特性其中一点就是函数能够返回多个值。
我们直接上代码看例子 我们直接上代码看例子
```Go
package main package main
import "fmt" import "fmt"
//返回 A+B 和 A*B //返回 A+B 和 A*B
@@ -245,31 +266,37 @@ Go语言比C更先进的特性其中一点就是函数能够返回多个值
fmt.Printf("%d + %d = %d\n", x, y, xPLUSy) 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, xTIMESy)
} }
```
上面的例子我们可以看到直接返回了两个参数,当然我们也可以命名返回参数的变量,这个例子里面只是用了两个类型,我们也可以改成如下这样的定义,然后返回的时候不用带上变量名,因为直接在函数里面初始化了。但如果你的函数是导出的(首字母大写),官方建议:最好命名返回值,因为不命名返回值,虽然使得代码更加简洁了,但是会造成生成的文档可读性差。 上面的例子我们可以看到直接返回了两个参数,当然我们也可以命名返回参数的变量,这个例子里面只是用了两个类型,我们也可以改成如下这样的定义,然后返回的时候不用带上变量名,因为直接在函数里面初始化了。但如果你的函数是导出的(首字母大写),官方建议:最好命名返回值,因为不命名返回值,虽然使得代码更加简洁了,但是会造成生成的文档可读性差。
```Go
func SumAndProduct(A, B int) (add int, Multiplied int) { func SumAndProduct(A, B int) (add int, Multiplied int) {
add = A+B add = A+B
Multiplied = A*B Multiplied = A*B
return return
} }
```
### 变参 ### 变参
Go函数支持变参。接受变参的函数是有着不定数量的参数的。为了做到这点首先需要定义函数使其接受变参 Go函数支持变参。接受变参的函数是有着不定数量的参数的。为了做到这点首先需要定义函数使其接受变参
```Go
func myfunc(arg ...int) {} func myfunc(arg ...int) {}
```
`arg ...int`告诉Go这个函数接受不定数量的参数。注意这些参数的类型全部是`int`。在函数体中,变量`arg`是一个`int``slice` `arg ...int`告诉Go这个函数接受不定数量的参数。注意这些参数的类型全部是`int`。在函数体中,变量`arg`是一个`int``slice`
```Go
for _, n := range arg { for _, n := range arg {
fmt.Printf("And the number is: %d\n", n) fmt.Printf("And the number is: %d\n", n)
} }
```
### 传值与传指针 ### 传值与传指针
当我们传一个参数值到被调用函数里面时实际上是传了这个值的一份copy当在被调用函数中修改参数值的时候调用函数中相应实参不会发生任何变化因为数值变化只作用在copy上。 当我们传一个参数值到被调用函数里面时实际上是传了这个值的一份copy当在被调用函数中修改参数值的时候调用函数中相应实参不会发生任何变化因为数值变化只作用在copy上。
为了验证我们上面的说法,我们来看一个例子 为了验证我们上面的说法,我们来看一个例子
```Go
package main package main
import "fmt" import "fmt"
//简单的一个函数,实现了参数+1的操作 //简单的一个函数,实现了参数+1的操作
@@ -288,7 +315,7 @@ Go函数支持变参。接受变参的函数是有着不定数量的参数的。
fmt.Println("x+1 = ", x1) // 应该输出"x+1 = 4" fmt.Println("x+1 = ", x1) // 应该输出"x+1 = 4"
fmt.Println("x = ", x) // 应该输出"x = 3" fmt.Println("x = ", x) // 应该输出"x = 3"
} }
```
看到了吗?虽然我们调用了`add1`函数,并且在`add1`中执行`a = a+1`操作,但是上面例子中`x`变量的值没有发生变化 看到了吗?虽然我们调用了`add1`函数,并且在`add1`中执行`a = a+1`操作,但是上面例子中`x`变量的值没有发生变化
理由很简单:因为当我们调用`add1`的时候,`add1`接收的参数其实是`x`的copy而不是`x`本身。 理由很简单:因为当我们调用`add1`的时候,`add1`接收的参数其实是`x`的copy而不是`x`本身。
@@ -296,8 +323,10 @@ Go函数支持变参。接受变参的函数是有着不定数量的参数的。
那你也许会问了,如果真的需要传这个`x`本身,该怎么办呢? 那你也许会问了,如果真的需要传这个`x`本身,该怎么办呢?
这就牵扯到了所谓的指针。我们知道,变量在内存中是存放于一定地址上的,修改变量实际是修改变量地址处的内存。只有`add1`函数知道`x`变量所在的地址,才能修改`x`变量的值。所以我们需要将`x`所在地址`&x`传入函数,并将函数的参数的类型由`int`改为`*int`,即改为指针类型,才能在函数中修改`x`变量的值。此时参数仍然是按copy传递的只是copy的是一个指针。请看下面的例子 这就牵扯到了所谓的指针。我们知道,变量在内存中是存放于一定地址上的,修改变量实际是修改变量地址处的内存。只有`add1`函数知道`x`变量所在的地址,才能修改`x`变量的值。所以我们需要将`x`所在地址`&x`传入函数,并将函数的参数的类型由`int`改为`*int`,即改为指针类型,才能在函数中修改`x`变量的值。此时参数仍然是按copy传递的只是copy的是一个指针。请看下面的例子
```Go
package main package main
import "fmt" import "fmt"
//简单的一个函数,实现了参数+1的操作 //简单的一个函数,实现了参数+1的操作
@@ -316,7 +345,7 @@ Go函数支持变参。接受变参的函数是有着不定数量的参数的。
fmt.Println("x+1 = ", x1) // 应该输出 "x+1 = 4" fmt.Println("x+1 = ", x1) // 应该输出 "x+1 = 4"
fmt.Println("x = ", x) // 应该输出 "x = 4" fmt.Println("x = ", x) // 应该输出 "x = 4"
} }
```
这样,我们就达到了修改`x`的目的。那么到底传指针有什么好处呢? 这样,我们就达到了修改`x`的目的。那么到底传指针有什么好处呢?
- 传指针使得多个函数能操作同一个对象。 - 传指针使得多个函数能操作同一个对象。
@@ -325,6 +354,7 @@ Go函数支持变参。接受变参的函数是有着不定数量的参数的。
### defer ### defer
Go语言中有种不错的设计即延迟defer语句你可以在函数中添加多个defer语句。当函数执行到最后时这些defer语句会按照逆序执行最后该函数返回。特别是当你在进行一些打开资源的操作时遇到错误需要提前返回在返回前你需要关闭相应的资源不然很容易造成资源泄露等问题。如下代码所示我们一般写打开一个资源是这样操作的 Go语言中有种不错的设计即延迟defer语句你可以在函数中添加多个defer语句。当函数执行到最后时这些defer语句会按照逆序执行最后该函数返回。特别是当你在进行一些打开资源的操作时遇到错误需要提前返回在返回前你需要关闭相应的资源不然很容易造成资源泄露等问题。如下代码所示我们一般写打开一个资源是这样操作的
```Go
func ReadWrite() bool { func ReadWrite() bool {
file.Open("file") file.Open("file")
@@ -342,8 +372,9 @@ Go语言中有种不错的设计即延迟defer语句你可以在函
file.Close() file.Close()
return true return true
} }
```
我们看到上面有很多重复的代码Go的`defer`有效解决了这个问题。使用它后,不但代码量减少了很多,而且程序变得更优雅。在`defer`后指定的函数会在函数退出前调用。 我们看到上面有很多重复的代码Go的`defer`有效解决了这个问题。使用它后,不但代码量减少了很多,而且程序变得更优雅。在`defer`后指定的函数会在函数退出前调用。
```Go
func ReadWrite() bool { func ReadWrite() bool {
file.Open("file") file.Open("file")
@@ -356,13 +387,14 @@ Go语言中有种不错的设计即延迟defer语句你可以在函
} }
return true return true
} }
```
如果有很多调用`defer`,那么`defer`是采用后进先出模式,所以如下代码会输出`4 3 2 1 0` 如果有很多调用`defer`,那么`defer`是采用后进先出模式,所以如下代码会输出`4 3 2 1 0`
```Go
for i := 0; i < 5; i++ { for i := 0; i < 5; i++ {
defer fmt.Printf("%d ", i) defer fmt.Printf("%d ", i)
} }
```
### 函数作为值、类型 ### 函数作为值、类型
在Go中函数也是一种变量我们可以通过`type`来定义它,它的类型就是所有拥有相同的参数,相同的返回值的一种类型 在Go中函数也是一种变量我们可以通过`type`来定义它,它的类型就是所有拥有相同的参数,相同的返回值的一种类型
@@ -370,8 +402,10 @@ Go语言中有种不错的设计即延迟defer语句你可以在函
type typeName func(input1 inputType1 , input2 inputType2 [, ...]) (result1 resultType1 [, ...]) type typeName func(input1 inputType1 , input2 inputType2 [, ...]) (result1 resultType1 [, ...])
函数作为类型到底有什么好处呢?那就是可以把这个类型的函数当做值来传递,请看下面的例子 函数作为类型到底有什么好处呢?那就是可以把这个类型的函数当做值来传递,请看下面的例子
```Go
package main package main
import "fmt" import "fmt"
type testInt func(int) bool // 声明了一个函数类型 type testInt func(int) bool // 声明了一个函数类型
@@ -410,7 +444,7 @@ Go语言中有种不错的设计即延迟defer语句你可以在函
even := filter(slice, isEven) // 函数当做值来传递了 even := filter(slice, isEven) // 函数当做值来传递了
fmt.Println("Even elements of slice are: ", even) fmt.Println("Even elements of slice are: ", even)
} }
```
函数当做值和类型在我们写一些通用接口的时候非常有用,通过上面例子我们看到`testInt`这个类型是一个函数类型,然后两个`filter`函数的参数和返回值与`testInt`类型是一样的,但是我们可以实现很多种的逻辑,这样使得我们的程序变得非常的灵活。 函数当做值和类型在我们写一些通用接口的时候非常有用,通过上面例子我们看到`testInt`这个类型是一个函数类型,然后两个`filter`函数的参数和返回值与`testInt`类型是一样的,但是我们可以实现很多种的逻辑,这样使得我们的程序变得非常的灵活。
### Panic和Recover ### Panic和Recover
@@ -424,6 +458,7 @@ Recover
>是一个内建的函数,可以让进入令人恐慌的流程中的`goroutine`恢复过来。`recover`仅在延迟函数中有效。在正常的执行过程中,调用`recover`会返回`nil`,并且没有其它任何效果。如果当前的`goroutine`陷入恐慌,调用`recover`可以捕获到`panic`的输入值,并且恢复正常的执行。 >是一个内建的函数,可以让进入令人恐慌的流程中的`goroutine`恢复过来。`recover`仅在延迟函数中有效。在正常的执行过程中,调用`recover`会返回`nil`,并且没有其它任何效果。如果当前的`goroutine`陷入恐慌,调用`recover`可以捕获到`panic`的输入值,并且恢复正常的执行。
下面这个函数演示了如何在过程中使用`panic` 下面这个函数演示了如何在过程中使用`panic`
```Go
var user = os.Getenv("USER") var user = os.Getenv("USER")
@@ -432,8 +467,9 @@ Recover
panic("no value for $USER") panic("no value for $USER")
} }
} }
```
下面这个函数检查作为其参数的函数在执行时是否会产生`panic` 下面这个函数检查作为其参数的函数在执行时是否会产生`panic`
```Go
func throwsPanic(f func()) (b bool) { func throwsPanic(f func()) (b bool) {
defer func() { defer func() {
@@ -444,7 +480,7 @@ Recover
f() //执行函数f如果f中出现了panic那么就可以恢复回来 f() //执行函数f如果f中出现了panic那么就可以恢复回来
return return
} }
```
### `main`函数和`init`函数 ### `main`函数和`init`函数
Go里面有两个保留的函数`init`函数(能够应用于所有的`package`)和`main`函数(只能应用于`package main`)。这两个函数在定义时不能有任何的参数和返回值。虽然一个`package`里面可以写任意多个`init`函数,但这无论是对于可读性还是以后的可维护性来说,我们都强烈建议用户在一个`package`中每个文件只写一个`init`函数。 Go里面有两个保留的函数`init`函数(能够应用于所有的`package`)和`main`函数(只能应用于`package main`)。这两个函数在定义时不能有任何的参数和返回值。虽然一个`package`里面可以写任意多个`init`函数,但这无论是对于可读性还是以后的可维护性来说,我们都强烈建议用户在一个`package`中每个文件只写一个`init`函数。
@@ -459,15 +495,17 @@ Go程序会自动调用`init()`和`main()`,所以你不需要在任何地方
### import ### import
我们在写Go代码的时候经常用到import这个命令用来导入包文件而我们经常看到的方式参考如下 我们在写Go代码的时候经常用到import这个命令用来导入包文件而我们经常看到的方式参考如下
```Go
import( import(
"fmt" "fmt"
) )
```
然后我们代码里面可以通过如下的方式调用 然后我们代码里面可以通过如下的方式调用
```Go
fmt.Println("hello world") fmt.Println("hello world")
```
上面这个fmt是Go语言的标准库其实是去`GOROOT`环境变量指定目录下去加载该模块当然Go的import还支持如下两种方式来加载自己写的模块 上面这个fmt是Go语言的标准库其实是去`GOROOT`环境变量指定目录下去加载该模块当然Go的import还支持如下两种方式来加载自己写的模块
1. 相对路径 1. 相对路径
@@ -505,12 +543,13 @@ Go程序会自动调用`init()`和`main()`,所以你不需要在任何地方
3. _操作 3. _操作
这个操作经常是让很多人费解的一个操作符请看下面这个import 这个操作经常是让很多人费解的一个操作符请看下面这个import
```Go
import ( import (
"database/sql" "database/sql"
_ "github.com/ziutek/mymysql/godrv" _ "github.com/ziutek/mymysql/godrv"
) )
```
_操作其实是引入该包而不直接使用包里面的函数而是调用了该包里面的init函数。 _操作其实是引入该包而不直接使用包里面的函数而是调用了该包里面的init函数。

View File

@@ -1,16 +1,19 @@
# 2.4 struct类型 # 2.4 struct类型
## struct ## struct
Go语言中也和C或者其他语言一样我们可以声明新的类型作为其它类型的属性或字段的容器。例如我们可以创建一个自定义类型`person`代表一个人的实体。这个实体拥有属性:姓名和年龄。这样的类型我们称之`struct`。如下代码所示: Go语言中也和C或者其他语言一样我们可以声明新的类型作为其它类型的属性或字段的容器。例如我们可以创建一个自定义类型`person`代表一个人的实体。这个实体拥有属性:姓名和年龄。这样的类型我们称之`struct`。如下代码所示:
```Go
type person struct { type person struct {
name string name string
age int age int
} }
```
看到了吗声明一个struct如此简单上面的类型包含有两个字段 看到了吗声明一个struct如此简单上面的类型包含有两个字段
- 一个string类型的字段name用来保存用户名称这个属性 - 一个string类型的字段name用来保存用户名称这个属性
- 一个int类型的字段age,用来保存用户年龄这个属性 - 一个int类型的字段age,用来保存用户年龄这个属性
如何使用struct呢请看下面的代码 如何使用struct呢请看下面的代码
```Go
type person struct { type person struct {
name string name string
@@ -22,6 +25,7 @@ Go语言中也和C或者其他语言一样我们可以声明新的类型
P.name = "Astaxie" // 赋值"Astaxie"给P的name属性. P.name = "Astaxie" // 赋值"Astaxie"给P的name属性.
P.age = 25 // 赋值"25"给变量P的age属性 P.age = 25 // 赋值"25"给变量P的age属性
fmt.Printf("The person's name is %s", P.name) // 访问P的name属性. fmt.Printf("The person's name is %s", P.name) // 访问P的name属性.
```
除了上面这种P的声明使用之外还有另外几种声明使用方式 除了上面这种P的声明使用之外还有另外几种声明使用方式
- 1.按照顺序提供初始化值 - 1.按照顺序提供初始化值
@@ -37,8 +41,10 @@ Go语言中也和C或者其他语言一样我们可以声明新的类型
P := new(person) P := new(person)
下面我们看一个完整的使用struct的例子 下面我们看一个完整的使用struct的例子
```Go
package main package main
import "fmt" import "fmt"
// 声明一个新的类型 // 声明一个新的类型
@@ -81,15 +87,17 @@ Go语言中也和C或者其他语言一样我们可以声明新的类型
fmt.Printf("Of %s and %s, %s is older by %d years\n", fmt.Printf("Of %s and %s, %s is older by %d years\n",
bob.name, paul.name, bp_Older.name, bp_diff) bob.name, paul.name, bp_Older.name, bp_diff)
} }
```
### struct的匿名字段 ### struct的匿名字段
我们上面介绍了如何定义一个struct定义的时候是字段名与其类型一一对应实际上Go支持只提供类型而不写字段名的方式也就是匿名字段也称为嵌入字段。 我们上面介绍了如何定义一个struct定义的时候是字段名与其类型一一对应实际上Go支持只提供类型而不写字段名的方式也就是匿名字段也称为嵌入字段。
当匿名字段是一个struct的时候那么这个struct所拥有的全部字段都被隐式地引入了当前定义的这个struct。 当匿名字段是一个struct的时候那么这个struct所拥有的全部字段都被隐式地引入了当前定义的这个struct。
让我们来看一个例子,让上面说的这些更具体化 让我们来看一个例子,让上面说的这些更具体化
```Go
package main package main
import "fmt" import "fmt"
type Human struct { type Human struct {
@@ -125,7 +133,7 @@ Go语言中也和C或者其他语言一样我们可以声明新的类型
mark.weight += 60 mark.weight += 60
fmt.Println("His weight is", mark.weight) fmt.Println("His weight is", mark.weight)
} }
```
图例如下: 图例如下:
![](images/2.4.student_struct.png?raw=true) ![](images/2.4.student_struct.png?raw=true)
@@ -133,13 +141,16 @@ Go语言中也和C或者其他语言一样我们可以声明新的类型
图2.7 struct组合Student组合了Human struct和string基本类型 图2.7 struct组合Student组合了Human struct和string基本类型
我们看到Student访问属性age和name的时候就像访问自己所有用的字段一样匿名字段就是这样能够实现字段的继承。是不是很酷啊还有比这个更酷的呢那就是student还能访问Human这个字段作为字段名。请看下面的代码是不是更酷了。 我们看到Student访问属性age和name的时候就像访问自己所有用的字段一样匿名字段就是这样能够实现字段的继承。是不是很酷啊还有比这个更酷的呢那就是student还能访问Human这个字段作为字段名。请看下面的代码是不是更酷了。
```Go
mark.Human = Human{"Marcus", 55, 220} mark.Human = Human{"Marcus", 55, 220}
mark.Human.age -= 1 mark.Human.age -= 1
```
通过匿名访问和修改字段相当的有用但是不仅仅是struct字段哦所有的内置类型和自定义类型都是可以作为匿名字段的。请看下面的例子 通过匿名访问和修改字段相当的有用但是不仅仅是struct字段哦所有的内置类型和自定义类型都是可以作为匿名字段的。请看下面的例子
```Go
package main package main
import "fmt" import "fmt"
type Skills []string type Skills []string
@@ -175,7 +186,7 @@ Go语言中也和C或者其他语言一样我们可以声明新的类型
jane.int = 3 jane.int = 3
fmt.Println("Her preferred number is", jane.int) fmt.Println("Her preferred number is", jane.int)
} }
```
从上面例子我们看出来struct不仅仅能够将struct作为匿名字段、自定义类型、内置类型都可以作为匿名字段而且可以在相应的字段上面进行函数操作如例子中的append 从上面例子我们看出来struct不仅仅能够将struct作为匿名字段、自定义类型、内置类型都可以作为匿名字段而且可以在相应的字段上面进行函数操作如例子中的append
这里有一个问题如果human里面有一个字段叫做phone而student也有一个字段叫做phone那么该怎么办呢 这里有一个问题如果human里面有一个字段叫做phone而student也有一个字段叫做phone那么该怎么办呢
@@ -183,8 +194,10 @@ Go语言中也和C或者其他语言一样我们可以声明新的类型
Go里面很简单的解决了这个问题最外层的优先访问也就是当你通过`student.phone`访问的时候是访问student里面的字段而不是human里面的字段。 Go里面很简单的解决了这个问题最外层的优先访问也就是当你通过`student.phone`访问的时候是访问student里面的字段而不是human里面的字段。
这样就允许我们去重载通过匿名字段继承的一些字段,当然如果我们想访问重载后对应匿名类型里面的字段,可以通过匿名字段名来访问。请看下面的例子 这样就允许我们去重载通过匿名字段继承的一些字段,当然如果我们想访问重载后对应匿名类型里面的字段,可以通过匿名字段名来访问。请看下面的例子
```Go
package main package main
import "fmt" import "fmt"
type Human struct { type Human struct {
@@ -205,7 +218,7 @@ Go里面很简单的解决了这个问题最外层的优先访问也就是
// 如果我们要访问Human的phone字段 // 如果我们要访问Human的phone字段
fmt.Println("Bob's personal phone is:", Bob.Human.phone) fmt.Println("Bob's personal phone is:", Bob.Human.phone)
} }
```
## links ## links
* [目录](<preface.md>) * [目录](<preface.md>)

View File

@@ -3,8 +3,10 @@
## method ## method
现在假设有这么一个场景你定义了一个struct叫做长方形你现在想要计算他的面积那么按照我们一般的思路应该会用下面的方式来实现 现在假设有这么一个场景你定义了一个struct叫做长方形你现在想要计算他的面积那么按照我们一般的思路应该会用下面的方式来实现
```Go
package main package main
import "fmt" import "fmt"
type Rectangle struct { type Rectangle struct {
@@ -21,7 +23,7 @@
fmt.Println("Area of r1 is: ", area(r1)) fmt.Println("Area of r1 is: ", area(r1))
fmt.Println("Area of r2 is: ", area(r2)) fmt.Println("Area of r2 is: ", area(r2))
} }
```
这段代码可以计算出来长方形的面积但是area()不是作为Rectangle的方法实现的类似面向对象里面的方法而是将Rectangle的对象如r1,r2作为参数传入函数计算面积的。 这段代码可以计算出来长方形的面积但是area()不是作为Rectangle的方法实现的类似面向对象里面的方法而是将Rectangle的对象如r1,r2作为参数传入函数计算面积的。
这样实现当然没有问题咯,但是当需要增加圆形、正方形、五边形甚至其它多边形的时候,你想计算他们的面积的时候怎么办啊?那就只能增加新的函数咯,但是函数名你就必须要跟着换了,变成`area_rectangle, area_circle, area_triangle...` 这样实现当然没有问题咯,但是当需要增加圆形、正方形、五边形甚至其它多边形的时候,你想计算他们的面积的时候怎么办啊?那就只能增加新的函数咯,但是函数名你就必须要跟着换了,变成`area_rectangle, area_circle, area_triangle...`
@@ -49,8 +51,10 @@ method的语法如下
func (r ReceiverType) funcName(parameters) (results) func (r ReceiverType) funcName(parameters) (results)
下面我们用最开始的例子用method来实现 下面我们用最开始的例子用method来实现
```Go
package main package main
import ( import (
"fmt" "fmt"
"math" "math"
@@ -85,7 +89,7 @@ method的语法如下
fmt.Println("Area of c2 is: ", c2.area()) fmt.Println("Area of c2 is: ", c2.area())
} }
```
在使用method的时候重要注意几点 在使用method的时候重要注意几点
@@ -104,10 +108,12 @@ method的语法如下
>值得说明的一点是图示中method用虚线标出意思是此处方法的Receiver是以值传递而非引用传递是的Receiver还可以是指针, 两者的差别在于, 指针作为Receiver会对实例对象的内容发生操作,而普通类型作为Receiver仅仅是以副本作为操作对象,并不对原实例对象发生操作。后文对此会有详细论述。 >值得说明的一点是图示中method用虚线标出意思是此处方法的Receiver是以值传递而非引用传递是的Receiver还可以是指针, 两者的差别在于, 指针作为Receiver会对实例对象的内容发生操作,而普通类型作为Receiver仅仅是以副本作为操作对象,并不对原实例对象发生操作。后文对此会有详细论述。
那是不是method只能作用在struct上面呢当然不是咯他可以定义在任何你自定义的类型、内置类型、struct等各种类型上面。这里你是不是有点迷糊了什么叫自定义类型自定义类型不就是struct嘛不是这样的哦struct只是自定义类型里面一种比较特殊的类型而已还有其他自定义类型申明可以通过如下这样的申明来实现。 那是不是method只能作用在struct上面呢当然不是咯他可以定义在任何你自定义的类型、内置类型、struct等各种类型上面。这里你是不是有点迷糊了什么叫自定义类型自定义类型不就是struct嘛不是这样的哦struct只是自定义类型里面一种比较特殊的类型而已还有其他自定义类型申明可以通过如下这样的申明来实现。
```Go
type typeName typeLiteral type typeName typeLiteral
```
请看下面这个申明自定义类型的代码 请看下面这个申明自定义类型的代码
```Go
type ages int type ages int
@@ -121,14 +127,16 @@ method的语法如下
... ...
"December":31, "December":31,
} }
```
看到了吗?简单的很吧,这样你就可以在自己的代码里面定义有意义的类型了,实际上只是一个定义了一个别名,有点类似于c中的typedef例如上面ages替代了int 看到了吗?简单的很吧,这样你就可以在自己的代码里面定义有意义的类型了,实际上只是一个定义了一个别名,有点类似于c中的typedef例如上面ages替代了int
好了,让我们回到`method` 好了,让我们回到`method`
你可以在任何的自定义类型中定义任意多的`method`,接下来让我们看一个复杂一点的例子 你可以在任何的自定义类型中定义任意多的`method`,接下来让我们看一个复杂一点的例子
```Go
package main package main
import "fmt" import "fmt"
const( const(
@@ -200,7 +208,7 @@ method的语法如下
fmt.Println("Obviously, now, the biggest one is", boxes.BiggestColor().String()) fmt.Println("Obviously, now, the biggest one is", boxes.BiggestColor().String())
} }
```
上面的代码通过const定义了一些常量然后定义了一些自定义类型 上面的代码通过const定义了一些常量然后定义了一些自定义类型
- Color作为byte的别名 - Color作为byte的别名
@@ -242,8 +250,10 @@ method的语法如下
### method继承 ### method继承
前面一章我们学习了字段的继承那么你也会发现Go的一个神奇之处method也是可以继承的。如果匿名字段实现了一个method那么包含这个匿名字段的struct也能调用该method。让我们来看下面这个例子 前面一章我们学习了字段的继承那么你也会发现Go的一个神奇之处method也是可以继承的。如果匿名字段实现了一个method那么包含这个匿名字段的struct也能调用该method。让我们来看下面这个例子
```Go
package main package main
import "fmt" import "fmt"
type Human struct { type Human struct {
@@ -274,11 +284,13 @@ method的语法如下
mark.SayHi() mark.SayHi()
sam.SayHi() sam.SayHi()
} }
```
### method重写 ### method重写
上面的例子中如果Employee想要实现自己的SayHi,怎么办简单和匿名字段冲突一样的道理我们可以在Employee上面定义一个method重写了匿名字段的方法。请看下面的例子 上面的例子中如果Employee想要实现自己的SayHi,怎么办简单和匿名字段冲突一样的道理我们可以在Employee上面定义一个method重写了匿名字段的方法。请看下面的例子
```Go
package main package main
import "fmt" import "fmt"
type Human struct { type Human struct {
@@ -315,7 +327,7 @@ method的语法如下
mark.SayHi() mark.SayHi()
sam.SayHi() sam.SayHi()
} }
```
上面的代码设计的是如此的美妙让人不自觉的为Go的设计惊叹 上面的代码设计的是如此的美妙让人不自觉的为Go的设计惊叹
通过这些内容我们可以设计出基本的面向对象的程序了但是Go里面的面向对象是如此的简单没有任何的私有、公有关键字通过大小写来实现(大写开头的为公有,小写开头的为私有),方法也同样适用这个原则。 通过这些内容我们可以设计出基本的面向对象的程序了但是Go里面的面向对象是如此的简单没有任何的私有、公有关键字通过大小写来实现(大写开头的为公有,小写开头的为私有),方法也同样适用这个原则。

View File

@@ -14,6 +14,7 @@ Go语言里面设计最精妙的应该算interface它让面向对象内容
上面这些方法的组合称为interface(被对象Student和Employee实现)。例如Student和Employee都实现了interfaceSayHi和Sing也就是这两个对象是该interface类型。而Employee没有实现这个interfaceSayHi、Sing和BorrowMoney因为Employee没有实现BorrowMoney这个方法。 上面这些方法的组合称为interface(被对象Student和Employee实现)。例如Student和Employee都实现了interfaceSayHi和Sing也就是这两个对象是该interface类型。而Employee没有实现这个interfaceSayHi、Sing和BorrowMoney因为Employee没有实现BorrowMoney这个方法。
### interface类型 ### interface类型
interface类型定义了一组方法如果某个对象实现了某个接口的所有方法则此对象就实现了此接口。详细的语法参考下面这个例子 interface类型定义了一组方法如果某个对象实现了某个接口的所有方法则此对象就实现了此接口。详细的语法参考下面这个例子
```Go
type Human struct { type Human struct {
name string name string
@@ -82,7 +83,7 @@ interface类型定义了一组方法如果某个对象实现了某个接口
Sing(song string) Sing(song string)
SpendSalary(amount float32) SpendSalary(amount float32)
} }
```
通过上面的代码我们可以知道interface可以被任意的对象实现。我们看到上面的Men interface被Human、Student和Employee实现。同理一个对象可以实现任意多个interface例如上面的Student实现了Men和YoungChap两个interface。 通过上面的代码我们可以知道interface可以被任意的对象实现。我们看到上面的Men interface被Human、Student和Employee实现。同理一个对象可以实现任意多个interface例如上面的Student实现了Men和YoungChap两个interface。
最后任意的类型都实现了空interface(我们这样定义interface{})也就是包含0个method的interface。 最后任意的类型都实现了空interface(我们这样定义interface{})也就是包含0个method的interface。
@@ -93,8 +94,10 @@ interface类型定义了一组方法如果某个对象实现了某个接口
因为m能够持有这三种类型的对象所以我们可以定义一个包含Men类型元素的slice这个slice可以被赋予实现了Men接口的任意结构的对象这个和我们传统意义上面的slice有所不同。 因为m能够持有这三种类型的对象所以我们可以定义一个包含Men类型元素的slice这个slice可以被赋予实现了Men接口的任意结构的对象这个和我们传统意义上面的slice有所不同。
让我们来看一下下面这个例子: 让我们来看一下下面这个例子:
```Go
package main package main
import "fmt" import "fmt"
type Human struct { type Human struct {
@@ -169,11 +172,12 @@ interface类型定义了一组方法如果某个对象实现了某个接口
value.SayHi() value.SayHi()
} }
} }
```
通过上面的代码你会发现interface就是一组抽象方法的集合它必须由其他非interface类型实现而不能自我实现 Go通过interface实现了duck-typing:即"当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子"。 通过上面的代码你会发现interface就是一组抽象方法的集合它必须由其他非interface类型实现而不能自我实现 Go通过interface实现了duck-typing:即"当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子"。
### 空interface ### 空interface
空interface(interface{})不包含任何的method正因为如此所有的类型都实现了空interface。空interface对于描述起不到任何的作用(因为它不包含任何的method但是空interface在我们需要存储任意类型的数值的时候相当有用因为它可以存储任意类型的数值。它有点类似于C语言的void*类型。 空interface(interface{})不包含任何的method正因为如此所有的类型都实现了空interface。空interface对于描述起不到任何的作用(因为它不包含任何的method但是空interface在我们需要存储任意类型的数值的时候相当有用因为它可以存储任意类型的数值。它有点类似于C语言的void*类型。
```Go
// 定义a为空接口 // 定义a为空接口
var a interface{} var a interface{}
@@ -182,17 +186,20 @@ interface类型定义了一组方法如果某个对象实现了某个接口
// a可以存储任意类型的数值 // a可以存储任意类型的数值
a = i a = i
a = s a = s
```
一个函数把interface{}作为参数那么他可以接受任意类型的值作为参数如果一个函数返回interface{},那么也就可以返回任意类型的值。是不是很有用啊! 一个函数把interface{}作为参数那么他可以接受任意类型的值作为参数如果一个函数返回interface{},那么也就可以返回任意类型的值。是不是很有用啊!
### interface函数参数 ### interface函数参数
interface的变量可以持有任意实现该interface类型的对象这给我们编写函数(包括method)提供了一些额外的思考我们是不是可以通过定义interface参数让函数接受各种类型的参数。 interface的变量可以持有任意实现该interface类型的对象这给我们编写函数(包括method)提供了一些额外的思考我们是不是可以通过定义interface参数让函数接受各种类型的参数。
举个例子fmt.Println是我们常用的一个函数但是你是否注意到它可以接受任意类型的数据。打开fmt的源码文件你会看到这样一个定义: 举个例子fmt.Println是我们常用的一个函数但是你是否注意到它可以接受任意类型的数据。打开fmt的源码文件你会看到这样一个定义:
```Go
type Stringer interface { type Stringer interface {
String() string String() string
} }
```
也就是说任何实现了String方法的类型都能作为参数被fmt.Println调用,让我们来试一试 也就是说任何实现了String方法的类型都能作为参数被fmt.Println调用,让我们来试一试
```Go
package main package main
import ( import (
@@ -215,12 +222,14 @@ interface的变量可以持有任意实现该interface类型的对象这给
Bob := Human{"Bob", 39, "000-7777-XXX"} Bob := Human{"Bob", 39, "000-7777-XXX"}
fmt.Println("This Human is : ", Bob) fmt.Println("This Human is : ", Bob)
} }
```
现在我们再回顾一下前面的Box示例你会发现Color结构也定义了一个methodString。其实这也是实现了fmt.Stringer这个interface即如果需要某个类型能被fmt包以特殊的格式输出你就必须实现Stringer这个接口。如果没有实现这个接口fmt将以默认的方式输出。 现在我们再回顾一下前面的Box示例你会发现Color结构也定义了一个methodString。其实这也是实现了fmt.Stringer这个interface即如果需要某个类型能被fmt包以特殊的格式输出你就必须实现Stringer这个接口。如果没有实现这个接口fmt将以默认的方式输出。
```Go
//实现同样的功能 //实现同样的功能
fmt.Println("The biggest one is", boxes.BiggestsColor().String()) fmt.Println("The biggest one is", boxes.BiggestsColor().String())
fmt.Println("The biggest one is", boxes.BiggestsColor()) fmt.Println("The biggest one is", boxes.BiggestsColor())
```
实现了error接口的对象即实现了Error() string的对象使用fmt输出时会调用Error()方法因此不必再定义String()方法了。 实现了error接口的对象即实现了Error() string的对象使用fmt输出时会调用Error()方法因此不必再定义String()方法了。
### interface变量存储的类型 ### interface变量存储的类型
@@ -233,6 +242,7 @@ interface的变量可以持有任意实现该interface类型的对象这给
如果element里面确实存储了T类型的数值那么ok返回true否则返回false。 如果element里面确实存储了T类型的数值那么ok返回true否则返回false。
让我们通过一个例子来更加深入的理解。 让我们通过一个例子来更加深入的理解。
```Go
package main package main
@@ -272,13 +282,14 @@ interface的变量可以持有任意实现该interface类型的对象这给
} }
} }
} }
```
是不是很简单啊同时你是否注意到了多个if里面还记得我前面介绍流程时讲过if里面允许初始化变量。 是不是很简单啊同时你是否注意到了多个if里面还记得我前面介绍流程时讲过if里面允许初始化变量。
也许你注意到了我们断言的类型越多那么if else也就越多所以才引出了下面要介绍的switch。 也许你注意到了我们断言的类型越多那么if else也就越多所以才引出了下面要介绍的switch。
- switch测试 - switch测试
最好的讲解就是代码例子,现在让我们重写上面的这个实现 最好的讲解就是代码例子,现在让我们重写上面的这个实现
```Go
package main package main
@@ -319,21 +330,23 @@ interface的变量可以持有任意实现该interface类型的对象这给
} }
} }
} }
```
这里有一点需要强调的是:`element.(type)`语法不能在switch外的任何逻辑里面使用如果你要在switch外面判断一个类型就使用`comma-ok` 这里有一点需要强调的是:`element.(type)`语法不能在switch外的任何逻辑里面使用如果你要在switch外面判断一个类型就使用`comma-ok`
### 嵌入interface ### 嵌入interface
Go里面真正吸引人的是它内置的逻辑语法就像我们在学习Struct时学习的匿名字段多么的优雅啊那么相同的逻辑引入到interface里面那不是更加完美了。如果一个interface1作为interface2的一个嵌入字段那么interface2隐式的包含了interface1里面的method。 Go里面真正吸引人的是它内置的逻辑语法就像我们在学习Struct时学习的匿名字段多么的优雅啊那么相同的逻辑引入到interface里面那不是更加完美了。如果一个interface1作为interface2的一个嵌入字段那么interface2隐式的包含了interface1里面的method。
我们可以看到源码包container/heap里面有这样的一个定义 我们可以看到源码包container/heap里面有这样的一个定义
```Go
type Interface interface { type Interface interface {
sort.Interface //嵌入字段sort.Interface sort.Interface //嵌入字段sort.Interface
Push(x interface{}) //a Push method to push elements into the heap Push(x interface{}) //a Push method to push elements into the heap
Pop() interface{} //a Pop elements that pops elements from the heap Pop() interface{} //a Pop elements that pops elements from the heap
} }
```
我们看到sort.Interface其实就是嵌入字段把sort.Interface的所有method给隐式的包含进来了。也就是下面三个方法 我们看到sort.Interface其实就是嵌入字段把sort.Interface的所有method给隐式的包含进来了。也就是下面三个方法
```Go
type Interface interface { type Interface interface {
// Len is the number of elements in the collection. // Len is the number of elements in the collection.
@@ -344,49 +357,55 @@ Go里面真正吸引人的是它内置的逻辑语法就像我们在学习Str
// Swap swaps the elements with indexes i and j. // Swap swaps the elements with indexes i and j.
Swap(i, j int) Swap(i, j int)
} }
```
另一个例子就是io包下面的 io.ReadWriter 它包含了io包下面的Reader和Writer两个interface 另一个例子就是io包下面的 io.ReadWriter 它包含了io包下面的Reader和Writer两个interface
```Go
// io.ReadWriter // io.ReadWriter
type ReadWriter interface { type ReadWriter interface {
Reader Reader
Writer Writer
} }
```
### 反射 ### 反射
Go语言实现了反射所谓反射就是能检查程序在运行时的状态。我们一般用到的包是reflect包。如何运用reflect包官方的这篇文章详细的讲解了reflect包的实现原理[laws of reflection](http://golang.org/doc/articles/laws_of_reflection.html) Go语言实现了反射所谓反射就是能检查程序在运行时的状态。我们一般用到的包是reflect包。如何运用reflect包官方的这篇文章详细的讲解了reflect包的实现原理[laws of reflection](http://golang.org/doc/articles/laws_of_reflection.html)
使用reflect一般分成三步下面简要的讲解一下要去反射是一个类型的值(这些值都实现了空interface)首先需要把它转化成reflect对象(reflect.Type或者reflect.Value根据不同的情况调用不同的函数)。这两种获取方式如下: 使用reflect一般分成三步下面简要的讲解一下要去反射是一个类型的值(这些值都实现了空interface)首先需要把它转化成reflect对象(reflect.Type或者reflect.Value根据不同的情况调用不同的函数)。这两种获取方式如下:
```Go
t := reflect.TypeOf(i) //得到类型的元数据,通过t我们能获取类型定义里面的所有元素 t := reflect.TypeOf(i) //得到类型的元数据,通过t我们能获取类型定义里面的所有元素
v := reflect.ValueOf(i) //得到实际的值通过v我们获取存储在里面的值还可以去改变值 v := reflect.ValueOf(i) //得到实际的值通过v我们获取存储在里面的值还可以去改变值
```
转化为reflect对象之后我们就可以进行一些操作了也就是将reflect对象转化成相应的值例如 转化为reflect对象之后我们就可以进行一些操作了也就是将reflect对象转化成相应的值例如
```Go
tag := t.Elem().Field(0).Tag //获取定义在struct里面的标签 tag := t.Elem().Field(0).Tag //获取定义在struct里面的标签
name := v.Elem().Field(0).String() //获取存储在第一个字段里面的值 name := v.Elem().Field(0).String() //获取存储在第一个字段里面的值
```
获取反射值能返回相应的类型和数值 获取反射值能返回相应的类型和数值
```Go
var x float64 = 3.4 var x float64 = 3.4
v := reflect.ValueOf(x) v := reflect.ValueOf(x)
fmt.Println("type:", v.Type()) fmt.Println("type:", v.Type())
fmt.Println("kind is float64:", v.Kind() == reflect.Float64) fmt.Println("kind is float64:", v.Kind() == reflect.Float64)
fmt.Println("value:", v.Float()) fmt.Println("value:", v.Float())
```
最后,反射的话,那么反射的字段必须是可修改的,我们前面学习过传值和传引用,这个里面也是一样的道理。反射的字段必须是可读写的意思是,如果下面这样写,那么会发生错误 最后,反射的话,那么反射的字段必须是可修改的,我们前面学习过传值和传引用,这个里面也是一样的道理。反射的字段必须是可读写的意思是,如果下面这样写,那么会发生错误
```Go
var x float64 = 3.4 var x float64 = 3.4
v := reflect.ValueOf(x) v := reflect.ValueOf(x)
v.SetFloat(7.1) v.SetFloat(7.1)
```
如果要修改相应的值,必须这样写 如果要修改相应的值,必须这样写
```Go
var x float64 = 3.4 var x float64 = 3.4
p := reflect.ValueOf(&x) p := reflect.ValueOf(&x)
v := p.Elem() v := p.Elem()
v.SetFloat(7.1) v.SetFloat(7.1)
```
上面只是对反射的简单介绍,更深入的理解还需要自己在编程中不断的实践。 上面只是对反射的简单介绍,更深入的理解还需要自己在编程中不断的实践。
## links ## links

View File

@@ -7,10 +7,12 @@
goroutine是Go并行设计的核心。goroutine说到底其实就是线程但是它比线程更小十几个goroutine可能体现在底层就是五六个线程Go语言内部帮你实现了这些goroutine之间的内存共享。执行goroutine只需极少的栈内存(大概是4~5KB)当然会根据相应的数据伸缩。也正因为如此可同时运行成千上万个并发任务。goroutine比thread更易用、更高效、更轻便。 goroutine是Go并行设计的核心。goroutine说到底其实就是线程但是它比线程更小十几个goroutine可能体现在底层就是五六个线程Go语言内部帮你实现了这些goroutine之间的内存共享。执行goroutine只需极少的栈内存(大概是4~5KB)当然会根据相应的数据伸缩。也正因为如此可同时运行成千上万个并发任务。goroutine比thread更易用、更高效、更轻便。
goroutine是通过Go的runtime管理的一个线程管理器。goroutine通过`go`关键字实现了,其实就是一个普通的函数。 goroutine是通过Go的runtime管理的一个线程管理器。goroutine通过`go`关键字实现了,其实就是一个普通的函数。
```Go
go hello(a, b, c) go hello(a, b, c)
```
通过关键字go就启动了一个goroutine。我们来看一个例子 通过关键字go就启动了一个goroutine。我们来看一个例子
```Go
package main package main
@@ -41,7 +43,7 @@ goroutine是通过Go的runtime管理的一个线程管理器。goroutine通过`g
// hello // hello
// world // world
// hello // hello
```
我们可以看到go关键字很方便的就实现了并发编程。 我们可以看到go关键字很方便的就实现了并发编程。
上面的多个goroutine运行在同一个进程里面共享内存数据不过设计上我们要遵循不要通过共享来通信而要通过通信来共享。 上面的多个goroutine运行在同一个进程里面共享内存数据不过设计上我们要遵循不要通过共享来通信而要通过通信来共享。
@@ -51,17 +53,20 @@ goroutine是通过Go的runtime管理的一个线程管理器。goroutine通过`g
## channels ## channels
goroutine运行在相同的地址空间因此访问共享内存必须做好同步。那么goroutine之间如何进行数据的通信呢Go提供了一个很好的通信机制channel。channel可以与Unix shell 中的双向管道做类比可以通过它发送或者接收值。这些值只能是特定的类型channel类型。定义一个channel时也需要定义发送到channel的值的类型。注意必须使用make 创建channel goroutine运行在相同的地址空间因此访问共享内存必须做好同步。那么goroutine之间如何进行数据的通信呢Go提供了一个很好的通信机制channel。channel可以与Unix shell 中的双向管道做类比可以通过它发送或者接收值。这些值只能是特定的类型channel类型。定义一个channel时也需要定义发送到channel的值的类型。注意必须使用make 创建channel
```Go
ci := make(chan int) ci := make(chan int)
cs := make(chan string) cs := make(chan string)
cf := make(chan interface{}) cf := make(chan interface{})
```
channel通过操作符`<-`来接收和发送数据 channel通过操作符`<-`来接收和发送数据
```Go
ch <- v // 发送v到channel ch. ch <- v // 发送v到channel ch.
v := <-ch // 从ch中接收数据并赋值给v v := <-ch // 从ch中接收数据并赋值给v
```
我们把这些应用到我们的例子中来: 我们把这些应用到我们的例子中来:
```Go
package main package main
@@ -85,18 +90,19 @@ channel通过操作符`<-`来接收和发送数据
fmt.Println(x, y, x + y) fmt.Println(x, y, x + y)
} }
```
默认情况下channel接收和发送数据都是阻塞的除非另一端已经准备好这样就使得Goroutines同步变的更加的简单而不需要显式的lock。所谓阻塞也就是如果读取value := <-ch它将会被阻塞直到有数据接收。其次任何发送ch<-5将会被阻塞直到数据被读出。无缓冲channel是在多个goroutine之间同步很棒的工具。 默认情况下channel接收和发送数据都是阻塞的除非另一端已经准备好这样就使得Goroutines同步变的更加的简单而不需要显式的lock。所谓阻塞也就是如果读取value := <-ch它将会被阻塞直到有数据接收。其次任何发送ch<-5将会被阻塞直到数据被读出。无缓冲channel是在多个goroutine之间同步很棒的工具。
## Buffered Channels ## Buffered Channels
上面我们介绍了默认的非缓存类型的channel不过Go也允许指定channel的缓冲大小很简单就是channel可以存储多少元素。ch:= make(chan bool, 4)创建了可以存储4个元素的bool 型channel。在这个channel 中前4个元素可以无阻塞的写入。当写入第5个元素时代码将会阻塞直到其他goroutine从channel 中读取一些元素,腾出空间。 上面我们介绍了默认的非缓存类型的channel不过Go也允许指定channel的缓冲大小很简单就是channel可以存储多少元素。ch:= make(chan bool, 4)创建了可以存储4个元素的bool 型channel。在这个channel 中前4个元素可以无阻塞的写入。当写入第5个元素时代码将会阻塞直到其他goroutine从channel 中读取一些元素,腾出空间。
```Go
ch := make(chan type, value) ch := make(chan type, value)
```
当 value = 0 时channel 是无缓冲阻塞读写的当value > 0 时channel 有缓冲、是非阻塞的,直到写满 value 个元素才阻塞写入。 当 value = 0 时channel 是无缓冲阻塞读写的当value > 0 时channel 有缓冲、是非阻塞的,直到写满 value 个元素才阻塞写入。
我们看一下下面这个例子你可以在自己本机测试一下修改相应的value值 我们看一下下面这个例子你可以在自己本机测试一下修改相应的value值
```Go
package main package main
@@ -111,9 +117,10 @@ channel通过操作符`<-`来接收和发送数据
} }
//修改为1报如下的错误: //修改为1报如下的错误:
//fatal error: all goroutines are asleep - deadlock! //fatal error: all goroutines are asleep - deadlock!
```
## Range和Close ## Range和Close
上面这个例子中我们需要读取两次c这样不是很方便Go考虑到了这一点所以也可以通过range像操作slice或者map一样操作缓存类型的channel请看下面的例子 上面这个例子中我们需要读取两次c这样不是很方便Go考虑到了这一点所以也可以通过range像操作slice或者map一样操作缓存类型的channel请看下面的例子
```Go
package main package main
@@ -137,7 +144,7 @@ channel通过操作符`<-`来接收和发送数据
fmt.Println(i) fmt.Println(i)
} }
} }
```
`for i := range c`能够不断的读取channel里面的数据直到该channel被显式的关闭。上面代码我们看到可以显式的关闭channel生产者通过内置函数`close`关闭channel。关闭channel之后就无法再发送任何数据了在消费方可以通过语法`v, ok := <-ch`测试channel是否被关闭。如果ok返回false那么说明channel已经没有任何数据并且已经被关闭。 `for i := range c`能够不断的读取channel里面的数据直到该channel被显式的关闭。上面代码我们看到可以显式的关闭channel生产者通过内置函数`close`关闭channel。关闭channel之后就无法再发送任何数据了在消费方可以通过语法`v, ok := <-ch`测试channel是否被关闭。如果ok返回false那么说明channel已经没有任何数据并且已经被关闭。
>记住应该在生产者的地方关闭channel而不是消费的地方去关闭它这样容易引起panic >记住应该在生产者的地方关闭channel而不是消费的地方去关闭它这样容易引起panic
@@ -148,6 +155,7 @@ channel通过操作符`<-`来接收和发送数据
我们上面介绍的都是只有一个channel的情况那么如果存在多个channel的时候我们该如何操作呢Go里面提供了一个关键字`select`,通过`select`可以监听channel上的数据流动。 我们上面介绍的都是只有一个channel的情况那么如果存在多个channel的时候我们该如何操作呢Go里面提供了一个关键字`select`,通过`select`可以监听channel上的数据流动。
`select`默认是阻塞的只有当监听的channel中有发送或接收可以进行时才会运行当多个channel都准备好的时候select是随机的选择一个执行的。 `select`默认是阻塞的只有当监听的channel中有发送或接收可以进行时才会运行当多个channel都准备好的时候select是随机的选择一个执行的。
```Go
package main package main
@@ -177,8 +185,9 @@ channel通过操作符`<-`来接收和发送数据
}() }()
fibonacci(c, quit) fibonacci(c, quit)
} }
```
`select`里面还有default语法`select`其实就是类似switch的功能default就是当监听的channel都没有准备好的时候默认执行的select不再阻塞等待channel `select`里面还有default语法`select`其实就是类似switch的功能default就是当监听的channel都没有准备好的时候默认执行的select不再阻塞等待channel
```Go
select { select {
case i := <-c: case i := <-c:
@@ -186,9 +195,10 @@ channel通过操作符`<-`来接收和发送数据
default: default:
// 当c阻塞的时候执行这里 // 当c阻塞的时候执行这里
} }
```
## 超时 ## 超时
有时候会出现goroutine阻塞的情况那么我们如何避免整个程序进入阻塞的情况呢我们可以利用select来设置超时通过如下的方式实现 有时候会出现goroutine阻塞的情况那么我们如何避免整个程序进入阻塞的情况呢我们可以利用select来设置超时通过如下的方式实现
```Go
func main() { func main() {
c := make(chan int) c := make(chan int)
@@ -207,7 +217,7 @@ channel通过操作符`<-`来接收和发送数据
}() }()
<- o <- o
} }
```
## runtime goroutine ## runtime goroutine
runtime包中有几个处理goroutine的函数 runtime包中有几个处理goroutine的函数

View File

@@ -1,13 +1,14 @@
# 2.8 总结 # 2.8 总结
这一章我们主要介绍了Go语言的一些语法通过语法我们可以发现Go是多么的简单只有二十五个关键字。让我们再来回顾一下这些关键字都是用来干什么的。 这一章我们主要介绍了Go语言的一些语法通过语法我们可以发现Go是多么的简单只有二十五个关键字。让我们再来回顾一下这些关键字都是用来干什么的。
```Go
break default func interface select break default func interface select
case defer go map struct case defer go map struct
chan else goto package switch chan else goto package switch
const fallthrough if range type const fallthrough if range type
continue for import return var continue for import return var
```
- var和const参考2.2Go语言基础里面的变量和常量申明 - var和const参考2.2Go语言基础里面的变量和常量申明
- package和import已经有过短暂的接触 - package和import已经有过短暂的接触
- func 用于定义函数和方法 - func 用于定义函数和方法

View File

@@ -23,7 +23,7 @@ Go中支持MySQL的驱动目前比较多有如下几种有些是支持data
`departname` VARCHAR(64) NULL DEFAULT NULL, `departname` VARCHAR(64) NULL DEFAULT NULL,
`created` DATE NULL DEFAULT NULL, `created` DATE NULL DEFAULT NULL,
PRIMARY KEY (`uid`) PRIMARY KEY (`uid`)
) );
CREATE TABLE `userdetail` ( CREATE TABLE `userdetail` (
`uid` INT(10) NOT NULL DEFAULT '0', `uid` INT(10) NOT NULL DEFAULT '0',
@@ -33,6 +33,7 @@ Go中支持MySQL的驱动目前比较多有如下几种有些是支持data
) )
如下示例将示范如何使用database/sql接口对数据库表进行增删改查操作 如下示例将示范如何使用database/sql接口对数据库表进行增删改查操作
```Go
package main package main
@@ -108,7 +109,8 @@ Go中支持MySQL的驱动目前比较多有如下几种有些是支持data
panic(err) panic(err)
} }
} }
```
通过上面的代码我们可以看出Go操作Mysql数据库是很方便的。 通过上面的代码我们可以看出Go操作Mysql数据库是很方便的。

View File

@@ -29,6 +29,7 @@ Go支持sqlite的驱动也比较多但是好多都是不支持database/sql接
); );
看下面Go程序是如何操作数据库表数据:增删改查 看下面Go程序是如何操作数据库表数据:增删改查
```Go
package main package main
@@ -104,7 +105,7 @@ Go支持sqlite的驱动也比较多但是好多都是不支持database/sql接
panic(err) panic(err)
} }
} }
```
我们可以看到上面的代码和MySQL例子里面的代码几乎是一模一样的唯一改变的就是导入的驱动改变了然后调用`sql.Open`是采用了SQLite的方式打开。 我们可以看到上面的代码和MySQL例子里面的代码几乎是一模一样的唯一改变的就是导入的驱动改变了然后调用`sql.Open`是采用了SQLite的方式打开。

View File

@@ -38,12 +38,14 @@ Go实现的支持PostgreSQL的驱动也很多因为国外很多人在开发
看下面这个Go如何操作数据库表数据:增删改查 看下面这个Go如何操作数据库表数据:增删改查
package main ```Go
package main
import ( import (
"database/sql" "database/sql"
"fmt" "fmt"
_ "https://github.com/lib/pq" _ "github.com/lib/pq"
) )
func main() { func main() {
@@ -58,10 +60,15 @@ package main
checkErr(err) checkErr(err)
//pg不支持这个函数因为他没有类似MySQL的自增ID //pg不支持这个函数因为他没有类似MySQL的自增ID
id, err := res.LastInsertId() // id, err := res.LastInsertId()
checkErr(err) // checkErr(err)
// fmt.Println(id)
var lastInsertId int
err = db.QueryRow("INSERT INTO userinfo(username,departname,created) VALUES($1,$2,$3) returning uid;", "astaxie", "研发部门", "2012-12-09").Scan(&lastInsertId)
checkErr(err)
fmt.Println("最后插入id =", lastInsertId)
fmt.Println(id)
//更新数据 //更新数据
stmt, err = db.Prepare("update userinfo set username=$1 where uid=$2") stmt, err = db.Prepare("update userinfo set username=$1 where uid=$2")
@@ -113,6 +120,7 @@ package main
panic(err) panic(err)
} }
} }
```
从上面的代码我们可以看到PostgreSQL是通过`$1`,`$2`这种方式来指定要传递的参数而不是MySQL中的`?`另外在sql.Open中的dsn信息的格式也与MySQL的驱动中的dsn格式不一样所以在使用时请注意它们的差异。 从上面的代码我们可以看到PostgreSQL是通过`$1`,`$2`这种方式来指定要传递的参数而不是MySQL中的`?`另外在sql.Open中的dsn信息的格式也与MySQL的驱动中的dsn格式不一样所以在使用时请注意它们的差异。