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
linux
1 2 3 4 5 6 7 GOROOT=/usr/local/go PATH=$PATH :$GOROOT /bin export GOROOT PATH
创建第一个项目 1 创建工作目录
2 设置环境变量
3 创建工作目录
4 启用依赖跟踪
5 编写代码
1 2 3 4 5 6 7 8 9 package main import "fmt" func main () { fmt.Println("hello world" ) }
6 运行代码
数据类型 数据类型:数组 数组:是一个序列的数据结构 slice
什么是序列? 是指它成员都是有序排列,并且可以通过索引访问一个或多个成员。
数组定义:
1 2 3 4 5 6 number :=[5 ]string {"川Aabc" ,"川Aabcd" ,"川Aabce" ,"川Aabcf" ,"川Aabdd" }
1 2 3 4 5 6 7 8 package mainimport "fmt" func main () { a := [5 ]int {1 ,2 ,3 ,4 ,5 } fmt.Println(a) }
数组:切片
切片定义 切片(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 a:=map [string ]interface {}{"name" :"zhangsan" ,"age" :18 ,"sex" :false ,"score" :100 }
注:字典通过key来访问value,因此字典中的key不允许重复。
1 2 3 4 5 6 7 8 9 package mainimport "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 type Order struct { Uid string Price string Type int time string ProductId int } order:=new (Order) order.Uid="11"
案例一
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package mainimport "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的区别
二者都是用来做内存分配的。
make只用于slice、map以及channel的初始化,返回的还是这三个引用类型本身;
而new用于类型的内存分配,并且内存对应的值为类型零值,返回的是指向类型的指针。
运算符 Go 语言内置的运算符有:
算术运算符
关系运算符
逻辑运算符
位运算符
赋值运算符
算术运算符
运算符
描述
+
相加
-
相减
*
相乘
/
相除
%
求余
注意: ++
(自增)和--
(自减)在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 { 循环体语句 }
条件表达式返回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) } }
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月份") } }
执行结果
算术运算符 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) } if breakFlag { break } }
break(跳出循环) break
语句可以结束for
、switch
和select
的代码块。
break
语句还可以在语句后面添加标签,表示退出某个标签对应的代码块,标签要求必须定义在对应的for
、switch
和 select
的代码块上。 举个例子:
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++ { 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