1. Go 语言介绍

1.1 Go 语言优势

  1. 语法简单 - 开发效率高(例如下面短短十行代码就实现了一个简单的 HTTP 服务器)
1
2
3
4
5
6
7
8
9
10
package main

import (
"net/http"
)

func main() {
http.Handle("/", http.FileServer(http.Dir(".")))
http.ListenAndServer(":8080", nil)
}
  1. 集各种语言的优势,有丰富的标准库和完善的工具链:大量参考 c 和 python

    • c 语言:静态类型系统、指针操作等特点
    • Python 语言:简洁语法、垃圾回收等优势
  2. 执行性能高: 直接编译成二进制,部署非常简单

    • 想运行 Python 程序则需要预先安装好 Python

    • 想运行 Java 程序则需要预先安装 jdk

    • 而想运行 go 程序则不用事先安装任何软件,因为它本身就是 exe 文件可以直接执行

  3. 并发编程非常高效:goroutine

  4. 编译速度快:比 C++、Java 编译快

1.2 Go 语言能做什么?

应用场景

  • Web 开发
    • gin (ginex)、hertz、beego 等
  • 容器虚拟化
    • docker、k8s、istio
  • 中间件
    • etcd、tidb、infuluxdb、nsq 等
  • 区块链
    • 以太坊、fabric
  • 微服务
    • kitex、go-zero、dapr、rpcx、kratos、dubbo-go

使用情况

  • 国内使用热度超过国外
  • 大量国内公司使用
    • 字节跳动、百度、腾讯、阿里、B 站、小米、滴滴、京东、360、七牛云、知乎、美团、OPPO 等

使用原因

  • 最初使用的 Python,由于性能问题换成了 Go
    • Python 是一种解释型语言。解释型语言在运行时需要解释器将代码翻译成机器语言,因此其执行速度相对较慢。
    • Go 是一种编译型语言。而编译型语言则直接将代码编译成机器语言,执行速度相对较快。
  • C++ 不太适合在线 Web 业务
  • 早期团队非 Java 背景
  • 性能比较好
  • 部署简单,学习成本低
  • 内部 RPC 和 HTTP 框架的推广

1.3 开发环境

入门配置教程:Golang-01:如何快速运行 Golang 程序

1.3.1 安装 Golang

直接打开浏览器输入 go.dev 网址进行下载即可。

https://go.dev/

如果打不开的话可以尝试使用 Golang 中国的 studygolang 镜像。

https://studygolang.com/dl

另外,如果访问 github 的速度非常慢的话,可以配置 go mod proxy,打开下面的网页按照提示操作即可,配置完成后下载第三方包的速度会大大加快。

https://goproxy.cn/

1.3.2 配置开发环境

常用的 Golang 编辑器 / IDE 有两个,我一般会更习惯 Goland 进行开发~

  1. vscode

https://code.visualstudio.com/

vscode 是一款由微软开发的,能运行在 Mac、Windows 和 Linux 上的跨平台开源代码编辑器。它可以通过扩展程序为编辑器实现代码高亮、代码提示编译调试、文档生成等功能,配置完成后可以视为一个齐全的 IDE。

  1. Goland

https://www.jetbrains.com/go/download/#section=windows

Goland 是由 JetBrains 公司开发的一个新的商业 IDE,相比 vscode,它在重构、代码生成等方面做的更好。Goland 是一个收费软件,我们可以直接从官网下载,然后可以 30 天免费试用。

2. 变量与常量

2.1 定义与使用

2.1.1 命名规则

  1. 一个名字必须以一个字母(Unicode 字母)或下划线开头,后面可以跟任意数量的字母、数字或下划线
  2. 大写字母和小写字母是不同的:heapSort 和 Heapsort 是两个不同的名字
  3. Go 语言程序员推荐使用驼峰式命名,当名字由几个单词组成时优先使用大小写分隔,而不是优先用下划线分隔
  4. 像 ASCII 和 HTML 这样的缩略词则避免使用大小写混合的写法,它们可能被称为 htmlEscape、HTMLEscape 或 escapeHTML,但不会是 escapeHtm
  5. Go 语言中类似 if 和 switch 的关键字有 25 个;关键字不能用于自定义名字,只能在特定语法结构中使用
1
2
3
4
5
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

变量名可见性

  1. 如果一个名字是在函数内部定义,那么它就只在函数内部有效。如果是在函数外部定义,那么将在当前包的所有文件中都可以访问。
  2. 名字的开头字母的大小写决定了名字在包外的可见性。如果一个名字是大写字母开头的,那么它将是导出的,也就是说可以被外部的包访问,例如 fmt 包的 Printf 函数就是导出的,可以在 fmt 包外部访问。
  3. 包本身的名字一般总是用小写字母。

2.1.2 如何定义变量?

  1. 变量必须先定义才能使用
  2. go 语言是静态语言,要求变量的类型和赋值类型一致
  3. 变量名不能冲突
  4. 简洁变量定义不能用于全局变量
  5. 变量是有零值,也就是默认值
  6. 定义了变量一定要使用,否则会报错

下面声明变量的方式都等价:实践中一般使用前两种形式中的某个,初始值重要的话就显式地指定变量的值,否则指定类型使用隐式初始化。

  • 第一种形式,是一条短变量声明,最简洁,但只能用在函数内部,而不能用于包变量。
  • 第二种形式依赖于字符串的默认初始化零值机制,被初始化为 ""。
  • 第三种形式用得很少,除非同时声明多个变量。
  • 第四种形式显式地标明变量的类型,当变量类型与初值类型相同时,类型冗余,但如果两者类型不同,变量类型就必须了。
1
2
3
4
5
6
7
8
// 方法一
s := ""
// 方法二
var s string
// 方法三
var s = ""
// 方法四
var s string = ""

注意:

自增语句 i++ 给 i 加 1;这和 i+=1 以及 i=i+1 都是等价的。对应的还有 i-- 给 i 减 1。它们是语句,而不像 C 系的其它语言那样是表达式。所以 j=i++ 非法,而且 ++ 和 -- 都只能放在变量名后面,因此 --i 也非法。

2.1.3 常量定义须知

  1. 常量定义的时候指定的值不能修改,常量尽量大写
  2. 常量类型只可以定义 bool、数值(整数、浮点数和复数)和字符串
  3. 不曾使用的常量,没有强制使用的要求
  4. 显示指定类型的时候,必须确保常量左右值类型一致
  5. 同时定义多个常量时,需要注意未指明的常量将引用前面定义的值:
1
2
3
4
5
6
7
8
const (
x int = 16
y
s = "abc"
z
m
)
fmt.Println(x, y, s, z, m) //16 16 abc abc abc

2.2 Iota 的使用细节

iota,特殊常量,可以认为是一个可以被编译器修改的常量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const(
ERR1 = iota
ERR2
ERR25
ERR3
)
fmt.Println(ERR1, ERR2, ERR25, ERR3) //0 1 2 3

const(
ERR1 = iota + 1
ERR2
ERR25 = "ha"
ERR3
ERR4 = iota
)
fmt.Println(ERR1, ERR2, ERR25, ERR3, ERR4) //1 2 ha ha 4

注意:

  1. 如果中断了 iota,那么必须显示的恢复,后续会自动递增
  2. 自增类型默认是 int 类型
  3. iota 能简化 const 类型的定义
  4. 每次出现 const 的时候,iota 初始化为 0

2.3 匿名变量的定义与使用

匿名变量是一种特殊的变量,使用 _ 表示,它只占位不存储值,可用于忽略函数返回值中不需要的部分,或者在多重赋值中丢弃不需要的变量。

1
2
3
4
5
6
7
8
9
10
//匿名变量
var _ int

func a() (int, bool){
return 0, false
}
_, ok := a()
if ok {
//打印
}

3. Go 基本数据类型

3.1 Bool 类型

布尔类型的值只可以是常量 true 或者 false,比如:var b bool = true。

3.2 数值类型

注意:

byte 和 rune 主要是用于存储字符,byte 存储的范围更小,rune 存储的范围更大,可以包括中文。

3.3 String 类型

3.3.1 底层存储原理

1
var str string = "hello"

go 语言和 c 语言对于字符串结尾的判定条件有所不同,c 语言会认为在结尾处加上 \0 就代表这个字符串的结束。但是这就会限制内容中不能再出现 \0,如果出现就会发生不可预估的后果。

因此,在 go 语言中并没有采用这种方式,而是在起始地址后边,多存了一个长度。

注意:

在起始地址后存的长度并不是表示字符个数,而是表示字节个数。

3.3.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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
package main

import (
"fmt"
"strings"
)

func main() {
//打印字符串字符长度
fmt.Println(len("hello")) // 5
b := "你好"
fmt.Println(len(b)) // 6

//是否包含
name := "golang - good"
fmt.Println(strings.Contains(name, "go")) // true

//出现次数
fmt.Println(strings.Count(name, "o") // 3

//分割字符串
fmt.Println(strings.Split(name, "-")) // [golang good]

//字符串是否包含前缀,是否包含后缀
fmt.Println(strings.HasPrefix(name, "gol")) // true
fmt.Println(strings.HasSuffix(name, "ood")) // true

//查找子串出现的位置
fmt.Println(strings.Index(name, "go")) // 0

//重复打印
fmt.Println(strings.Repeat("hello", 2)) // hellohello

//子串替换
fmt.Println(strings.Replace(name, "go", "java", 1)) // javalang - good

//大小写转换
fmt.Println(strings.ToLower("GO")) // go
fmt.Println(strings.ToUpper("java")) // JAVA

//去掉字符串两头的特殊字符
fmt.Println(strings.Trim("#hello #go#", "#")) // hello #go
}

3.3.3 数字解析

在 go 语言中,关于字符串和数字类型之间的转换的函数都在 strconv 这个包下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main

import (
"fmt"
"strconv"
)

func main() {
f, _ := strconv.ParseFloat("1.234", 64) // 解析为float64类型
fmt.Println(f) // 1.234

n1, _ := strconv.ParseInt("111", 10, 64) // 以十进制解析为int64类型
fmt.Println(n1) // 111

n2, _ := strconv.ParseInt("0x100", 0, 64) // 进制参数为0,表示根据字符串的前缀自动判断进制,这里0x表示十六进制,所以解析为int64类型
fmt.Println(n2) // 4096

n3, _ := strconv.Atoi("123") // 解析为int类型
fmt.Println(n3) // 123

n4, err := strconv.Atoi("AAA")
fmt.Println(n4, err) // 0 strconv.Atoi: parsing "AAA": invalid syntax
}

3.3.4 格式化输出

注意:

以字母 f 结尾的格式化函数,如 log.Printf 和 fmt.Errorf,都采用 fmt.Printf 的格式化准则。而以 ln 结尾的格式化函数,则遵循 Println 的方式,以跟 %v 差不多的方式格式化参数,并在最后添加一个换行符(译注:后缀 f 指 format,ln 指 line)。

  1. 缺省格式和类型
格式化后的效果 动词 描述
[0 1] %v 缺省格式
[]int64{0, 1} %#v go 语法打印
[]int64 %T 类型打印
  1. 整形(缩进、进制类型、正负符号)
格式化后的效果 动词 描述
15 %d 十进制
+15 %+d 必须显示正负符号
__15 %4d Pad 空格 (宽度为 4,右对齐)
15__ %-4d Pad 空格 (宽度为 4,左对齐)
1111 %b 二进制
17 %o 八进制
f %x 16 进制,小写
  1. 字符(有引号、Unicode)

Value:65(Unicode letter A)

格式化后的效果 动词 描述
A %c 字符
‘A’ %q 有引号的字符
U+0041 %U Unicode
U+0041 ‘A’ %#U Unicode 有引号
  1. 浮点(缩进、精度、科学计数)

Value:123.456

格式化后的效果 动词 描述
1.234560e+02 %e 科学计数
123.456000 %f 十进制
  1. 字符串/字节 silce(引号、缩进、16 进制)

Value:"cafe"

格式化后的效果 动词 描述
cafe %s 字符串原样输出
__cafe %6s 宽度为 6,右对齐
  1. 案例
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
package main

import "fmt"

type point struct {
x, y int
}

func main() {
s := "hello"
n := 123
p := point{1, 2}
fmt.Println(s, n) // hello 123
fmt.Println(p) // {1 2}

fmt.Printf("s=%v\n", s) // s=hello
fmt.Printf("n=%v\n", n) // n=123
fmt.Printf("p=%v\n", p) // p={1 2}
fmt.Printf("p=%+v\n", p) // p={x:1 y:2}
fmt.Printf("p=%#v\n", p) // p=main.point{x:1, y:2}

f := 3.141592653589793
fmt.Println(f) // 3.141592653589793
fmt.Printf("%.2f\n", f) // 3.14
}

3.3.5 只读特性

下标访问字符串:

1
2
3
str := "golang"
fmt.Println("%c\n", s1[2]) //可以执行
//s1[2] = 'e' //错误

注意:

go 语言中不能用下标直接去修改字符串内容,但是可以用来访问。

原因是 go 认为上面这样的字符串是不会被修改的,所以会把这样定义的字符串内容分配到只读内存段。

如果想修改字符串内容的话,可以转化成 slice,但是可能会同时修改底层字符串的内容。

如果想脱离这个只读的限制,并且想修改时不会影响到底层字符串的内容。那么可以转换成字节 slice,这样就会为 slice 变量重新分配一段内存,同时会也会拷贝原来字符串的内容。

1
2
3
byteStr := ([]byte)(s1)
byteStr[2] = 'x'
fmt.Printf("%c\n", byteStr[2]) //x

举个具体点的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package main

import(
"fmt"
"unicode/utf8"
)

func main() {
s := "go语言"
arr := []byte(s)
fmt.Println(arr) //[103 111 232 175 173 232 168 128]
fmt.Println(arr[2], s[2]) //232 232
arr[2] = 9
//s[2] = 9 //字符串不能修改

fmt.Println(len(s), len(arr)) //8 8

brr := []rune(s)
fmt.Println(brr) //[103 111 35821 35328]
fmt.Printf("%d %d %s %s\n", brr[2], s[2], string(brr[2]), string(s[2])) //35821 232 语 è
fmt.Println(utf8.RuneCountInString(s)) //4
}

3.3.6 高性能字符串拼接

1
2
3
4
username := "张三"
age := 24
address := "北京"
mobile := "18989898989"

性能要求不高,平时一般采用:

1
userMsg := fmt.Sprinf("用户名:%s,年龄:%d,地址:%s,电话:%s", username, age, address, mobile)

性能要求高:

1
2
3
4
5
6
7
8
9
10
11
var builder string.Builder
builder.WriteString("用户名:")
builder.WriteString(username)
builder.WriteString(",年龄:")
builder.WriteString(strconv.Itoa(age))
builder.WriteString(",地址:")
builder.WriteString(address)
builder.WriteString(",电话:")
builder.WriteString(mobile)
re := builder.String()
fmt.Println(re)