这是我参与「第五届青训营 」伴学笔记创作活动的第 1 天
本堂课重点内容
- Go语言简介
- Go语言基础语法
- Go语言实战
1. Go语言简介
1.1 Go语言的优势
- 高性能、高并发
- 语法简单、学习曲线平缓
- 丰富的标准库:功能丰富且兼容性强并支持高并发的标准库
- 完善的工具链:代码检查、编译、包管理、内置完善的单元测试框架(性能测试及优化)
- 静态链接:仅需要保留一个可执行文件,无需像C那样添加大量的.so文件或者Java那样附加一个大体积的jre
- 快速编译:大量微服务可实现时间小于1分钟的增量编译
- 跨平台:Linux,Windows,Macos,路由器,树莓派
- 垃圾回收:让用户专注于业务开发
2. Go语言基础语法
2.1 部署开发环境
a. 安装Golang
b. IDE
- VScode + Go插件
- Goland
c. 基于云的开发环境
2.2 基础语法
2.2.1 常量与变量
三种声明方式
var a = 1
var b int = 2
c := 3 // 第一和第三种自动推断类型
const country = "China" // 自行通过上下文推断
2.2.2 if-else
if condition {
...
}else if{
...
}else{
...
}
注意点:
- 条件无需加括号
- if及后续代码块不能在一行
- else if 与第一个代码块的右花括号要在同一行,else同理
2.2.3 循环
Go只有for一种循环
for i:=1; i < n; i++{
...
}
Go也具有continue和break关键字
当循环与fmt.Scanf("%d", &i)一起使用时,需要额外吃掉一个换行符,否则后续循环会出现
unexpected newline,原因是换行符留在了缓冲区,如果不清理掉,下次程序将尝试将换行符赋予整型变量导致出错
2.2.4 switch
除了像C或C++那样的用法,Go中的switch还支持不写变量,直接在case处写条件表达式,有点像sql中的case when
switch variable {
case value1:
...
case value2:
...
...
default:
...
}
switch {
case condition 1:
...
case condition 2:
...
...
default:
...
}
2.2.5 数组
import "fmt"
var a[5]int
a[1] = 10
b := [3]int{1, 2, 3}
// 二维数组
var twoD [3][4]float
// 遍历
for i:=1; i < len(a); i++{
...
}
// 打印
fmt.Println("arr: ", a)
2.2.6 切片
// 分配了长度为3,容量为5的切片空间
s := make([]int, 3, 5)
// append操作
s = append(s, 1)
// 索引操作
fmt.Println(s[1:]) // 打印从第一个位置往后的所有元素
fmt.Println(s[:4]) // 打印第四个位置前的所有元素
fmt.Println(s[1:4]) // 打印从第一个位置到第四个位置前的所有元素
// 初始化
well := []string{"w", "e", "l", "l"}
注意点:
- 切片原理是存储了一个数组(指针)、当前长度与容量,当容量不足的时候,此时使用append操作会进行扩容并返回新的切片对象,因此需要使用原始切片对象接收append函数的返回值
- 不像Python,Go中的切片不支持负数索引
- 切片索引规则:左闭右开,如
arr[1: 4],实际上是获取[1, 4)的元素
2.2.7 map
Python中称为dict
// 初始化
m := make(map[string]int) // map[键类型]值类型{初始化值}
mm := map[string]int{"third": 3, "forth": 4}
// 赋值
m["first"] = 1
// 访问元素
res, ok := m["second"] // 0 false(map中不存在该k-v对)
res, ok = m["first"] // 1 truy(map中存在该k-v对)
// 删除元素
delete(m, "first")
注意点:
- Go中的map是完全无序的,既不会按字母表顺序输出,也不会按插入顺序输出,而是随机顺序
2.2.8 range
快速遍历slice或map
nums := []int{1, 2, 3, 4}
sum := 0
for idx, item := range nums{
sum += item
}
m := map[string]int{"a": 1, "b": 2}
for k, v := range m{
...
}
注意点:
- 遍历切片是两个返回值的索引,元素值
- 遍历字典是则是键值对
2.2.9 函数
func main(){
res, err := divide(4, 2) // 2, nil
res, err = divide(4, 0) // 0, error(""denominator can not be 0")
}
func divide(numerator int, denominator int)(res int, err, error){
if denominator == 0{
return 0, errors.New("denominator can not be 0")
}
return numerator / denominator, nil
}
注意点:
- Go中没有try catch概念, 通常由函数的第一个值作为返回值,第二个值作为错误信息
2.2.10 指针
相比于C与C++中的指针,Go中的指针支持的操作有限,其中一个主要作用是对传入的参数进行修改
2.2.11 结构体
type user struct{
name string
password string
}
// 声明结构体的方法,类似类成员方法
func (u user) checkPassword(password string){
return u.password == password
}
func (u *user) resrtPassword(password string){
u.password == password
}
func main(){
u := user{name: "keith", password: "0512"}
u.resrtPassword("1024")
u.checkPassword("1024") // true
}
注意点:
- Go中的结构体可以进行json格式化,下面会进行说明
- Go中的结构体可以使用中括号进行初始化,没有初始化的字段值,会以该字段的默认值进行初始化
- 函数中传入结构体指针,可以避免大体积结构体的拷贝开销
- 结构体方法如果声明了传入的对象为指针类型,则说明该方法可以对结构体进行修改,反之则不能
2.2.12 错误处理
Go中没有try catch概念, 通常由函数的第一个值作为返回值,第二个值作为错误信息
--from 2.2.9
由于该特性,Go中的错误可以通过if-else结构来处理,
func main(){
res, err := divide(4, 2) // 2, nil
if err != nil {
log.Printf("divide function happened error: %w", err)
return
}
log.Printf("divide success, res = %f", res)
res, err = divide(4, 0) // 0, error(""denominator can not be 0")
if err != nil {
log.Printf("divide function happened error: %w", err)
return
}
log.Printf("divide success, res = %f", res)
}
2.2.13 字符串处理
常用的字符串处理函数包括:
- Contains(str string, target string) bool: 判断字符串str是否包含目标字符串
- Count(str string, target string) int: 统计字符串str中目标字符串出现的次数
- HasPrefix(str string, target string) bool: 判断字符串str是否以目标字符串开头
- HasSuffix(str string, target string) bool: 判断字符串str是否以目标字符串结尾
- Index(str string, target string) int: 返回目标字符串在字符串str第一次出现的索引,不存在返回-1
- Join(strs []string, connection string) string: 使用connection连接strs里的所有字符串
- Repeat(str string, times int) string: 将str字符串重复times次,并返回
- Replace(source string, old string, new string, n int) string: 将字符串source中的old替换成new,n指定次数,n=-1时不限制修改次数
- Split(str string, connection string) []string: 将str字符串以connection为分隔符切分
- ToLower(str string) string: 转换成小写
- ToUpper(str string) string: 转换成大写
- len(str string) int: 返回字符串长度
字符串格式化
常用标识:
- %v:打印任意类型的变量
- %+v: 打印详细结果
- %#v:打印进一步详细的结果
2.2.14 JSON处理
import (
"encoding/json"
"fmt"
"strings"
)
type user struct{
Name string `json: "name"`
Password string `json: "password"`
StudentId string `json: "student_id"`
}
func main(){
u1 := user{Name: "keith", Password: "1024", StudentId: "2048"}
buf, err := json.Marshal(u1)
if err != nil {
panic(err) // 抛出错误
}
fmt.Println(string(buf)) // {"name":"keith","password":"1024","student_id":"2048"}
buf = strings.Replace(buf, "1024", "4096", -1)
var u2 user
err = json.Unmarshal(buf, &u2) // 注意传入指针,需要修改属性
if err != nil {
panic(err) // 抛出错误
}
fmt.Printf("%v\b", u2) // {Name:"keith",Password:"4096",StudentId:"2048"}
}
注意点:
- 将需要json序列化的结构体中的属性命名成公开字段,即第一个字母大写
- 可以为每个字段指定json tag,表示该字段在序列化后的字段名
2.2.15 时间处理
标准库:time
常见用法:
2.2.16 数字分析
标准库:strconv
常用函数
- ParseInt(s string, base int, bits int) int :将字符串解析成bits位的base进制的整型。base=0时由编译器自动推断,其他的ParseXXX函数作用类似
- Atoi(s string)(int, error):将一个十进制字符串转换为整型,当输入不合法时,返回一个错误
- itoA(num int)string:将整型转换为字符串
2.2.17 进程信息
标准库:os, os/exec
基础用法
3.实战
3.1 猜数字游戏
注意点:
-
当循环与fmt.Scanf("%d", &i)一起使用时,需要额外吃掉一个换行符,否则后续循环会出现 unexpected newline,原因是换行符留在了缓冲区,如果不清理掉,下次程序将尝试将换行符赋予整型变量导致出错 --from 2.2.3
- 每次执行程序前需要修改随机数种子,否则每次生成的随机数不变。涉及代码:
rand.Seed(time.Now().UnixNano())
3.2 在线词典
使用到的工具:
- curlconvertor(curlconverter.com/go/):用于从浏览器…
- json2go(https:/oktools.netoo/json2go):用于将复杂的json响应字符串解析成固定结构的Go结构体,配合上Go标准库的json.Marshal,能够轻松地解析http响应体
注意点:
- 请求结构体DictRequest,需要为每个字段标注json tag,使其转换成json后字段名与API匹配
- 获得响应时,应该先判断状态码,再继续下一步操作
- 某些API返回的中文是unicode编码的,json.Marshal还帮我们将其编码成UTF-8
3.3 代理服务器
这个程序说明会在下一篇笔记中