Golang-练基本功:扎马步、练投篮、练运球
@TOC
前言
云时代已经逐渐到来,在工作中学习中,我们时不时会用到Go语言,那就开始学起吧,与诸君共勉。
开发效率跟执行效率不能兼得
比较
- 执行效率高,但是开发和编译低效:C++
- 执行效率低,但是编译效率高:.NET、Java
- 有较高执行速度、编译速度和开发速度:Go
- Go1.5发布后,解决了广为诟病的GC时延问题【STM】,可以说Go语言在服务端开发方面,几乎抹平了所有弱点
- Go是一个容器语言,是google29年发布的系统开发语言,基于编译、垃圾收集和并发的编程语言
Go的发展历程中的特殊点:
- 1.7的context
- 1.11的modules
- 1.13 的error嵌套
- 1.18:类型参数的实现,也就是俗称的泛型
基本语法:https://www.runoob.com/go/go-program-structure.html
一个程序只能有一个main包,也得有一个main包。也就是说package main和func main必不可少
变量
- 定义变量类型用:var,形式:var 变量名 变量类型
1
2
3
4
5
6
7//定义单个变量:
var 变量名 变量类型 = 符合类型的赋值内容
//短变量声明并初始化【必须使用在函数中】。即可以用:=,:=代表自动推导
name := "hhb",自动推导类型
fmt.Printf("%T",name)代表打印name的类型1
2
3
4
5
6
7
8
9
10//定义多个变量,定义完成之后再赋值。同时定义或者叫声明多个变量:
var{
name string
age int
...
}
//定义完成之后再赋值
name = "hhb"
age = 22 - 变量的变化,用地址空间中存储的的不同值体现
- 取变量的地址:&变量名
- 变量的交换:go语言与其他语言不一样:b,a=a,b
- 匿名变量:go语言中你定义了这个变量但是不去使用这个变量,是会报错的,所以不能乱定义变量。但是有时候咱们作为接收方时接收到不想要的但又不能不要的变量时,就可以使用匿名变量实现无视或者说忽视某个变量的目标
- 变量的作用域:局部变量【只能在函数内使用】和全局变量
- 几种不同定义方式
- 显式定义:const URL string = “…”
- 隐式定义:const URL = “…”
- 同时定义多个常量:const a,b,c,d= “xxxx”,3.12,1,false
- 变量类型:https://www.runoob.com/go/go-data-types.html
go语言中的特殊常量:iota,是一个常量(的)计数器
- 可以被编译器修改的特殊常量,iota在新的const关键字出现时【const内部的第一行之前】将被重置为0,iota就像const语句块中的每一行的行索引。
- 比如,第一个iota等于0,那么iota在新一行被使用时iota的值会自动加1,并且如果是同一组常量【也就是这一组常量在一个const()中,只要有一个常量出现,iota就自动加1】,如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18const(
a = iota //iota=0,也就是a = 0
b = iota //iota = 1,即b = 1
c = iota //iota = 2,即c = 2
d = "hu"
e //e = hu,会跟随上一个常量d
f = 100
g //g = 100,会跟随上一个常量f
h = iota //iota = 7,也就是h = 7
i //iota增为8,也就是i = 8
)
上面的形式可以简化为
const(
a = iota
b
c
)
数据类型
基本类型
- 基本类型之布尔:var 变量名 bool,默认值为false
- 基本类型之数值类型:var 变量名 int/float64,四舍五入
- 尽量使用float64,32位容易丢失精度
- 基本类型之字符串型:
- var str string
- 双引号和单引号,单引号括起来的都是字符,字符对应的类型都是int整形【所以打印时用的是…%d…】,对应的字符串打印时用的是…%s…
- 字符串拼接依旧可以和java一样,用+进行字符串拼接
- \依旧可以做转义字符
值传递之一:值类型
- 值类型操作的是数据本身:int、string、bool、float64、array…
值传递之二:基础类型、array、struct
- 数组:arr := [元素个数]元素类型{x,y,…}
数据类型转换:
- Go语言不存在隐式类型转换,所以Go语言中的数据类型转换都必须显式声明
1 | |
- 转换也不能瞎转换,int不能强转为bool;大转小也会有失真的风险
字符串
- string中的字符不能修改
- 可以打印长度,len(),可以str[i]去借助for循环遍历或者说获取每个字符
运算符:https://www.runoob.com/go/go-operators.html
- 算术、关系、逻辑、位运算【加解密常用位运算,移位等操作可实现加密】、赋值运算符
- 流程控制:顺序、选择、循环:https://www.runoob.com/go/go-decision-making.html
- go中只有for循环
- for range循环,可以用来便利数组、切片…,返回下标和对应的值
- if 条件 {}
- switch 条件 {}
- fallthrough,case穿透,强制执行后面的case中的语句
- break终止整个判断,跳出
- continue结束本次单次循环,进行下一次循环
打印:fmt.Sprintf 、fmt.Printf【格式化输出】、fmt.Print【普通打印输出】、fmt.Println【打印后并换行】
- https://www.runoob.com/go/go-fmt-printf.html
- https://www.runoob.com/go/go-fmt-sprintf.html
- 键盘输入与输出:fmt.Scan…
1
2
3
4
5
6func main(){
var x int
var y float64
fmt.Scanln(&x, &y) //遇到Scanln会阻塞,等待键盘输入
}
- 键盘输入与输出:fmt.Scan…
函数:
1 | |
- Go语言最少有个main函数
- 函数声明告诉了编译器函数的名称、返回类型、参数
- 形参与实际参数顺序、个数要一一对应
- 可变参数最多只有一个func methonName(num …int){},可变参数要放到形参列表的最后一个
值传递 [你把arr传入一个函数中,实际上传入函数的是这个数组拷贝的副本,在函数中魔改的也是这个数组的副本,这个数组原来的身体内容不会发生变化] 和引用传递【操作同一个地址空间,不是副本】
- 引用传递之引用类型:
- 引用类型操作的是数据的地址:slice、map、channel…
- 切片,slice,就是一个可以扩容的数组,s1 := []元素类型{x, y,
- 引用类型操作的是数据的地址:slice、map、channel…
函数中变量的作用域:局部变量和全局变量
- 局部变量遵循就近原则
递归函数与动态规划【自顶向下、自底向上】
defer:指定defer修饰的函数延迟到最后执行,但是如果defer修饰的函数有传参过程,就会立即执行传参,只不过这个函数调用过程会被延迟到最后才执行
函数的数据类型
- 比如函数f1(),如果加了括号就是函数的调用,f1如果不加括号,函数就是一个变量【那就然是一个变量了,不就可以对f1这个函数变量进行一定类型的赋值了嘛】
1
2
3
4
5
6
7
8
9
10
11
12
13
14func main() {
fmt.Printf("%T", f1)
var f2 func(int, int)
f2 = f1//f2跟f1就是一个东西了,或者说f1与f2指向同一个内存地址中的函数
f2(21, 38)
fmt.Println("f2的内存地址空间:", f2, "f1的内存地址空间:", f1) //f2的内存地址空间: 0x10c9600 f1的内存地址空间: 0x10c9600
}
func f1(a, b int) {
fmt.Println(a, b)
}
匿名函数:没有名字的函数,调用时可以直接在函数体后面加上(),就算是匿名函数自己调用了自己
1 | |
- 并且匿名函数可以有参数、返回值
1
2
3
4
5
6
7
8func(a, b int){
fmt.Println("...")
}(1, 2)
func(a, b int) int {
return a + b
fmt.Println("...")
}(1, 2)
回调函数:将匿名函数作为另外一个函数的参数。比如,f1()函数作为f2()函数的参数,f2()函数就叫做高阶函数【接收了一个函数作为参数的函数】;f1()函数就叫做回调函数【作为另外一个函数的参数】
- 想调用同一个函数产生不同的结果时,可以考虑回调函数。把一个函数当作另一个函数的参数传进去
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
29package main
import "fmt"
func main() {
result := add(11, 21)
fmt.Println(result)
result1 := highLevel(11, 22, add)
fmt.Println(result1)
}
func add(a, b int) int {
fmt.Println(a, b)
return a + b
}
func highLevel(a, b int, fun func(int, int) int) int {
res := fun(a, b)
return res
}
fun1 := highLevelNoName(a, b, func(a int, b int) int {
if b == 0{
fmt.Println("...")
return 0
}
return a / b
})
fmt.Println(fun1)
闭包结构:将匿名函数作为另外一个函数的返回值,可以形成闭包结构
- 一个外层函数中有内层函数,这个内层函数中可以操作外层函数的局部变量,并且该外层函数的返回值就是这个内层函数,这个内层函数和外层函数的局部变量,统称为闭包结构
- 正常的局部变量会随着函数的调用而创建随着函数的结束而销毁,但是在闭包结构中,如果内层函数在使用变量的话,外层函数的局部变量并不会随着外层函数的结束而销毁【因为你内层函数操作或者说调用了外层函数的局部变量】
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
30package main
import "fmt"
func main() {
r1 := increment()
fmt.Println(r1) //此时内层函数是没有执行的,所以只会打印出来外层函数func的地址
res1 := r1()
fmt.Println(res1) //此时调用外层函数increment(),内层函数才会执行
//或者这种写法也行,也能调用内层函数
fmt.Println(increment()())
//或者拆开写也行,也能调用内层函数
r1 := increment()()
fmt.Println(r1)
}
//定义一个自增函数increment(),这个自增函数的返回值为func() int
func increment() func() int {
//定义一个局部变量i
i := 0
//定义一个匿名函数,给变量i自增并返回i
fun := func() int {
i++
return i
}
return fun //返回内层函数,才符合闭包结构
}
泛型:https://www.bilibili.com/video/BV1KG4y1f79A/?spm_id_from=333.999.0.0&vd_source=148c9a901a3d9b458bd84120bb215c94
巨人的肩膀
All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.


