Go 学习笔记

Go 语言简介

  • 最大特色:执行性能和开发效率的平衡。
  • 核心特性:
    • 高效的并发编程
    • 内存回收(gc)
    • 编译速度快
    • 函数多返回值
    • 语言交互性
    • 没有异常处理

Go 环境搭建(Mac)

1
vim .bashs_profile
1
2
3
4
export GOROOT=/usr/local/go
export GOPATH=/Users/raohui/go
export GOBIN=$GOROOT/bin
export PATH=$PATH:$GOBIN
1
2
3
4
source ~/.bash_profiles

go verison
go env

Go 命令及其执行原理

Go 的源码文件

Go 的命令

  • go run:专门用来运行命令源码文件的命令。

  • go build:用于编译我们指定的源码文件或代码包以及他们的依赖包。

  • go install:用来编译并安装代码包或者源码文件。

  • go get:用于从远程代码仓库(比如 Github)上下载并安装代码包。

Go 开发工具

IDEGoland

⚠️ IDE 中这两个位置的配置是否正确

Goland 常用快捷键

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
# 打开文件
CTRL + E,打开最近浏览过的文件。
CTRL + SHIFT + E,打开最近更改的文件。
CTRL + N,可以快速打开struct结构体。
CTRL + SHIFT + N,可以快速打开文件。

# 代码格式化
CTRL + ALT + T,可以把代码包在一个块内,例如if{…}else{…}。
CTRL + ALT + L,格式化代码。
CTRL + 空格,代码提示。
CTRL + /,单行注释
CTRL + SHIFT + /,进行多行注释。
CTRL + B,快速打开光标处的结构体或方法(跳转到定义处)。
CTRL + “+/-”,可以将当前方法进行展开或折叠。

# 查找和定位
CTRL + R,替换文本。
CTRL + F,查找文本。
CTRL + SHIFT + F,进行全局查找。
CTRL + G,快速定位到某行。

# 代码编辑
ALT + Q,可以看到当前方法的声明。
CTRL + Backspace,按单词进行删除。
SHIFT + ENTER,可以向下插入新行,即使光标在当前行的中间。
CTRL + X,删除当前光标所在行。
CTRL + D,复制当前光标所在行。
ALT + SHIFT + UP/DOWN,可以将光标所在行的代码上下移动。
CTRL + SHIFT + U,可以将选中内容进行大小写转化。

编码规范

命名规范

包命名

保持 package 的名字和目录保持一致,尽量采取有意义的包名,简短,有意义,尽量和标准库不要冲突。包名应该为小写单词,不要使用下划线或者混合大小写。

1
2
package demo
package main

文件命名

尽量采取有意义的文件名,简短,有意义,应该为小写单词,使用下划线分隔各个单词。

1
my_test.go

结构体命名

  • 采用驼峰命名法,首字母根据访问控制大写或者小写
  • struct 申明和初始化格式采用多行,例如下面:
1
2
3
4
5
6
7
8
9
10
11
// 多行申明
type User struct{
Username string
Email string
}

// 多行初始化
u := User{
Username: "astaxie",
Email: "astaxie@gmail.com",
}

接口命名

  • 命名规则基本和上面的结构体类型
  • 单个函数的结构名以 “er” 作为后缀,例如 Reader , Writer 。
1
2
3
type Reader interface {
Read(p []byte) (n int, err error)
}

变量命名

  • 和结构体类似,变量名称一般遵循驼峰法,首字母根据访问控制原则大写或者小写,但遇到特有名词时,需要遵循以下规则:
    • 如果变量为私有,且特有名词为首个单词,则使用小写,如 apiClient
    • 其它情况都应当使用该名词原有的写法,如 APIClient、repoID、UserID
    • 错误示例:UrlArray,应该写成 urlArray 或者 URLArray
  • 若变量类型为 bool 类型,则名称应以 Has, Is, Can 或 Allow 开头
1
2
3
4
var isExist bool
var hasConflict bool
var canManage bool
var allowGitHook bool

常量命名

常量均需使用全部大写字母组成,并使用下划线分词

1
const APP_VER = "1.0"

如果是枚举类型的常量,需要先创建相应类型:

1
2
3
4
5
6
type Scheme string

const (
HTTP Scheme = "http"
HTTPS Scheme = "https"
)

关键字

下面的列表显示了Go中的保留字。这些保留字不能用作常量或变量或任何其他标识符名称。

break default func interface select
case defer go map struct
chan else goto package switch
const fallthrough if range type
continue for import return var

注释

包注释

每个包都应该有一个包注释,一个位于 package 子句之前的块注释或行注释。包如果有多个 go 文件,只需要出现在一个 go 文件中(一般是和包同名的文件)即可。 包注释应该包含下面基本信息(请严格按照这个顺序,简介,创建人,创建时间):

  • 包的基本简介(包名,简介)
  • 创建者,格式: 创建人: rtx 名
  • 创建时间,格式:创建时间: yyyy/MM/dd

例如 util 包的注释示例如下

1
2
3
// util 包, 该包包含了项目共用的一些常量,封装了项目中一些共用函数。
// 创建人: hanru
// 创建时间: 2019/04/19

结构(接口)注释

每个自定义的结构体或者接口都应该有注释说明,该注释对结构进行简要介绍,放在结构体定义的前一行,格式为: 结构体名, 结构体说明。同时结构体内的每个成员变量都要有说明,该说明放在成员变量的后面(注意对齐),实例如下:

1
2
3
4
5
// User ,用户对象,定义了用户的基础信息
type User struct{
Username string // 用户名
Email string // 邮箱
}

函数(方法)注释

每个函数,或者方法(结构体或者接口下的函数称为方法)都应该有注释说明,函数的注释应该包括三个方面(严格按照此顺序撰写):

  • 简要说明,格式说明:以函数名开头,“,”分隔说明部分
  • 参数列表:每行一个参数,参数名开头,“,”分隔说明部分
  • 返回值: 每行一个返回值

示例如下:

1
2
3
4
5
6
7
// NewtAttrModel , 属性数据层操作类的工厂方法
// 参数:
// ctx : 上下文信息
// 返回值:
// 属性操作类指针
func NewAttrModel(ctx *common.Context) *AttrModel {
}

代码逻辑注释

对于一些关键位置的代码逻辑,或者局部较为复杂的逻辑,需要有相应的逻辑说明,方便其他开发者阅读该段代码,实例如下:

1
2
3
4
// 从 Redis 中批量读取属性,对于没有读取到的 id , 记录到一个数组里面,准备从 DB 中读取
xxxxx
xxxxxxx
xxxxxxx

注释风格

统一使用中文注释,对于中英文字符之间严格使用空格分隔, 这个不仅仅是中文和英文之间,英文和中文标点之间也都要使用空格分隔,例如:

1
// 从 Redis 中批量读取属性,对于没有读取到的 id , 记录到一个数组里面,准备从 DB 中读取

上面 Redis 、 id 、 DB 和其他中文字符之间都是用了空格分隔。

  • 建议全部使用单行注释
  • 和代码的规范一样,单行注释不要过长,禁止超过 120 字符。

代码风格

缩进和折行

  • 缩进直接使用 gofmt 工具格式化即可(gofmt 是使用 tab 缩进的);
  • 折行方面,一行最长不超过 120 个字符,超过的请使用换行展示,尽量保持格式优雅。

我们使用 Goland 开发工具,可以直接使用快捷键:ctrl + alt + L,即可。

语句的结尾

Go 语言中是不需要类似于 Java 需要冒号结尾,默认一行就是一条数据

如果你打算将多个语句写在同一行,它们则必须使用 ;

括号和空格

括号和空格方面,也可以直接使用 gofmt 工具格式化(go 会强制左大括号不换行,换行会报语法错误),所有的运算符和操作数之间要留空格。

1
2
3
4
5
6
7
8
9
10
// 正确的方式
if a > 0 {

}

// 错误的方式
if a>0 // a ,0 和 > 之间应该空格
{ // 左大括号不可以换行,会报语法错误

}

import 规范

import 在多行的情况下,goimports 会自动帮你格式化,但是我们这里还是规范一下 import 的一些规范,如果你在一个文件里面引入了一个 package,还是建议采用如下格式:

1
2
3
import (
"fmt"
)

如果你的包引入了三种类型的包,标准库包,程序内部包,第三方包,建议采用如下方式进行组织你的包:

1
2
3
4
5
6
7
8
9
10
11
import (
"encoding/json"
"strings"

"myproject/models"
"myproject/controller"
"myproject/utils"

"github.com/astaxie/beego"
"github.com/go-sql-driver/mysql"
)

有顺序的引入包,不同的类型采用空格分离,第一种实标准库,第二是项目包,第三是第三方包。

在项目中不要使用相对路径引入包:

1
2
3
4
5
// 这是不好的导入
import “../net”

// 这是正确的做法
import “github.com/repo/proj/src/net”

但是如果是引入本项目中的其他包,最好使用相对路径。

错误处理

  • 错误处理的原则就是不能丢弃任何有返回 err 的调用,不要使用 _ 丢弃,必须全部处理。接收到错误,要么返回 err,或者使用 log 记录下来
  • 尽早 return:一旦有错误发生,马上返回
  • 尽量不要使用 panic,除非你知道你在做什么
  • 错误描述如果是英文必须为小写,不需要标点结尾
  • 采用独立的错误流进行处理
1
2
3
4
5
6
7
8
9
10
11
12
13
// 错误写法
if err != nil {
// error handling
} else {
// normal code
}

// 正确写法
if err != nil {
// error handling
return // or continue, etc.
}
// normal code

语法基础

基础知识

  • 变量
  • 常量
  • iota 关键字
  • 基本数据类型:布尔类型、数值类型、字符串
  • 程序的流程结构:顺序结构、分支结构(if、switch)、循环结构(for、break、continue)

练习

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
const (
A = iota
B
C
D = "haha"
E
F = 100
G
H = iota
I
)
const (
J = iota
)
println(A)
println(B)
println(C)
println(D)
println(E)
println(F)
println(G)
println(H)
println(I)
println(J)

// 运行结果
0
1
2
haha
haha
100
100
7
8
0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 打印乘法表
for i := 1; i < 10; i++ {
for j := 1; j <= i; j++ {
fmt.Printf("%d * %d = %d\t", i, j, i*j)
}
fmt.Println()
}

// 运行结果
1 * 1 = 1
2 * 1 = 2 2 * 2 = 4
3 * 1 = 3 3 * 2 = 6 3 * 3 = 9
4 * 1 = 4 4 * 2 = 8 4 * 3 = 12 4 * 4 = 16
5 * 1 = 5 5 * 2 = 10 5 * 3 = 15 5 * 4 = 20 5 * 5 = 25
6 * 1 = 6 6 * 2 = 12 6 * 3 = 18 6 * 4 = 24 6 * 5 = 30 6 * 6 = 36
7 * 1 = 7 7 * 2 = 14 7 * 3 = 21 7 * 4 = 28 7 * 5 = 35 7 * 6 = 42 7 * 7 = 49
8 * 1 = 8 8 * 2 = 16 8 * 3 = 24 8 * 4 = 32 8 * 5 = 40 8 * 6 = 48 8 * 7 = 56 8 * 8 = 64
9 * 1 = 9 9 * 2 = 18 9 * 3 = 27 9 * 4 = 36 9 * 5 = 45 9 * 6 = 54 9 * 7 = 63 9 * 8 = 72 9 * 9 = 81
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 素数:只能被1和本身整除的数。
for i := 2; i < 100; i++ {
flag := true //记录i是否为素数
for j := 2; j <= int(math.Sqrt(float64(i))); j++ { //判断到根号i就可以,不需要到i的前一个
if i%j == 0 {
flag = false //不是素数了
break
}
}
if flag {
fmt.Println(i)
}
}

// 运行结果
153
370
371
407
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 生成随机数
rand.Seed(time.Now().UnixNano())
for i := 1; i < 10; i++ {
fmt.Println(rand.Intn(90))
}

// 运行结果
53
91
70
57
78
88
33
59
34
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
// 数组
arr := [5]int{15, 23, 8, 10, 7}

// 遍历
for _, value := range arr {
fmt.Println(value)
}

// 冒泡排序
for i := 1; i < len(arr); i++ {
for j := 0; j < len(arr)-i; j++ {
if arr[j] > arr[j+1] {
arr[j], arr[j+1] = arr[j+1], arr[j]
}
}
fmt.Println(arr)
}

// 运行结果
15
23
8
10
7
[15 8 10 7 23]
[8 10 7 15 23]
[8 7 10 15 23]
[7 8 10 15 23]

复合数据类型

复合数据类型:array、slice、map、function、pointer、struct、interface、channel

函数、指针、接口、错误处理

学习资料