Go语言学习
1 安装
1.1 下载
可以访问Go语言官网下载go语言安装包.如下图选择Download Go:
进入下载页面后根据自己的操作系统选择对应的go语言安装包即可下载:
1.2 安装和配置
我的操作系统是linux的,所以我选择下载了linux版本的安装包.将下载好的go语言安装包复制到安装目录然后解压,解压好后配置环境变量:
1
2
3
4 1GOROOT=/home/deepin/dev/env/go/go
2export PATH=$PATH:$JAVA_HOME/bin:$CLASS_PATH:$MAVEN_HOME/bin:$NODE_HOME/bin:$GOROOT/bin/
3
4
配置好环境变量后,在控制台输入如下命令,出现类似输出则表示配置成功:
1
2
3
4 1$ go version
2go version go1.12.5 linux/amd64
3
4
注意:配置好环境变量后可能要使配置文件被重新加载source 配置文件或者注销当前账户重新登录!!!
2 入门Hello World
2.1 新建一个文件main.go
新建一个文件`main.go`,并在文件中键入以下内容:
1
2
3
4
5
6
7
8 1package main
2import "fmt"
3func main() {
4 fmt.Println("Hello World")
5}
6
7
8
依次对上面的代码解释:
- package main 包,表示当前程序在哪个包下,入口程序一定要在main包下,入口程序也即程序最开始运行的地方.main方法就是程序的入口.
- import “fmt” 导入外部依赖(库),这里导入了一个fmt库(format),这个库提供了多种数据格式化的方法.
- func main() 定义main函数,main函数就是程序执行和入口函数,main函数一定要在main包下.
- fmt.Println(“Hello World”) 程序向控制台输出Hello World。
3 基础
3.1 包
包程序由包构成,程序从main程序开始执行,mian程序在mian包下。
3.1.1 主程序
新建一个index.go文件,并键入以下代码:
1
2
3
4
5
6 1package main
2func main() {
3 println("hello world!")
4}
5
6
主程序必须是func main并且在main包下。
3.1.2 导包
包中可以提供方法供其他程序使用,其他程序要使用包就需要导包,导包通过import关键字。导包有要注意的地方是导包的路径是从GOPATH和GOROOT开始查找包的,其中GOROOT是在安装GO环境是配置的,GOPATH默认是用户家目录/go。我们更改默认的GOPATH配置:
1
2
3
4 1GOPATH=//home/deepin/Documents/workspace/go
2export PATH=$PATH:$JAVA_HOME/bin:$CLASS_PATH:$MAVEN_HOME/bin:$NODE_HOME/bin:$GOROOT/bin/:$GOPATH
3
4
配置好后,新建测试程序:
-
新建项目(文件夹demo)并在项目目录下新建两个文件夹main和util
-
在main包下新建一个main.go文件并键入以下代码:
1
2
3
4
5
6
7
8
9 1package main
2
3import "../util"
4
5func main() {
6 util.PrintHello()
7
8
9
-
在util文件夹下新建一个hello.go文件,并键入以下内容:
1
2
3
4
5
6
7
8
9
10
11
12 1package util
2
3func PrintHello() {
4 hello()
5}
6
7
8func hello() {
9 println("hello")
10}
11
12
运行go run main/main.go。
注意:
相对路径导包:
1
2
3 1import "../util"
2
3
如果写成绝对路径导包的话要走GOPATH,也就是从$GOPATH/src/开始查找包,应该是import 项目名/util(项目在src下)。项目如果不在src/下的话,就要把util包发到src下。
注:import导包的时候写的是目录名使用的时候使用的是包名。
3.1.3 导包可以同时导入多个,例如:
1
2
3
4
5
6 1import (
2 "demo00/utils"
3 "fmt"
4)
5
6
3.1.4 给包起别名
1
2
3
4
5
6
7
8 1import (
2 hello "demo00/utils"
3 "fmt"
4)
5// 或
6import hello "demo00/utils"
7
8
3.1.5 导出约定
要导出的内容(方法/变量等)必须以大小字母开头。
3.2 函数
使用func关键字定义函数,格式为
1
2
3
4 1func 函数名(参数名1 参数类型, 参数名2 参数类型, ....) 返回值类型 {
2}
3
4
例如定义一个两数相加返回结果的方法:
1
2
3
4
5 1func add(x int, y int) int {
2 return x + y
3}
4
5
3.2.1 参数类型缩写
如上add函数,x和y是同一个类型的,所以参数列表也可以用下缩写写法:
1
2
3
4
5 1func add(x, y int) int {
2 return x + y
3}
4
5
3.2.2 多值返回
函数不能返回多值,比如下面这个方法计算计算两个数的和和差并分别返回:
1
2
3
4
5 1func calc(x, y int) (int, int) {
2 return x + y, x - y;
3}
4
5
3.2.3 命令返回值
可以为返回值定义变量名,在函数末尾使用return就直接返回:
1
2
3
4
5
6
7 1func calc(x, y int) (add, minus int) {
2 add = x + y
3 minux = x - y
4 return
5}
6
7
3.3 变量
3.3.1 变量声明及初始化
1
2
3
4
5
6 1// 声明变量
2var a, b int
3// 声明变量并初始化
4var x, y int = 2, 3
5
6
3.3.2 变量声明及初始化简写
简写变量的形式只能在函数内使用:
1
2
3
4 1// 声明变量初始化简写,类型自动推导
2a, b := 3, 4
3
4
3.3.3 定义多组变量
就像导入多组包一样,可以定义多级变量:
1
2
3
4
5
6 1var (
2 a,b int = 1, 2
3 c, d bool
4)
5
6
3.3.4 默认值
在定义了变量且没有显示赋值时,变量会有一个默认值:
1
2
3
4
5 1数值类型为 0,
2布尔类型为 false,
3字符串为 ""(空字符串)。
4
5
3.3.5 类型转换
go中类型必须显示转换:
1
2
3
4
5
6
7
8
9 1var a int = 1
2// 显示转换
3var b float64 = float64(a)
4println(b)
5// 显示转换
6var c int = int(b)
7println(c)
8
9
3.3.6 常量
go中常量通过const关键字定义,常量不允许被修改:
1
2
3
4
5
6
7
8 1 const Pi = 3.14
2 println(Pi)
3 // 试图修改一个常量,编译不通过
4 Pi = 3.15
5 println(Pi)
6
7
8
3.4 流程控制语句
3.4.1 for
在go中只有一种循环语句,就是for循环,如下:
1
2
3
4
5 1for 前置语句; 条件语句; 后置语句 {
2
3}
4
5
前置语句最先执行且执行一次,每次执行循环前执行条件语句,每次循环结束时执行后置语句。{}不能省略。
-
死循环
1
2
3
4
5 1for {
2 // 代码块...
3}
4
5
-
只判断条件
1
2
3
4
5
6 1for ; i < 10; {
2 // 代码块...
3
4}
5
6
3.4.2 if
if语句不需要小括号,必须要大括号,如下
1
2
3
4
5 1if 条件 {
2 // do something...
3}
4
5
- 可以在if中执行一条语句
类似for语句一样,在执行if语句的时候也可以执行一条语句:
1
2
3
4
5
6
7
8
9
10
11 1func main() {
2 var i int = 1
3
4 if a := 2; i < a {
5 i = a + i
6 }
7 println(i)
8}
9
10
11
如果在if中定义了一个变量,这个变量在整个if中共享:
1
2
3
4
5
6
7
8
9
10
11
12
13 1func main() {
2 var i int = 1
3
4 if a := 2; i < 0 {
5 i = a + i
6 } else if i < 2 {
7 i = a + 2
8 }
9 println(i)
10}
11
12
13
3.4.3 switch
下面例子是所有switch语句相关的用法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31 1package main
2
3func main() {
4 var c string = "abc"
5
6 // switch 也可以执行一条语句
7 switch i := "bc"; c {
8 case "a" + i:
9 println("a")
10 // 默认有break语句的作用 不会继续向下执行
11 case "ab":
12 println("ab")
13 // break语句作用无效 也就是会继续向下执行
14 fallthrough
15 // switch 的case 可以是一个函数
16 case fun():
17 println("abc")
18 case "d":
19 println("d")
20 default: // 条件都不匹配时执行
21 println("default")
22 }
23}
24
25func fun() string {
26 println("fun执行...")
27 return "abc"
28}
29
30
31
如果switch语句没有条件,相当于swith true:
1
2
3
4
5
6
7
8
9
10
11 1
2func main() {
3 switch {
4 case 1 < 10:
5 println("true")
6 case 1 > 10:
7 println("false")
8 }
9}
10
11
4.4 延迟执行defer
defer后面跟着一个函数,defer关键字会使该函数延迟执行,也即在defer所在函数执行完后(return后)执行该函数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 1func main() {
2 j := test()
3 println(j) // 4
4 defer println("main", j) // 5
5}
6
7func test() int{
8 defer println("test exec ...") // 3
9 i := 1
10 println("test", i) // 1
11 defer println("test defer", i) // 2
12 return 1
13}
14
15
16
执行结果是:
1
2
3
4
5
6
7 1test 1
2test defer 1
3test exec ...
41
5main 1
6
7
defer 栈:推迟的函数调用会被压入一个栈中。当外层函数返回时,被推迟的函数会按照后进先出的顺序调用。
3.5 更多类型
指针指向(保存)了值的内存地址。
3.5.1 指针
a 定义指针
通过var 变量名 *类型可以定义一个指向对应类型的指针变量,如下:
1
2
3 1var p *int
2
3
变量都有默认值,这里p的默认值是nil。
b 取址
通过&变量就可以取到对应变量的地址了,如下:
1
2
3
4
5 1i := 10
2// 取变量 i的地址
3var p *int = &i
4
5
c 取值
通过*指针变量就可以取到指针变量对应的地址的值:
1
2
3
4
5
6
7 1var p *int
2i := 3
3p = &i
4j := *p
5// j 的值就是3
6
7
下面两种是不同的操作(地址就是内存地址):
1
2
3
4
5
6
7
8
9
10
11
12 1func main() {
2 var p *int
3 i := 3
4 p = &i
5 // 取p中的值,p保存的是i的地址,所以是地址值
6 println(p)
7 // 取p中的地址中的值, 也就是对就的i的值
8 println(*p)
9}
10
11
12
看下面 这个程序:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 1package main
2
3func main() {
4 // 定义一个变量 值是21 根据类型推导 i的类型是int型
5 i := 21
6 // 定义一个变量 值是&i 根据类型推导 p的类型是 *int型
7 p := &i
8 // *p 的操作就是对 p中保存的地址上的内存的操作 *p = 42 就是改变了内存地址上的值为42
9 *p = 42
10 // 所以在这里打印i 就是42
11 println(i)
12}
13
14
15
3.5.2 结构体
a 定义结构体
定义一个结构体通过type 结构体名 struct:
1
2
3
4
5 1type Student struct {
2
3}
4
5
b 为结构体添加字段
结构体可以看做是一个对象,字段可以看做是这个对象的属性:
1
2
3
4
5
6 1type Student struct {
2 X int
3 Y int
4}
5
6
c 定义一个结构体
1
2
3
4
5
6
7
8
9
10 1// X = 1 , Y = 3
2var s Student = Student{1, 3}
3// X = 1, Y = 0
4var s1 = Student{X: 1}
5// X = 1 , Y = 2
6var s2 = Student{X: 1, Y: 2}
7// X = 0, Y = 0
8var s3 = Student{}
9
10
d 获取结构体字段值
1
2
3
4
5 1var s Student = Student{1, 3}
2// 获取s中的X的值保存在变量i中
3var i int = s.X
4
5
e 结构体指针
可以通过(*指针).字段通过指针访问属性值,也可以直接访问指针.字段。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 1package main
2import "fmt"
3
4func main() {
5 var s Student = Student{1, 3}
6 p := &s
7 fmt.Println((*p).X)
8 fmt.Println(p.X)
9}
10type Student struct {
11 X int
12 Y int
13}
14
15
3.5.3 数组
a 定义数组
定义一个10个长度的数组i,数组中每个元数都被会初始化(这里每个元素的类型是int,所以被始化为0)
1
2
3 1var i [10]int
2
3
如果用:形式定义数组就必需显示初始化数组:
1
2
3 1i := [4]int{1, 3, 4, 5}
2
3
3.5.4 切片
切片类似于数组,数组长度固定,切片长度是动态的。
a 定义一个切片
定义一个切片可以看做是定义一个没有长度的数组,如下定义了一个接收int类型的切片:
1
2
3 1var i []int
2
3
b 从数组中初始化
从一个数组中初始化切片,如下j[1:3]即取j中下角标1和2中的数据(包含1不包含3):
1
2
3
4
5
6
7 1var i []int
2// 定义一个数组
3j := [4]int{1, 2, 3, 4}
4// 初始化切片
5i = j[1:3]
6
7
注:j[1:3]其左右两边的数值都可以省略,如果左边省略表示从0开始,右边省略表示到数组最后的下角标.
c 切片中不保存数据
切下中不保存任何数据,只是描述了数组中的一段,如下从数组中扩展的切片改变数组上对应的数据也会发生变化:
1
2
3
4
5 1j := [4]int{1, 2, 3, 4}
2i := j[1:3]
3i[1] = 2 // j[2]也变成了2
4
5
c 直接初始化切片
可以不从数组来初始化切片:
1
2
3 1s := []int{1, 2, 3, 4, 3}
2
3
d 切片长度
可以通过leng(切片)获取切片长度,cap(切片)来获取切片的容量:
1
2
3
4
5 1s := []int{1, 2, 3, 4, 3}
2fmt.Println(len(s)) // 5
3fmt.Println(cap(s)) // 5
4
5
*长度和容量:长度可以理解为当前切片中元素的个数,容量可以理解为当前切片可以保存元素的个数,容量可以扩充,当前当前元素个数>容量时,容量扩充一倍。
e 重新切片
重新切片,改变切片容量:
1
2
3
4 1s := []int{1, 2, 3, 4, 3} // [1 2 3 4 3] len = 5 cap = 5
2s = s[3:] // [4 3] len = 2 , cap = 2
3
4
d 通过make创建切片
如下,创建了一个长度为0,容量为5的切片:
1
2
3 1s := make([]int, 0, 5) // []
2
3
如下,创建一个长度为2(切片中有两个元素,默认值为0),容量为3的切片:
1
2
3 1s := make([]int, 2, 3) // [0 0]
2
3
e 向切片中添加元素
可以通过append(切片, 元素1, 元素2, …, 元素n)向切片中添加元素,当元素长度超过了容量时,容量扩容一倍:
1
2
3
4
5
6 1s := make([]int, 0, 2) // [] len = 0 cap = 2
2s = append(s, 1) // [1] len = 1 cap = 2
3s = append(s, 2) // [1 2] len = 2 cap = 2
4s = append(s, 3, 4) // [1 2 3 4] len = 4 cap = 4
5
6
f 遍历切片
通过for和range可以遍历切片:
1
2
3
4
5
6
7
8
9
10 1func main() {
2 s := []int{1, 2, 3, 4, 5}
3 // i 是下角标 v是对应下角标的元素
4 for i, v := range s {
5 fmt.Print("i = ", i, " ")
6 fmt.Println("v = ", v)
7 }
8}
9
10
可以用_代替i或v中的其中一个,也就是说如果只要获取下角标或元素可以像下面这样写:
1
2
3
4
5
6
7
8
9 1for i, _ := range s {
2 fmt.Print("i = ", i, " ")
3}
4// 或
5for _, v := range s {
6 fmt.Print("i = ", i, " ")
7}
8
9
如果只要下角标也可以这样写:
1
2
3
4
5 1for i := range s {
2 // do something...,
3}
4
5
3.5.5 映射 map
a 定义映射
新定义 的映射是nil,不能向其添加数据:
1
2
3 1var m map[string]string
2
3
b make初始化
映射通过make初始化后可以向其中添加数据:
1
2
3
4 1var m map[string]string
2m = make(map[string]string])
3
4
c 初始化数据
在创建时初始化数据:
1
2
3
4
5
6
7 1var m = map[string]string {
2 "a": "xiaoming",
3 "b": "xiaohong",
4 }
5fmt.Println(m) // map[a:xiaoming b:xiaohong]
6
7
d 向映射中添加或删除数据
1
2
3
4 1// 如果没有添加数据,如果存在就修改数据
2m["c"] = "xiaogang"
3
4
e 获取元素
直接通过key来获取元素:
1
2
3
4
5
6
7
8
9 1func main() {
2 var m = map[string]string {
3 "a": "xiaoming",
4 "d": "xiaohong",
5 }
6 fmt.Println(m["a"])
7}
8
9
f 删除元素
可以通过delete(映射, key)删除元素:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 1func main() {
2 var m = map[string]string {
3 "a": "xiaoming",
4 "d": "xiaohong",
5 }
6 // 删除元素
7 delete(m, "a")
8 fmt.Println(m)
9}
10```下单接口json
11##### g 检测值是否存在
12通过下面语法检测值是否存在,如果存在 `elem`就是对应的值,`ok`为`true`,如果不存在,`elem`是对应类型的默认值,`ok`为`false`:
13```go
14elem, ok := m["c"]
15
16
3.5.6 函数作为值传递
下面这个例子,函数作为另一个函数的参数和返回值传递:
1
2
3
4
5
6
7
8
9
10
11
12
13
14 1func main() {
2 i := func(a, b int) int {
3 return a + b
4 }
5 fn := test(i)
6 r := fn(1, 2)
7 fmt.Println(r)
8}
9
10func test(fn func(a, b int) int ) func(a, b int) int {
11 return fn
12}
13
14
3.5.7 函数的闭包
函数的闭包也就是函数的返回值是一个函数,如果函数中有变量,那么返回的函数在执行时会共享同一个变量:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 1func main() {
2 f1 := test()
3 fmt.Println(f1(1)) // 1
4 fmt.Println(f1(1)) // 2
5 f2 := test()
6 fmt.Println(f2(1)) // 1
7 fmt.Println(f2(1)) // 2
8}
9
10func test() func(b int) int {
11 sum := 0 // 这个就是共享变量
12 return func(b int) int{
13 sum += b
14 return sum
15 }
16}
17
18
4 方法和接口
4.1 方法
go中没有类。不过你可以为结构体定义方法。方法也就是带接收者的函数,用func定义方法,func后是接收者类似:func 接收者 方法名(参数) 返回值.
4.1.1 定义方法
为结构体定义一个方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 1
2func main() {
3 s := Student{"Tom", 18}
4 s.say("hello")
5}
6type Student struct {
7 name string
8 age int
9}
10// 为结构体定义方法
11func (s Student) say(word string) {
12 fmt.Println(s.name, ":", word);Scale
13}
14
15
4.1.2 扩展类型添加方法
我们可以扩展一个类型,type 类型名 类型,如type MyInt int,也可以为其添加方法,如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 1package main
2import "fmt"
3
4func main() {
5 var m MyInt = MyInt(1)
6 sum := m.add(11)
7 fmt.Println(sum)
8}
9type MyInt int
10func (m MyInt) add(param int) (sum int){
11 sum = int(m) + param
12 return
13}
14
15
4.1.3 方法的接收者
方法的接收者可以是一个‘类型’,也可以是一个指针,如果是一个类型的话,接收者只是这个‘类型’的副本,如果是指针,接收者指向对应的‘类型’,也就是说如果修改值,一个会修改副本一个修改值本身:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38 1package main
2import "fmt"
3
4func main() {
5 var m My = My{1, 2}
6 m.print() // x 1 y 2
7 m.change(2) // x 2 y 4
8 m.print() // x 1 y 2
9 m.changeP(3)
10 m.print() // x 3 y 6
11}
12type My struct {
13 X int
14 Y int
15}
16
17// 打印结果
18func (m My) print() {
19 fmt.Println("m.X -> ", m.X)
20 fmt.Println("m.Y -> ", m.Y)
21}
22
23// 改变值
24func (m My) change(param int) {
25 m.X = param
26 m.Y = 2 * param
27 println("---------------------")
28 m.print()
29 println("---------------------")
30}
31
32// 使用指针改变值
33func (m *My) changeP(param int) {
34 m.X = param
35 m.Y = 2 * param
36}
37
38
如果方法的接收者是值本身,如果使用指针调用这个方法,会被解释为(*指针).方法(),如果方法的接收者是值本身,使用值本身调用方法时,会被解释为(&值本身).方法名()。
4.2 接口
接口是定义了一组方法签名的集合,如下就是一个接口:
1
2
3
4
5
6 1type MyInterface interface {
2 say(word string)
3 eat(food string)
4}
5
6
4.1 实现接口
实现接口只需要实现接口中定义的方法即可!
a 使用指针作用接收者
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 1// 结构体
2type Student struct {
3 name string
4 age int
5}
6// 为Student实现接口MyInterface中的say方法
7func (s *Student) say(word string) {
8 fmt.Println(s.name, " say -> ", word)
9}
10
11// 为Student实现接口MyInterface中的eat方法
12func (s *Student) eat(food string) {
13 fmt.Println(s.name, " eat -> ", food)
14}
15
16
使用指针接收的方法,接口接收的类型也应该是指针类型:
1
2
3
4
5
6
7
8
9
10 1func main() {
2 var mi MyInterface
3 s := Student{"Tom", 18}
4 // 这里是指针类型
5 mi = &s
6 mi.say("hello")
7 mi.eat("orange")
8}
9
10
b 不使用指针接收
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 1// 结构体
2type Student struct {
3 name string
4 age int
5}
6// 为Student实现接口MyInterface中的say方法
7func (s Student) say(word string) {
8 fmt.Println(s.name, " say -> ", word)
9}
10
11// 为Student实现接口MyInterface中的eat方法
12func (s Student) eat(food string) {
13 fmt.Println(s.name, " eat -> ", food)
14}
15
16
不使用指针接收方法,接口接收的类型可以是指针或不是指针:
1
2
3
4
5
6
7
8
9
10 1func main() {
2 var mi MyInterface
3 s := Student{"Tom", 18}
4 mi = &s
5 mi = s
6 mi.say("hello")
7 mi.eat("orange")
8}
9
10
4.2 接口作为值传递
下面是接口作为值传递的例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27 1package main
2import "fmt"
3
4func main() {
5 s := MyStruct1{"Tom"}
6 var ms MyStruct1 = s
7 exec(ms, "hello")
8}
9
10type MyInterface interface {
11 show(param string)
12}
13
14type MyStruct1 struct {
15 name string
16}
17
18func (m MyStruct1) show(param string) {
19 fmt.Println(param)
20}
21
22// 传递接口
23func exec(m MyStruct1, param string) {
24 m.show(param)
25}
26
27
4.3 空接口
没有任何方法定义的接口就是空接口,空接口可以接收任何参数类型interface{}:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 1func main() {
2 print(Student{"Tom"})
3}
4
5
6
7type Student struct {
8 name string
9}
10
11func print(i interface{}) {
12 fmt.Println(i)
13}
14
15
16
4.4 空接口断言
变量1 , 变量二 : 空接口.(类型),变量1保存了空接口中保存的变量值,变量保存了空接口中是否是保存的对应变量的布尔值,具体看下面这个例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 1package main
2import "fmt"
3
4func main() {
5 var i interface{} = Student{"Tom"}
6 // 判断i保存的变量 是不是int类型的
7 // 如果是就返回 该变量 并保存到 s1中,且ok为true,
8 // 如果不是s1为int类型默认值 ok为false
9 s1, ok := i.(int)
10 fmt.Println(s1, ok)
11 s2, ok := i.(Student)
12 fmt.Println(s2, ok)
13 // 如果只有一个接收的变量,这个值就是口中保存的值
14 s3 := i.(Student)
15}
16
17
18
19type Student struct {
20 name string
21}
22
23
4.5 类型选择
可以使用switch i.(type)作类型选择,i.(type)会返回i中保存的变量,并且只能用于switch:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 1package main
2import "fmt"
3
4func main() {
5 var i interface{} = Student{"Tom"}
6 switch v := i.(type) {
7 case string:
8 fmt.Println("string")
9 case int:
10 fmt.Println("int")
11 case Student:
12 fmt.Println("student", v)
13 }
14}
15
16
17
18type Student struct {
19 name string
20}
21
22
4.6 Stringer
fmt 包中定义的 Stringer 是最普遍的接口之一。
1
2
3
4
5 1type Stringer interface {
2 String() string
3}
4
5
该接口的方法有点类似于toString()方法,可以返回用fmt.Sprintf()格式化好的字符串,在调用fmt.Print()等方法时会被输出:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 1package main
2import "fmt"
3
4func main() {
5 s := Student{"Tom", 18}
6 fmt.Println(s)
7}
8
9
10
11type Student struct {
12 name string
13 age int
14}
15
16func (s Student) String() string {
17 return fmt.Sprintf("Student[name = %v, age = %v]", s.name, s.age)
18}
19
20
4.7错误
在go中有一个内置接口error,定义如下:
1
2
3
4
5 1type error interface {
2 Error() string
3}
4
5
自定义异常时,只需要实现这个接口即可 :
1
2
3
4
5
6
7
8
9
10 1type MyError struct {
2 Name string
3 Desc string
4}
5
6func (m *MyError) Error() string {
7 return fmt.Sprintf("错误:(%v),%v", m.Name, m.Desc)
8}
9
10
使用自定义异常时,在要抛出异常的地方返回该异常:
1
2
3
4
5
6
7
8
9 1func testError(i int) error {
2 if i == 0 {
3 return &MyError{"零值", "接收的参数不能为零"}
4 }
5 fmt.Println("接收的参数是:", i)
6 return nil
7}
8
9
要捕获异常时,判断返回的异常的值:
1
2
3
4
5
6
7
8
9
10 1func main() {
2 err := testError(0)
3 if err != nil {
4 fmt.Println(err)
5 }
6}
7
8
9
10
4.8 Reader
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25 1package main
2import (
3 "fmt"
4 "strings"
5 "io"
6)
7
8func main() {
9 // 被读取的数据
10 s := strings.NewReader("hello world!")
11
12 // 切片,容量长度为8
13 b := make([]byte, 8)
14 for {
15 n, err := s.Read(b)
16 // n是读取了多少 长度 err在读取到文件末尾时返回eof错误
17 fmt.Printf("%q", b[:n])
18 println()
19 if err == io.EOF {
20 break
21 }
22 }
23}
24
25
5 并发
5.1 goroutine
使用go关键字,方法会启用新的线程来执行:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 1package main
2
3import (
4 "fmt"
5 "time"
6)
7
8func main() {
9 // 使用了go关键字 开启新的线程运行
10 go say("hello")
11 // 当前线程执行下面的方法
12 say("hi")
13}
14
15func say(s string) {
16 for i := 0; i < 5; i++ {
17 fmt.Println(s)
18 time.Sleep(100 * time.Millisecond)
19 }
20}
21
22
5.2 信道
5.2.1 创建信道
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 1package main
2
3import (
4 "fmt"
5 "time"
6)
7
8func main() {
9 // 创建信道
10 c := make(chan int)
11 go in(1, c)
12 // <- 从信道中拿出数据 是阻塞的操作,会等待数据写入
13 fmt.Println(<- c)
14}
15
16func in(i int, c chan int) {
17 time.Sleep(1000 * time.Millisecond)
18 c <- i
19}
20
21
22
5.2.2 写入和取出数据
通过<-进行写入和读取数据,如上所示c <- i就是把i写入信道c中,<- c就是把信道中的值从信道中读出。
5.2.3 带缓冲的信道
在创建信道是指定缓冲大小,缓冲区满了就无法再写入数据。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 1package main
2
3import (
4 "fmt"
5)
6
7func main() {
8 c := make(chan int, 2)
9 c <- 1
10 c <- 2
11 // c <- 3
12 // fmt.Println(<-c)
13 fmt.Println(<-c)
14 fmt.Println(<-c)
15}
16
17
5.2.3 range和close
range可以从信道中读取值,直到遇到close:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26 1package main
2
3import (
4 "fmt"
5 "time"
6)
7
8func main() {
9 c := make(chan int, 2)
10 go in(c)
11 for i := range c {
12 fmt.Println(i)
13 }
14}
15
16func in(c chan int) {
17 for i := 0; i <= 10; i++ {
18 time.Sleep(1000 * time.Millisecond)
19 c <- i
20 }
21 // 不写入数据后close
22 close(c)
23}
24
25
26
5.2.4 select
从信道中读取数据时,如果信道中没有数据且没关闭,会出现死锁的情况,如果使用select就不会出现类似情况:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37 1package main
2
3import (
4 "fmt"
5 "time"
6)
7
8func main() {
9 i := make(chan int, 10)
10 s := make(chan string, 5)
11 for a := 0; a < 10; a++ {
12 i <- a
13 }
14 for a := 0; a < 5; a++ {
15 s <- "h"
16 }
17
18 for {
19 select {
20 // 从 i 中读取数据,如果读取到了就执行
21 case v := <- i:
22 fmt.Printf("从i中读取的数据:%d \n", v)
23 time.Sleep(100 * time.Millisecond)
24 // 从s中读取数据,如果读取到了就执行
25 case <- s:
26 fmt.Println("从s中读取了数据")
27 time.Sleep(100 * time.Millisecond)
28 default:
29 fmt.Println("默认...")
30 return;
31 }
32
33 }
34
35}
36
37