0%

Go快速入门.md

Go快速入门

1
2
3
4
5
6
7
8
9
10
11
12
13
Go语言介绍
Go开发环境搭建
Go命令使用
基础语法
数据类型
结构体与方法
流程控制
函数
面向接口
面向对象
常用标准库
异常处理
案例:分析Nginx访问日志多维度TOP

Go语言介绍

Go 语言是谷歌开源的编程语言,出身名门,快速吸引大批开发者的关注和使用,经过短短几年时间, 已经挤进“开发语言排行榜”前10名,Go之所以能够取得如此出色的成绩,与他自身特点及发展密 不可分,Go具有语法简洁、高并发、跨平台等优势!

官方网站:https://go.dev
官方文档:https://go.dev/doc

为什么选择Go?
• 语法简单,易于学习
• 高并发
• 跨平台,基本所有操作系统都能运行 • DevOps领域应用广泛

Go代表项目:Docker、Kubernetes、Etcd、treafik-mesh、caddy

Go开发环境搭建

window

Go软件包下载地址:https://go.dev/dl/

IDE:Goland

image-20220306091757283

linux

image-20220306091905267

1
2
3
4
5
6
7
# tar zxvf go1.17.8.linux-amd64.tar.gz # mv go /usr/local/
# vi /etc/profile.d/go.sh
GOROOT=/usr/local/go
PATH=$PATH:$GOROOT/bin
export GOROOT PATH
# source /etc/profile.d/go.sh
# go version

创建第一个项目

1 创建工作目录

1
mkdir /opt/go

2 设置环境变量

1
2
3
# vi /etc/profile.d/go.sh
export GOPATH=/opt/go
# source /etc/profile.d/go.sh

3 创建工作目录

1
2
# mkdir -p /opt/go/hello
# cd /opt/go/hello

4 启用依赖跟踪

1
2
# go mod init   //创建go.mod,包管理文件
# go mod tidy //清空当前目录的go.mod文件

5 编写代码

1
2
3
4
5
6
7
8
9
package main  // 定义去哪找到我

//1. golang里面的话,只要引用到了,那就必须使用
import "fmt" //引入包或者依赖

func main() {
fmt.Println("hello world")
}

6 运行代码

1
# go run hello.go

数据类型

数据类型:数组

数组:是一个序列的数据结构 slice

什么是序列?
是指它成员都是有序排列,并且可以通过索引访问一个或多个成员。

数组定义:

1
2
3
4
5
6
//数组的好处是什么? 数组的好处是刚开始就定义了容量,不会再增加,可以确定使用的范畴,不浪费。
//数组实例
//我们小区禁止外来停车,100个车位,所以车位比较紧张,那我们应该怎么做呢?那就固定车位,并且禁止外来停车。

number :=[5]string{"川Aabc","川Aabcd","川Aabce","川Aabcf","川Aabdd"}

1
2
3
4
5
6
7
8
package main

import "fmt"

func main(){
a := [5]int{1,2,3,4,5}
fmt.Println(a)
}

数组:切片

image-20220306104250773

切片定义

切片(Slice)是一个拥有相同类型元素的可变长度的序列。它是基于数组类型做的一层封装。它非常灵活,支持自动扩容。

切片是一个引用类型,它的内部结构包含地址长度容量。切片一般用于快速地操作一块数据集合。

切片拥有自己的长度和容量,我们可以通过使用内置的len()函数求长度,使用内置的cap()函数求切片的容量。

1
2
3
4
5
6
7
8
9
10
11
12
//slice的好处是什么? 我不确定这个数据量有多少,可以自动扩容
//定义方式一
a:=make([]int,3)
//定义方式二
a := [5]int{1, 2, 3, 4, 5} //定义一个数组
s := a[1:3] //从数组中切片,此时切片内容[2 3] len=2 cap=4

// 声明切片类型
var a []string //声明一个字符串切片
var b = []int{} //声明一个整型切片并初始化
var c = []bool{false, true} //声明一个布尔切片并初始化
var d = []bool{false, true} //声明一个布尔切片并初始化
1
2
3
4
5
6
7
8
9
10
11
12
package main

import "fmt"

func main(){
a:=make([]int,3) //定义初始化数组slice,3caps
a=append(a,12) //此时数组内容 [0 0 0 12]
//golang的index 是从下标0开始
a[1]=10 //此时数组内容 [0 12 0 12]
fmt.Println(a)

}

数据类型:Map

Map:是一个具有映射关系的数据结构。用于存储有一定关系的元素。

map定义

1
2
3
4
5
//go语言是强类型的
//所以map的key,values都需要定义类型map-name:=map[key-type]values-type{k:v,k:v}
//当类型未知时或者可以有多种类型,可以使用interface{}类接受所有类型
a:=map[string]interface{}{"name":"zhangsan","age":18,"sex":false,"score":100}
//map的使用场景?不用固定数据类型,可以顺义增加减少

注:字典通过key来访问value,因此字典中的key不允许重复。

1
2
3
4
5
6
7
8
9
package main

import "fmt"
func main(){
a:=map[string]interface{}{"name":"zhangsan","age":18,"sex":false,"score":100}
b:=map[string]int{"num":1,"age":20,}
fmt.Println(a)
fmt.Println(b)
}

结构体与方法

结构体定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// struct的使用场景?固定好数据类型,不可以变更

// 定义一个订单结构图
type Order struct {
Uid string
Price string
Type int
time string
ProductId int
}

// 用结构体创建对象
order:=new(Order)
// 为结构体赋值
order.Uid="11" //因为Uid定义类型为string,所以只能赋值为string,定义好的数据类型不能变更

案例一

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

import "fmt"

type Order struct {
Uid string
Price string
Type int
time string
ProductId int
}

func main(){
order:=new(Order)
order.Uid="111"
order.Price="108"
order.Type=1
order.time="20220306110200"
order.ProductId=98
fmt.Println(order)
}

执行结果

1
&{111 108 1 20220306110200 98}

案例二

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

import "fmt"

type student struct {
name string
age int
score int
sex bool
}

func main() {
//liming :=new(student)
liming := &student{}
liming.name = "tigerfive"
liming.age = 22
liming.score = 100
liming.sex = true
fmt.Println(liming)
}

方法[函数]

定义方法

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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
func 函数名(参数)(返回值){
函数体
}
情况一:
func abc(){
fmt.Println("abc")
}

情况二:
函数的参数中如果相邻变量的类型相同,则可以省略类型,例如:
func intSum(x, y int){
ret := x + y
fmt.Println(ret)
}

情况三:
可变参数是指函数的参数数量不固定。Go语言中的可变参数通过在参数名后加`...`来标识。
func intSum2(x ...int) int {
fmt.Println(x) //x是一个切片
sum := 0
for _, v := range x {
sum = sum + v
}
return sum
}

情况四:
多返回值
func calc(x, y int) (int, int) {
sum := x + y
sub := x - y
return sum, sub
}

情况五:
返回值命名
func calc1(x, y int) (sum, sub int) {
sum = x + y
sub = x - y
return
}

返回值补充:当我们的一个函数返回值类型为slice时,nil可以看做是一个有效的slice,没必要显示返回一个长度为0的切片。
func someFunc(x string) []int {
if x == "" {
return nil // 没必要返回[]int{}
}
...
}

匿名函数
func(参数)(返回值){
函数体
}
闭包:
//闭包指的是一个函数和与其相关的引用环境组合而成的实体。简单来说,`闭包=函数+外层引用环境`。 首先我们来看一个例子
func adder() func(int) int {
var x int
return func(y int) int {
x += y
return x
}
}

方法调用

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
情况一:
abc()
情况二:
intSum(1,3)
情况三:
a := intSum2(1,2)
fmt.Println(a)
情况四:
c,b := calc(1,2)
fmt.Println(c,b)
情况五:
e,f := calc(1,2)
fmt.Println(e,f)
匿名函数:
//自执行函数:匿名函数定义完加()直接执行
func(x, y int) {
fmt.Println(x + y)
}(10, 20)
闭包:
var f = adder()
fmt.Println(f(10)) //10
fmt.Println(f(20)) //30
fmt.Println(f(30)) //60

f1 := adder()
fmt.Println(f1(40)) //40
fmt.Println(f1(50)) //90
1
函数名首字母大写代表是 Public,首字母小写代表是private

特殊案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package main

import "fmt"

func PcCount(a,b *int){ //引入的一定是指针类型
*a=100 //*一定要取当前指针类型指向的值那里,并且重新赋值
*b=200
}

func main() {
a:=10
b:=20
PcCount(&a,&b) //传递地址到函数里面取
fmt.Println(a,b)
}

new与make的区别

  1. 二者都是用来做内存分配的。
  2. make只用于slice、map以及channel的初始化,返回的还是这三个引用类型本身;
  3. 而new用于类型的内存分配,并且内存对应的值为类型零值,返回的是指向类型的指针。

运算符

Go 语言内置的运算符有:

  1. 算术运算符
  2. 关系运算符
  3. 逻辑运算符
  4. 位运算符
  5. 赋值运算符

算术运算符

运算符 描述
+ 相加
- 相减
* 相乘
/ 相除
% 求余

注意: ++(自增)和--(自减)在Go语言中是单独的语句,并不是运算符。

关系运算符

运算符 描述
== 检查两个值是否相等,如果相等返回 True 否则返回 False。
!= 检查两个值是否不相等,如果不相等返回 True 否则返回 False。
> 检查左边值是否大于右边值,如果是返回 True 否则返回 False。
>= 检查左边值是否大于等于右边值,如果是返回 True 否则返回 False。
< 检查左边值是否小于右边值,如果是返回 True 否则返回 False。
<= 检查左边值是否小于等于右边值,如果是返回 True 否则返回 False。

逻辑运算符

运算符 描述
&& 逻辑 AND 运算符。 如果两边的操作数都是 True,则为 True,否则为 False。
|| 逻辑 OR 运算符。 如果两边的操作数有一个 True,则为 True,否则为 False。
! 逻辑 NOT 运算符。 如果条件为 True,则为 False,否则为 True。

位运算符

位运算符对整数在内存中的二进制位进行操作。

运算符 描述
& 参与运算的两数各对应的二进位相与。 (两位均为1才为1)
| 参与运算的两数各对应的二进位相或。 (两位有一个为1就为1)
^ 参与运算的两数各对应的二进位相异或,当两对应的二进位相异时,结果为1。 (两位不一样则为1)
<< 左移n位就是乘以2的n次方。 “a<<b”是把a的各二进位全部左移b位,高位丢弃,低位补0。
>> 右移n位就是除以2的n次方。 “a>>b”是把a的各二进位全部右移b位。

赋值运算符

运算符 描述
= 简单的赋值运算符,将一个表达式的值赋给一个左值
+= 相加后再赋值
-= 相减后再赋值
*= 相乘后再赋值
/= 相除后再赋值
%= 求余后再赋值
<<= 左移后赋值
>>= 右移后赋值
&= 按位与后赋值
|= 按位或后赋值
^= 按位异或后赋值

流程控制

for循环

Go 语言中的所有循环类型均可以使用for关键字来完成。

for循环的基本格式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
//基本写法
for 初始语句;条件表达式;结束语句{
循环体语句
}
//无限循环
for {
循环体语句
}
//for range(键值循环)
//Go语言中可以使用`for range`遍历数组、切片、字符串、map 及通道(channel)。 通过`for range`遍历的返回值有以下规律:
//1. 数组、切片、字符串返回索引和值。
//2. map返回键和值。
//3. 通道(channel)只返回通道内的值。

条件表达式返回true时循环体不停地进行循环,直到条件表达式返回false时自动退出循环。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
func main(){
//循环方法一
for i := 0;i<10;i++ {
fmt.Println(i)
}
//循环方法二
age := [5]int{11,20,30,40,50}
for k,v := range age {
fmt.Println("k=", k, "v=", v)
}

//循环方法三 死循环
//for true {
// fmt.Println(123)
//}
}

if循环

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
package main

import "fmt"

func main(){
name:="tigerfive"
age:=20
sex:=false


if name == "tigerfive" {
fmt.Println("你是tigerfive,我喜欢你")
if age > 30 {
fmt.Println("你太老了,但是我不想跟你说话")
}else {
fmt.Println("我们可以讲话,因为你没过30")
if sex==false {
fmt.Println("我可以嫁给你")
}else {
fmt.Println("我不是蕾丝,那我们没戏")
}
}
} else {
fmt.Println("你不是tigerfive,我不喜欢你")
}
}

输出结果

1
2
3
你是tigerfive,我喜欢你
我们可以讲话,因为你没过30
我可以嫁给你

if条件判断特殊写法

1
2
3
4
5
6
7
8
if score := 65; score >= 90 {
fmt.Println("A")
} else if score > 75 {
fmt.Println("B")
} else {
fmt.Println("C")
}

switch case

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"
)

func main(){
// switch
// 开关语句
mouth:=3
switch mouth {
case 1:
fmt.Println("1月份")
case 2:
fmt.Println("2月份")
case 3:
fmt.Println("3月份")
case 4:
fmt.Println("4月份")
case 5:
fmt.Println("5月份")
case 6:
fmt.Println("6月份")
}
}

执行结果

1
3月份

算术运算符

goto(跳转到指定标签)

goto语句通过标签进行代码间的无条件跳转。goto语句可以在快速跳出循环、避免重复退出上有一定的帮助。Go语言中使用goto语句能简化一些代码的实现过程。 例如双层嵌套的for循环要退出时:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var breakFlag bool
for i := 0; i < 10; i++ {
for j := 0; j < 10; j++ {
if j == 2 {
// 设置退出标签
breakFlag = true
break
}
fmt.Printf("%v-%v\n", i, j)
}
// 外层for循环判断
if breakFlag {
break
}
}

break(跳出循环)

break语句可以结束forswitchselect的代码块。

break语句还可以在语句后面添加标签,表示退出某个标签对应的代码块,标签要求必须定义在对应的forswitchselect的代码块上。 举个例子:

1
2
3
4
5
6
7
8
9
for i := 0; i < 10; i++ {
for j := 0; j < 10; j++ {
if j == 2 {
break BREAKDEMO1
}
fmt.Printf("%v-%v\n", i, j)
}
}
fmt.Println("...")

continue(继续下次循环)

continue语句可以结束当前循环,开始下一次的循环迭代过程,仅限在for循环内使用。

continue语句后添加标签时,表示开始标签对应的循环。例如:

1
2
3
4
5
6
7
8
9
10
11
12
func continueDemo() {
forloop1:
for i := 0; i < 5; i++ {
// forloop2:
for j := 0; j < 5; j++ {
if i == 2 && j == 2 {
continue forloop1
}
fmt.Printf("%v-%v\n", i, j)
}
}
}

作业

1
2
slice定义中: a:=make([]int,3) 的3 怎么解释?
二维数组怎么定义

指针

区别于C/C++中的指针,Go语言中的指针不能进行偏移和运算,是安全指针。

要搞明白Go语言中的指针需要先知道3个概念:指针地址、指针类型和指针取值。

任何程序数据载入内存后,在内存都有他们的地址,这就是指针。而为了保存一个数据在内存中的地址,我们就需要指针变量。

比如,”如果感觉能力配不上你的野心,那就去学习”这句话是我的座右铭,我想把它写入程序中,程序一启动这句话是要加载到内存(假设内存地址0x123456),我在程序中把这段话赋值给变量A,把内存地址赋值给变量B。这时候变量B就是一个指针变量。通过变量A和变量B都能找到我的座右铭。

Go语言中的指针不能进行偏移和运算,因此Go语言中的指针操作非常简单,我们只需要记住两个符号:&(取地址)和*(根据地址取值)。

指针地址和指针类型

每个变量在运行时都拥有一个地址,这个地址代表变量在内存中的位置。Go语言中使用&字符放在变量前面对变量进行“取地址”操作。 Go语言中的值类型(int、float、bool、string、array、struct)都有对应的指针类型,如:*int*int64*string等。

在对普通变量使用&操作符取地址后会获得这个变量的指针,然后可以对指针使用*操作,也就是指针取值。

1
2
3
4
5
6
7
8
9
10
11
12
package main

import "fmt"

func main(){
name:="tigerfive" //变量赋值
//xname:=&name
//xxname:=*xname
fmt.Println(name) //打印变量值
fmt.Println(&name) //打印变量地址[即地址]
fmt.Println(*&name) //指针取值
}

执行结果

1
2
3
tigerfive
0xc00003c1f0
tigerfive

分解指针

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
package main

import (
"fmt"
"reflect"
)

func main(){
//分解指针
//有人告诉主办方来了,主办方也为他准备了位置,但是还没到,默认会到的
//一个变量的初始值,还没有赋予新的值,相当于占位
var xname bool
var xage int
fmt.Println(xname,"===",&xname) //false === 0xc00000a0c8
fmt.Println(xage,"===",&xage) //0 === 0xc00000a0e0

//有人告诉主办方他要来,但是没有确定,所以主办方还没为他准备位置
var xxname *string //[指针类型]
fmt.Println(reflect.TypeOf(xxname)) //*string
fmt.Println(xxname) //因为没有初始化地址,所以是空指针 //<nil>

xxname =new(string) //tom确定要来了,主办方也准备了椅子 new就是实例化,申请内存地址,指针有值
fmt.Println(xxname)
fmt.Println(&xxname) //指针也是值
fmt.Println(*xxname)
*xxname = "tom" //tom坐到了凳子上
fmt.Println(*xxname)
}

执行结果

1
2
3
4
5
6
7
8
false === 0xc00000a0c8
0 === 0xc00000a0e0
*string
<nil>
0xc00003c1f0
0xc000006030

tom

cpu、进程、线程、协程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// cpu 指挥官
// 进程 1团,2团,3团
// 线程 工兵,排兵,连长(人)
// 协程 发动攻击---地道战,空战,地雷战,敌后武装
// 协程的最终执行者是线程!!! 线程调度的资源是进程申请的,进程里
// 协程相当于方案(事情)
//goroutine==协程
// 实际做事的只有线程
//在计算机里没有协程的概念,只有最轻量级的线程。
//工兵今天要达到area的一个据点,需要工兵、炮兵和步兵{人,ps:线程}

//进程:进程是系统进行资源分配的基本单元,有独立的内存空间
//线程:线程是CPU调度和分派的基本单位,线程依附于进程存在,每个线程回共享父进程的资源
//协程:协程是一种用户态的轻量级线程,协程的调度完全由用户控制,协程见切换只需要套保存任务的上下文,没有内核的开销

开始协程案例

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

import (
"fmt"
)

func sumx(max int){
res := 0
for i := 1; i <=max; i++ {
fmt.Println(i)
}

}
func main() {
//协程序是异步执行的,所以等主程序退出了,但是协程还没有执行完成,所以看不到打印值!
go sumx(100) //开启协程

}

解决协程异常问题

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

import (
"fmt"
"sync"
)

func main() {
num:=10
var wg sync.WaitGroup //定义等待锁
for i:=1; i<num; i++{
go func(index int) { //开启协程 index把下面的i这个值传递到func里面!func是一个匿名函数
defer wg.Done() //每执行完一个协程,Done会锁计数减一 defer延后执行
fmt.Println(index)
}(i) //这个i是传递参数
wg.Add(1) //每产生一个协程Add会锁计数加一
}
//在协程里面 锁的目的是保证协程一定要完成之后才能退出
wg.Wait() //阻塞,等待锁计数为零
fmt.Println("done")
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Go语言的 defer 语句会将其后面跟随的语句进行延迟处理,在 defer 归属的函数即将返回时,将延迟处理的语句按 defer 的逆序进行执行,也就是说,先被 defer 的语句最后被执行,最后被 defer 的语句,最先被执行。
defer测试案例
package main

import "fmt"

func main() {
defer fmt.Println(1)
defer fmt.Println(2)
defer fmt.Println(3)
fmt.Println(4)
fmt.Println(5)
fmt.Println(6)

}

执行结构
4
5
6
3
2
1

channel待添加

接口

1
2
3
4
5
6
7
8
//接口,类,实例化
//goLang不是面向对象的语言,但是可以写成面向对象的形式(模拟)!!!
//车:有4个轮子,有方向盘(接口)
//大众车,byd, bmw,奔驰,他们都有4个轮子,有方向盘。(类,对象)
//造车(实例化)

//k8s
//k8s cni就是接口(标准) flannel就是类(实现了这些标准) 把flannel转到k8s集群,就是实例化
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
package main

import "fmt"

func main() {
fxpan := NewDazongCar().BuildDriver()
wheel := NewDazongCar().BuildFourWhell()
fmt.Println("方向盘:",fxpan)
fmt.Println("轮子:",wheel)
}

// 定义规范[interface+方法] -- 创建类[结构体] -- 实例化

//车的规范
type Car interface {
BuildFourWhell() int //
BuildDriver() int
}

//这里是类
type DazongCar struct {

}

//创建类似面向对象的类
//创建实例化
func NewDazongCar() *DazongCar {
//NewDazongCar 这里new的前缀不是必须的 *DazongCar 这个是指针函数返回
return &DazongCar{} //返回一个DazongCar数据类型的指针
}

func (this *DazongCar)BuildFourWhell() int{
return 4
}

func (this *DazongCar)BuildDriver() int{
return 1
}
//goland里面的继承,就是需要类, 也就是struct函数去实现 interface的方法,这样就"类似"类的继承


1
2
3
4
链接: https://pan.baidu.com/s/1bdiE9zGIL5EmUkPoZHIOdg ;提取码: mdck

Client-go之Informer机制初探
https://blog.csdn.net/u013276277/article/details/108576227

下周

gin gorm