Go语言学习

释放双眼,带上耳机,听听看~!

Go语言学习

1 安装

1.1 下载

可以访问Go语言官网下载go语言安装包.如下图选择Download Go:
Go语言学习
进入下载页面后根据自己的操作系统选择对应的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   &quot;fmt&quot;
5)
6
7func main() {
8   c := make(chan int, 2)
9   c &lt;- 1
10  c &lt;- 2
11  // c &lt;- 3
12  // fmt.Println(&lt;-c)
13  fmt.Println(&lt;-c)
14  fmt.Println(&lt;-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   &quot;fmt&quot;
5   &quot;time&quot;
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 &lt;= 10; i++ {
18      time.Sleep(1000 * time.Millisecond)
19      c &lt;- 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   &quot;fmt&quot;
5   &quot;time&quot;
6)
7
8func main() {
9   i := make(chan int, 10)
10   s := make(chan string, 5)
11   for a := 0; a &lt; 10; a++ {
12     i &lt;- a
13   }
14   for a := 0; a &lt; 5; a++ {
15     s &lt;- &quot;h&quot;
16   }
17
18   for {
19     select {
20     // 从 i 中读取数据,如果读取到了就执行
21     case v := &lt;- i:
22      fmt.Printf(&quot;从i中读取的数据:%d \n&quot;, v)
23      time.Sleep(100 * time.Millisecond)
24      // 从s中读取数据,如果读取到了就执行
25     case &lt;- s:
26      fmt.Println(&quot;从s中读取了数据&quot;)
27      time.Sleep(100 * time.Millisecond)
28     default:
29      fmt.Println(&quot;默认...&quot;)
30      return;
31     }
32
33   }
34
35}
36
37

给TA打赏
共{{data.count}}人
人已打赏
安全技术

c++ list, vector, map, set 区别与用法比较

2022-1-11 12:36:11

安全经验

linux安装git

2021-10-11 16:36:11

个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索