Go工程师 导学 Go语言基础入门与编程思想来源:https://class.imooc.com/sale/go --ccmouse老师
做个人学习记录
- 基本语法
- 指针,值类型/引用类型
- 切片与map
- error机制
- panic/recover机制
- struct及其方法
- struct的内嵌
- interface
- 函数式编程
- goroutine
- channel/select
- 并发编程模式
- context机制
- 超时机制
- json格式处理
- 微服务
- grpc
- 领域划分服务
- 领域防入侵
- 数据一致性保证(不使用事务)
- Docker+k8s部署
- 服务治理
- Go主流框架、库
- zap
- grpc及中间件
- jwt的验证机制
- 图片上传端到端三方协作
- 数据库
- mongodb
- 索引操作的原子性
- 基于真实mongodb的单元测试框架
- 中间件
- rabbitmq
- websocket(与前端进行通信)
- 前端
- typescript、css(短小篇幅)
- 异步编程
- 小程序开发
Go语言的安装领域驱动的全栈开发,从前端出发开始做一个产品的原型,从这个原型开始定义我们的业务模型,才开始做后端
- 官网:https://go.dev/dl/
- 国内下载:https://studygolang.com/dl
- 国内镜像:
- https://goproxy.cn/
- https://proxy.golang.com.cn
本课程使用的IDEgo env -w GO111MODULE=on
go env -w GOPROXY=https://goproxy.cn,direct
- Goland/Intellij IDEA+Go插件 (Go语法基础及Bobby老师的电商项目)
- VSCode(ccmouse 老师的租辆酷车项目)
- 优点:免费
- 缺点:对Go语言的支持上不如Goland。主要体现在代码重构或者对接口的支持上面
Goland的安装点击转到对应页面进行下载(笔者已经装好了其开发环境,大家可以网上找教程跟着安装即可,这里不做过多赘述,如有下载安装遇到问题欢迎私信我)
-
go mod 的配置
- 初始化项目时选择go module的方式,再次配置了国内的镜像
-
File Watcher 和 goimports的配置
- 保证我们在保存文件的时候一律去运行goimports
-
运行方式配置
- 选择按照File去运行
- 删除行
- 前进/后退
- 转到定义
使用var关键字
go中变量定义了以后要求有一个合理的初始值,也就是ZeroValue,且定义之后必须要对变量进行使用。
C:变量定义了之后其值是不确定(未决)的
Java:null
-
var a,b,c bool
-
var s1,s2 string = "hello","world"
-
可放在函数内,或直接放在包内
-
使用var()集中定义变量
让编译器自动决定类型
-
var a,b,i,s1,s2 = true,false,3,"hello","world"
使用:=定义变量
-
a,b,i,s1,s2 := true,false,3,"hello","world"
-
只能在函数内使用
- bool,string
- (u) (int,int8,int16,int32,int64),uintptr
- byte,rune(字符型int32)
- Unicode、UTF-8、UTF-16
- float32,float64,complex64(float32,float32),complex128(float64,float64)
-
i = − 1 sqrt{-1} −1
-
复数:3+4 i
- 3:实部
- 4:虚部
-
KaTeX parse error: Undefined control sequence: abs at position 1: ̲a̲b̲s̲{3 + 4i}= 3 2 + 4 2 sqrt{3^2 + 4^2} 32+42 = 5
-
i 2 = − 1 , i 3 = − i , i 4 = 1 , . . . i^2 = -1,i^3 = -i,i^4 = 1,... i2=−1,i3=−i,i4=1,...
-
e i φ = c o s φ + i s i n φ e^{ivarphi}=cosvarphi + isinvarphi eiφ=cosφ+isinφ
-
KaTeX parse error: Undefined control sequence: abs at position 1: ̲a̲b̲s̲{e^{ivarphi}}=…
-
e 0 = 1 , e i π 2 e^0 = 1, e^{ifrac{pi}{2}} e0=1,ei2π
-
e i π = − 1 , e i 3 2 π = − i , e i 2 π = 1 e^{ipi} = -1, e^{ifrac{3}{2}pi}=-i,e^{i2pi}=1 eiπ=−1,ei23π=−i,ei2π=1
- e i π = − 1 e^{ipi} = -1 eiπ=−1
-
类型转换是强制的
-
var a,b int =3,4
-
var c int = math.Sqrt(a*a+b*b) ❌
-
var c int = int(math.Sqrt(float64(a*a + b*b))) ❓
- float会导致一定的不精确性
-
const filename = "abc.txt"
-
const数值可作为各种类型使用
-
const a,b = 3,4
-
var c int = int(math.Sqrt(a*a + b*b))
- 普通枚举类型
- 自增值枚举类型
func enums() {
const (
//一个自增值的种子
//iota 0-1-2-3...
cpp = iota
_
python
golang
javascript
)
const (
b = 1 << (10 * iota)
kb
mb
gb
tb
pb
)
fmt.Println(cpp, javascript, python, golang)
fmt.Println(b, kb, mb, gb, tb, pb)
}
变量定义要点回顾
- 变量类型写在变量名之后
- 编译器可推测变量类型
- 没有char,只有rune(int32)
- 原生支持复数类型
func bounded(v int) int {
if v > 100 {
return 100
} else if v < 0 {
return 0
}else {
return v
}
}
if contents, err := ioutil.ReadFile(filename); err != nil {
fmt.Println(string(contents))
} else {
fmt.Println("cannot print file contents:", err)
}
- if的条件里可以赋值
- if的条件里赋值的变量作用域就在这个if语句里
func eval(a, b int, op string) int {
var result int
switch op {
case "+":
result = a + b
case "-":
result = a - b
case "*":
result = a * b
case "/":
result = a / b
default:
//报错,让程序停下来
panic("unsupported operator:" + op)
}
return result
//switch会自动break 除非使用fallthrough
}
- switch后可以没有表达式
sum := 0
for i := 1; i <= 100; i++ {
sum += i
}
- for的条件里不需要括号
- for的条件里可以省略初始条件、结束条件、递增表达式
//将整数转成二进制表达式
func convertToBin(n int) string {
result := ""
for ; n > 0; n /= 2 {
lsb := n % 2
result = strconv.Itoa(lsb) + result
}
return result
}
- 省略初始条件,相当于while
//while 死循环
func forever(){
for {
fmt.Println("abc")
}
}
//应用:goroutines
- 无限循环
- for,if后面的条件没有括号
- if条件里也可定义变量
- 没有while
- switch不需要break,也可以直接switch多个条件
-
func eval(a,b int, op string) int
-
函数返回多个值时可以起名字
func div(a,b int)(int,int) { return a/b,a%b }func div(a, b int) (q, r int) { //建议写法 return a/b,a%b //另外一种写法,但不建议 } -
仅用于非常简单的函数
-
对于调用者而言没有区别
-
函数作为参数
func apply(op func(int,int) int, a,b int) int { fmt.Printf("Calling %s with %d, %dn", runtime.FuncForPC(reflect.ValueOf(op).Pointer()).Name(),a,b) return op(a,b) } -
可变参数列表
func sum(numbers ...int) int { s := 0 for i := range numbers { s += numbers[i] } return s }
- 返回值类型写在最后面
- 可返回多个值
- 函数作为参数
- 没有默认参数,可选参数
var a int = 2 var pa * int = &a *pa = 3 fmt.Println(a)
- 指针不能运算
- 只能进行指向,而不能像C/C++那样 ++
值传递?引用传递?
//lang : C++
void pass_by_val(int a){
a++;
}
void pass_by_ref(int& a){
a++;
}
int main(){
int a = 3;
pass_by_val(a);
printf("After pass_by_val: %dn",a);
pass_by_ref(a);
printf("After pass_by_ref: %dn",a);
}
// Output:3 4
Go语言使用值传递?引用传递?
- Go语言只有值传递一种方式
func swap(a,b * int) {
*b,*a = *a,*b
}
值传递
采用指针进行传递,&a的拷贝了a的地址到 func f() 中,如果在func f()中修改pa所指向的地址里面的内容 *p=xx,则 a的值被修改为了 xx
数组、切片和容器这是一个object(对象)cache,其类型为Cache,将cache传递给 func f()。cache的结构其本身并不带有data,通常只是一个指向data的指针(pData)。当拷贝了一份cache到func f()中。func f()中的pData与。这两个pData所指向的是同一个data。拷贝的是指针,但其指向的仍旧是同一份内容。
考虑:Go语言中我们自定义的一些类型,它在定义的时候要考虑到我们用它是当作指针来用还是说当作值来用。此处的cache是作为值,其可以被很安全的拷贝到其他的位置进行使用。
不能够应用的场景:除了pData之外还要维护一些状态。比如说这里的data有多个,cache中有多个数据,这种情况就不能用作值传递的一个类型。
//map
package main
func main() {
cache := make(map[string]string)
cache["name"] = "ccmouse"
}
数组
//创建数组arr1(数组内的值为对应类型的零值)
var arr1 [5]int
//创建数组并对其进行赋值 arr2[0] = 1 ...
arr2 := [3]int{1, 2, 3}
//采用 ... 语法糖,接收可变个参数,并对其赋值
arr3 := [...]int{2, 4, 6, 8, 10}
var grid [4][5]bool
数组的遍历
//得到最大的value,和最大value所在的索引位置 i
maxi := -1
maxValue := -1
for i,v := range numbers {
if v > maxValue {
maxi, maxValue = i,v
}
}
//求出值的总和
sum := 0
for _,v := range numbers {
sum += v
}
- 可通过_省略变量
- 不仅range,任何地方都可以通过 _省略变量
- 如果只要i,可写成 for i := range numbers
- 意义明确,美观
- C++:没有类似能力
- Java/Python:只能for each value,不能同时获取i,v
-
[10]int 和[20]int是不同类型
-
调用func f(arr [10]int)会 拷贝 数组
-
在go语言中一般不直接使用数组
arr := [...]int{0,1,2,3,4,5,6,7}
//左取右不取
s := arr[2:6]
s[0] = 10
- Slice本身没有数据,是对底层array的一个view(视图)
- arr的值变为[0 1 10 3 4 5 6 7]
s := arr[2:6] s = s[:3] s = s[1:] s = arr[:]Slice的扩展
arr := [...]int{0,1,2,3,4,5,6,7}
s1 := arr[2:6]
s2 := s1[3:5]//不会报错,取到s1中对应位置的cap值
- s1的值为[2 3 4 5],s2的值为[5 6]
- slice可以向后扩展,不可以向前扩展
- s[i]不可以超越len(s),向后扩展不可以超越底层数组cap(s)
- ptr 指向slice开头的元素
- len 说明slice的长度
- 当用 []进行取值的时候,只能取到len里面的值,当下标 ≥ geq ≥ len会报错(下标越界)
- cap 代表整个array,从ptr开始到结束的整个的一个长度
- s3,s4,s5的值为?arr的值为?
arr := [...]int{0, 1, 2, 3, 4, 5, 6, 7}
s1 := arr[2:6]
s2 := s1[3:5]
s3 := append(s2, 10)
s4 := append(s3, 11)
s5 := append(s4, 12)
//s1 = 2 3 4 5
//s2 = 5 6
//s3 = 5 6 10
//s4 and s5: 开辟一个新的arr,将旧arr中的值拷贝到新arr中。
//s4 = 5 6 10 11
//s5 = 5 6 10 11 12
//若arr有人用则不GC,没人用就GC
//arr = 0 1 2 3 4 5 6 10
- 添加元素时如果超越cap,系统会重新分配更大的底层数组
- 由于值传递的关系,必须接收append的返回值
- s = append(s,val)
//对slice的一些操作
fmt.Println("Deleting elements from slice")
s2 = append(s2[:3], s2[4:]...)
printSlice(s2)
fmt.Println("Popping from front")
front := s2[0]
s2 = s2[1:]
fmt.Println("Popping from back")
tail := s2[len(s2)-1]
s2 = s2[:len(s2)-1]
fmt.Println(front, tail)
printSlice(s2)
Map
m := map[string]string {
"name": "ccmouse",
"course":"golang",
"site":"com",
"quality":"notbad",
}
- map[K] V,map[K1]map[K2]V
-
创建:make(map[string]int)
m := map[string]string{ "name": "ccmouse", "course": "golang", "site": "imooc", "quality": "notbad", } m2 := make(map[string]int) // m2 == empty map var m3 map[string]int //m3 == nil //nil 和 empty map可以进行混用 fmt.Println(m, m2, m3) -
获取元素:m[key]
-
key不存在时,获得Value类型的初始值(string->空串"")
-
用value,ok := m[key]来判断是否存在key
-
用delete删除一个key
- 使用range 遍历key,或者遍历key,value对
- 不保证遍历顺序,如需顺序,需手动对key排序(是一个无序的字典)
- 使用len获得元素个数
-
map使用哈希表,必须可以比较相等
-
除了slice,map,function的内建类型都可以作为key
-
Struct类型不包含上述字段(slice,map,function),也可作为key
https://leetcode-cn.com/problems/longest-substring-without-repeating-characters/description/
-
abcabcbb -> abc
-
bbbbb ->b
-
pwwkew ->wke
自行查看leetcode的题解,略过
对于每一个字母x
-
lastOccurred[x]不存在,或者< start ->无需操作
-
lastOccured[x] >= start ->更新start
-
更新lastOccurred[x],更新maxLength
- 使用range遍历pos,rune对
- 使用utf8.RuneCountInString获得字符数量
- 使用len获得字节长度
- 使用[]byte获得字节
strings.xxx
- Fields,Split,Join
- Contains,Index
- ToLower,ToUpper
- Trim,TrimRight,TrimLeft
- go语言仅支持封装,不支持继承和多态
- go语言没有class,只有struct
type TreeNode struct {
Left,Right *TreeNode
Value int
}
结构的创建
root := TreeNode{value: 3}
root.left = &TreeNode{}
root.right = &TreeNode{5, nil, nil}
root.right.left = new(TreeNode)
- 不论地址还是结构本身,一律使用 . 来访问成员
//添加工厂函数
func createTreeNode(value int)*TreeNode {
//无&且返回->stack//&且返回 ->heap
return &TreeNode{Value: value}
//堆上分配完了之后参与垃圾回收
}
root.Left.Right = createTreeNode(2)
- 使用自定义工厂函数
- 注意返回了局部变量的地址!
- 不需要知道
func (node TreeNode) print() {
fmt.Print(node.Value)
}
- 显示定义和命名方法接收者
func (node *TreeNode) setValue(value int) {
node.Value = value
}
-
只有使用指针才可以改变结构内容
-
nil指针也可以调用方法!
- 要改变内容必须使用指针接收者
- 结构过大也考虑使用指针接收者
- 一致性:如有指针接收者,最好都是指针接受者
- 值接收者是go语言特有
- 值/指针接受者均可接收值/指针
- 名字一般使用CamerCase
- 首字母大写:public
- 首字母小写:private
- 每个目录一个包
- main包包含可执行入口
- 为结构定义的方法必须放在同一个包内
- 可以是不同文件
如何扩充系统类型或者别人的类型
- 定义别名:最简单
- 使用组合:最常用
- 使用内嵌:需要省下许多代码
- 依赖的概念
- 依赖管理的三个阶段GOPATH,GOVENDOR,go mod
-
默认在~/go(unix,linux),%USERPROFILE%go(windows)
-
历史:Google将20亿行代码,9百万个文件放在一个repo里
- 每个项目有自己的vendor目录,存放第三方库
- 大量第三方依赖管理工具:glide,dep,go dep…
- 由go命令统一的管理,用户不必关心目录结构
- 初始化:go mod init
- 增加依赖:go get
- 更新依赖:go get [@v…], go mod tidy
- 将旧项目迁移到go mod: go mod init, go build ./…
2022-04-28
对于基础知识部分进行二刷,对细节知识点进行了一定的阐释



