# Go 基础阶段

# 课前准备

# 安装 Go 语言

# 配置 Go 语言开发环境

可以选择安装 VS Code (opens new window) , 或者 Goland (opens new window) ,对于 VS Code,需要安装 Go 插件 (opens new window)

# 下载课程示例代码

git clone https://github.com/wangkechun/go-by-example.git
1
  • 进入课程示例项目代码目录,运行 go run example/01-hello/main.go 如果正确输出 hello world,则说明环境配置正确

# 参考资料

阅读: Go语言圣经(中文版) (opens new window)

案例课程: http://books.studygolang.com/gobyexample/

路线图: https://bytedance.feishu.cn/docs/doccn3SFTuFIAVr4CDZGx48KKdd

# Go 语言简介

# Go 语言有什么特点呢?

  1. 高性能 高并发
  2. 语法简单 跨平台
  3. 丰富的标准库
  4. 完善的工具链
  5. 静态链接 快速编译
  6. 垃圾回收

举个例子

简单几句代码, 就可以启动一个 web 服务

package main

import (
	"fmt"
	"net/http"
)

func main() {
	http.Handle("/", http.FileServer(http.Dir(".")))
	fmt.Println("启动web服务成功, 访问: http://localhost:8080")
	http.ListenAndServe("localhost:8080", nil)
}
1
2
3
4
5
6
7
8
9
10
11
12

运行

go run web.go
1

由于 Go 入门很简单, 开发效率高, 性能比较好, 部署也简单, 所以让我们全面拥抱 GO 语言吧!

# 基础语法

# hello world

查看课程示例example/01-hello目录的文件

先来看一下 go 语言中, hello word 的代码长啥样

// package main 代表这个文件属于 main 包的一部分
package main  // main 包是用户程序的入口

// 导入其他包
import (
	"fmt" // 标准库里面的 fmt 包, 主要用来输入输出字符串、格式化字符串的
)

 // main 函数是执行的用户入口函数
func main() {
	fmt.Println("hello world") // 调用了 fmt.Println 输出 hello word
}
1
2
3
4
5
6
7
8
9
10
11
12

把代码跑起来, 有两种方式:

  • 直接运行

    go run main.go
    
    1
  • 编译后再运行

    go build main.go
    ./main
    
    1
    2

# 变量

变量名由字母、数字、下划线组成, 其中首个字符不能为数字

# 变量类型

  • 值类型

    整型 浮点型 字符串 布尔型 结构体

  • 引用类型

    指针 切片 map 函数 接口 channel

# 变量声明

查看课程示例example/02-var目录的文件

变量在使用前, 必须要先声明, 可以在声明时候手动赋值, 如果不赋值, 则默认是零值

  • 显示声明, 使用 var 关键字

    // 显式声明, 并手动指定类型, 但不赋值
    var i int 
    
    // 显式声明, 并手动指定类型, 手动赋值
    var a string= "initial"
    
    // 多个变量在同一行同时声明
    var b, c int = 1, 2
    
    1
    2
    3
    4
    5
    6
    7
    8
  • 类型推断

    // 显式声明, 但是自动推导类型
    var d = true
    
    var e float64
    
    1
    2
    3
    4
  • 省略 var 关键字, 并自动推导类型, 使用 :=

    // 省略var, 自动推导类型, 并手动赋值
    f := float32(e)
    // 只能在函数内部使用
    g := a + "foo"
    
    1
    2
    3
    4

# 常量声明

使用 const 关键字

// 手动指定类型
const s string = "constant"
// 自动推导类型
const h = 500000000
const i = 3e20 / h
1
2
3
4
5

常量的值不可更改, 指的是该常量的内存空间存的内容不能修改

# 条件判断

Go 语言提供了以下几种条件判断语句:

语句 描述
if 语句 if 语句 由一个布尔表达式后紧跟一个或多个语句组成
switch 语句 switch 语句用于基于不同条件执行不同动作
select 语句 select 语句类似于 switch 语句。但是 select 会随机执行一个可运行的case, 如果没有 case 可运行,它将阻塞,直到有case可运行

注意: Go 没有三目运算符,所以不支持?:形式的条件判断

# if 语句

查看课程示例example/04-if目录的文件

  • if 后面可以没有括号

  • if后面的开始大括号 { 必须跟 if 在同一行, 不能换行

  • if后面也可以定义变量,但只能出现一个分号

    if num := 9; num < 0 { // 在 if 后面定义变量
      fmt.Println(num, "is negative")
    } else if num < 10 {
      fmt.Println(num, "has 1 digit")
    } else {
      fmt.Println(num, "has multiple digits")
    }
    
    1
    2
    3
    4
    5
    6
    7

# switch 语句

查看课程示例example/05-switch目录的文件

  • Go 的 switch可以匹配任意类型

  • switch 后面可以不加括号

  • case 里也不需要加break,默认情况下 case 最后自带 break 语句,匹配成功后就不会执行其他 case,如果需要执行后面的 case,可以使用 fallthrough 关键字

  • case 可以匹配多条件, 使用逗号分隔

  • switch后面可以不加匹配条件,然后在case里再进行匹配

    	t := time.Now()
    	switch {
    	case t.Hour() < 12:
    		fmt.Println("It's before noon")
    	default:
    		fmt.Println("It's after noon")
    	}
    
    1
    2
    3
    4
    5
    6
    7

# 循环 for

在 Go 里面没有 while 循环、do while 循环,只有 for 循环

查看课程示例example/03-for目录的文件

支持以下几种循环控制语句: (循环控制语句可以控制循环体内语句的执行过程)

控制语句 描述
break 语句 用于中断跳出当前 for 循环或跳出 switch 语句
continue 语句 用于跳过当前循环的剩余语句,然后继续进行下一轮循环
goto 语句 跳到被标记的语句处

for 定义普通的循环, 条件是三段式的, 任何一段都可以省略. 在 for 循环里使用 continuebreak 来跳出循环

// 普通for循环
for i := 0; i < 5; i++ {
    fmt.Println(i)
}
// 使用continue
for i := 0; i < 5; i++ {
    if i == 3 {
        continue
    }
    fmt.Println(i)
}
// 使用break
for i := 0; i < 5; i++ {
    if i == 3 {
        break
    }
    fmt.Println(i)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

for 定义 while 的功能

num := 1
for i <= 3 {
  fmt.Println(i)
  i = i + 1
}
1
2
3
4
5

for 定义死循环,相当于 while(true)

for { // 最简单的 for 循环, 条件都不写
  fmt.Println("loop")
}
for true {
  fmt.Println("loop")
}
1
2
3
4
5
6

# 数组 array

数组是具有相同类型的长度固定的一组数据序列, 其类型可以是原始类型或者自定义类型

查看课程示例example/06-array目录的文件

package main

import "fmt"

func main() {

	var a [5]int // 一维数组
	a[4] = 100
	fmt.Println("get:", a[2])
	fmt.Println("len:", len(a))

	b := [5]int{1, 2, 3, 4, 5} // 初始化
	fmt.Println(b)

	var twoD [2][3]int // 二维数组
	for i := 0; i < 2; i++ {
		for j := 0; j < 3; j++ {
			twoD[i][j] = i + j
		}
	}
	fmt.Println("2d: ", twoD)

	// 不能像 java 那样, 二维长度不同
	// var twoE [2][]int 错误的
}
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

在真实业务代码里面, 我们很少直接使用数组, 因为它长度是固定的, 我们用的更多的是切片

# 切片 slice

切片与数组的区别: 切片的长度不是固定的而是可变的, 比数组的用途更加广泛

查看课程示例example/07-slice目录的文件

切片的声明

// 1. make 方式
// 2. 引用数组方式
// 3. 字面量初始化方式, 底层还是引用数组
1
2
3

https://www.yuque.com/vbaicai/po7hfk/xn277d#cqsZ7

切片的本质

type slice struct {
	array unsafe.Pointer
	len   int
	cap   int
}
1
2
3
4
5

https://www.yuque.com/vbaicai/po7hfk/xn277d#exZEu

可以通过 append 方法向切片中添加元素

// 需要一个变量或原变量来接收返回值, 因为切片可能会扩容
s = append(s, "d")
s = append(s, "e", "f")

// 因为slice底层, 实际上是存储了一个长度和一个容量,加一个指向数组的指针
// 在你执行append操作的时候,如果容量不够的话,会扩容并且返回新的slice
1
2
3
4
5
6

可以通过 copy方法对切片进行复制,复制后是两个独立的切片,更改元素相互不影响

c := make([]string, len(s))
copy(c, s)
fmt.Println(c) // [a b c d e f]
1
2
3

# 集合 map

map 是一种无序的键值对的集合, 也被称为映射、字典、 哈希

查看课程示例example/08-map目录的文件

map的声明

// 1. make 方式, 且可以同时对其进行初始化
// 2. 字面量初始化的方式, 底层是 make 方式
1
2

https://www.yuque.com/vbaicai/po7hfk/xn277d#vzDGO

map的本质

https://www.yuque.com/vbaicai/po7hfk/xn277d#PPmWY

map 的访问, 通过”key下标“来访问的, 如果访问的key不存在的话会返回0

注意: 对于不存在的key返回0,如果一个key的值也为0,就很容易产生不必要的误会,因此Go在查询key时返回了两个值,一个是结果,另一个是bool类型的值代表是否存在

r, ok := m["unknow"]
fmt.Println(r, ok) // 0 false
1
2

可以使用 delete 函数来删除 map 中的元素

delete(m, "one")
1

# 遍历 range

数组、切片、map 等都可以使用 range 来遍历

查看课程示例example/09-range目录的文件

通过range可以得到索引和值

package main

import "fmt"

func main() {
	nums := []int{2, 3, 4}
	sum := 0
	for i, num := range nums {
		sum += num
		if num == 2 {
			fmt.Println("index:", i, "num:", num) // index: 0 num: 2
		}
	}
	fmt.Println(sum) // 9

	m := map[string]string{"a": "A", "b": "B"}
	for k, v := range m {
		fmt.Println(k, v) // b 8; a A
	}
	// 可以只要一个值
	for k := range m {
		fmt.Println("key", k) // key a; key b
	}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# 函数 func

查看课程示例example/10-func目录的文件

# 指针 point

查看课程示例example/10-func目录的文件

# 结构体 struct

查看课程示例example/10-func目录的文件

# 错误处理

查看课程示例example/10-func目录的文件

# 标准库