GO的基础语法
# 1、递归(Recursive)
一个方法内部可以调用他自己。
package main
import "fmt"
func Recursive(n int){
if(n > 10){
return
}
else
Recursive(n + 1)
}
2
3
4
5
6
7
8
9
10
- 若方法使用不当,会出现goroutine栈溢出情况(Stack Overflow)

解决办法:
治标:考虑通过增加goroutine栈的大小解决。因为一个goroutine只有2KB。
治本:找到对应的递归代码进行修改逻辑。一般出现溢出问题肯定是逻辑不对。
# 2、函数编程入门
# (1)方法作为变量
方法本身就可以赋值给某个变量,而这个变量就可以直接发起调用。
package main
import "fmt"
func Add(a int,b int) (int ,error) {
return a+b ,nil
}
func main(){
myFunc := Add //myFunc本身是一个变量,只是这个变量等于方法Add
res, _ = myFunc(1,2)
fmt.Println(res)
}
2
3
4
5
6
7
8
9
10
11
3
# (2)局部变量
可以在一个方法内部声明一个局部变量,它的作用域就在本方法内。
package main
func main(){
fn := func(name string) string{
return "hello " + name
}
fn("Demon")
}
2
3
4
5
6
7
hello Demon
# (3)方法作为返回值
方法作为一个返回值返回给变量/方法。(闭包的一种应用,因为golang没有构造函数的说法)
package main
func Add(a int,b int) (int ,error) {
return a+b ,nil
}
//方法作为一个返回值返回给方法,意思是我返回了一个返回string的无参数方法
func ReturnName () func() string{
return func() string{
return "hello world"
}
}
func main(){
//方法作为一个返回值返回给变量
fn(Add(),2)
ReturnName()
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
5
hello world
# (4)立即执行函数
在方法内部可以声明一个匿名方法,但是需要立刻发起调用。(defer比较经常会使用到)
Q:为什么需要立刻发起调用?
A:因为是匿名函数,没有名字。如果不立刻调用后面都没有办法调用了
package main
import "fmt"
func ReturnName () {
fn := func() string{
return "hello world"
} //返回值赋值给了fn
fmt.Println(fn) //输出fn
}
func main(){
ReturnName()
}
2
3
4
5
6
7
8
9
10
11
12
hello world
# (5)闭包(closure)
格式:方法 + 它绑定的运行上下文。
闭包使用不当可能会引起内存泄漏问题,因为它不会被垃圾回收!
package main
import "fmt"
func Closure(age int) func() int {
//返回的这个函数就是闭包
//引用到了Closure这个方法的入参
var age = 0
return func() int{
age ++
return age
}
}
func main(){
str := Closure()
//str已经从Closure方法中返回了
//但是str还是需要用到age
fmt.Println(str())
fmt.Println(str())
fmt.Println(str())
//重新赋值后数值会重新计算
str = Closure()
fmt.Println(str())
fmt.Println(str())
fmt.Println(str())
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
1
2
3
1
2
3
大致逻辑如下图所示:

# (6)不定参数
不定参数是指最后一个参数,可以传入任意多个值;在方法内部可以被当成切片来使用。(使用比较多的是Option模式)
注意!!!
必须是最后一个参数才可以声明为不定参数!
使用any传值时,如果使用切片写法,它只会传一个切片的值!!
package main
import "fmt"
//不定参数带类型:只能输出相关类型的数据
func YourName(name string , alias ...string) {
if len(alias) >0 {
fmt.Println(alias)
}
}
//不定参数+any:输出任意类型的数据,建议需要类型断言
func YourNameAny(name string , alias ...any) {
if len(alias) >0 {
fmt.Println(alias)
}
}
func main(){
YourName("Demon")
YourName("Demon","Angela")
YourName("Demon","Angela","爱摸鱼的Demon")
//切片写法
alias := []string{"Angela","爱摸鱼的Demon"}
YourName("Demon",alias...)
//带any
YourNameAny("Demon","爱摸鱼的Demon",true)
//带any的切片
YourNameAny("Demon",alias) //此处不可以加...
}
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
Angela
Angela 爱摸鱼的Demon
Angela 爱摸鱼的Demon
爱摸鱼的Demon true
[[Angela 爱摸鱼的Demon]]
# (7)defer
允许在返回的前一刻执行一段逻辑,即defer(延迟调用)。类似于栈(后进先出)。
一个方法中不能超过8个defer
package main
import "fmt"
func Defer() {
defer func() {
fmt.Println("The first Defer")
}()
defer func() {
fmt.Println("The second Defer")
}()
}
func main() {
Defer()
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
The second Defer
The first Defer
defer 执行顺序示意图:

Q:通常都用闭包来写的defer,那defer执行时如何确定里面的值?
A:作为参数传入的:定义defer的时候就确定了;作为闭包引入的:执行defer对应的方法时才确定。
package main
import "fmt"
func main() {
DeferClosure()
DeferClosureI()
}
//将i的值先赋值后打印
func DeferClosure() {
i := 0
defer func() {
fmt.Println(i)
}()
i = 1
}
//将i的值先打印后赋值
func DeferClosureI() {
i := 0
defer func(i int) { //倾向于这种写法
fmt.Println(i)
}(i)
i = 1
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
1
0
defer 修改返回值:
package main
import "fmt"
func main() {
fmt.Println(DeferReturn())
fmt.Println(DeferReturnV1())
fmt.Println(DeferReturnV2())
}
//没带名字的返回值,不可以修改返回值
func DeferReturn() int {
a := 0 //这边赋值时,地址假设为A
defer func() {
a = 1 //这边在修改时,地址为B,与A不同,所以无法修改到值
}()
return a
}
//带名字的返回值,可以修改返回值
func DeferReturnV1() (a int) {
a = 0
defer func() {
a = 1
}()
return a
}
type MyStruct struct {
name string
}
//修改指针,只是修改到指针指向的结构体,而不是修改指针
func DeferReturnV2() *MyStruct {
a := &MyStruct{
name: "Jerry",
}
defer func() {
a.name = "Tom"
}()
return a
}
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
0
1
&{Tom}

# (8)控制结构
1、if-else if-else
package main
import "fmt"
func main() {
fmt.Println(IfElseIf(15))
}
func IfElseIf(age int) string {
if age >= 18 {
return "成年了"
} else if age >= 12 {
return "青少年"
} else { //else / else if只能跟在右大括号(})后面,换行会报错
return "小孩子"
}
//return "小孩子" //此处等同于上面的else
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
青少年
if-else支持一种新的写法,可以在if-else块里定义一个新的局部变量。
package main
import (
"fmt"
)
func main() {
fmt.Println(IfNewVariable(15, 155))
}
func IfNewVariable(start int, end int) string {
//distance 一旦离开if-else范围就无法使用了
if distance := end - start; distance > 100 {
return "距离太远了"
} else if distance > 60 {
return "距离比较近了"
} else {
return "太近啦"
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
距离太远了
2、for循环
for循环有很多中写法
1)基础写法
package main
import "fmt"
func main() {
ForLoop()
}
func ForLoop() {
for i := 0; i < 10; i++ {
fmt.Printf("%d ", i)
}
}
2
3
4
5
6
7
8
9
10
0 1 2 3 4 5 6 7 8 9
for循环中可以不写第三段,但是会跳不出循环,须将第三段放入循环内部自增。
package main
import "fmt"
func main() {
ForLoop()
}
func ForLoop() {
for i := 0; i < 10; {
fmt.Printf("%d ", i)
i++
}
}
2
3
4
5
6
7
8
9
10
11
0 1 2 3 4 5 6 7 8 9
for循环中也可以不写第一段,但是须要在for循环前定义好实参。
package main
import "fmt"
func main() {
ForLoop()
}
func ForLoop() {
i := 0
for ; i < 10; i++ {
fmt.Printf("%d ", i)
}
}
2
3
4
5
6
7
8
9
10
11
0 1 2 3 4 5 6 7 8 9
2)类似于while循环(Go语言中没有while循环)
package main
import "fmt"
func main() {
ForLoopWhile()
}
func ForLoopWhile() {
i := 0
for i < 10 {
fmt.Printf("%d ", i)
i++
}
}
2
3
4
5
6
7
8
9
10
11
12
0 1 2 3 4 5 6 7 8 9
警惕!!!死循环操作!!!谨慎使用!!!没有办法退出会引起CPU 100%导致CPU满载!
package main
import "fmt"
func DieLoop() {
i := 0
for true {
fmt.Printf("%d ", i)
i++
}
for {
fmt.Printf("%d ", i)
i++
}
}
2
3
4
5
6
7
8
9
10
11
12
13
加上判定条件并用break跳出死循环
package main
import "fmt"
func LoopBreak() {
i := 0
for {
if i >= 10 {
break
}
fmt.Printf("%d ", i)
i++
}
}
func main() {
LoopBreak()
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
0 1 2 3 4 5 6 7 8 9
continue:跳过当前步骤
package main
import "fmt"
func LoopContinue() {
for i := 0; i < 10; i++ {
if i%2 == 0 {
continue
}
fmt.Printf("%d ", i)
}
}
func main() {
LoopContinue()
}
2
3
4
5
6
7
8
9
10
11
12
13
1 3 5 7 9
3)for-range(用于遍历数组、切片和map)
遍历数组/切片:
使用两个迭代参数:一个是下标,第二个是值
使用单个迭代参数:下标
package main
import "fmt"
func main() {
ForRangeLoop()
}
func ForRangeLoop() {
fmt.Println("遍历数组")
arr := [3]string{"a", "b", "c"}
for i, val := range arr {
fmt.Printf("arr[%d]=%s\n", i, val)
}
//写法2
arrint := [3]int{10, 20, 30}
for i := range arrint {
fmt.Printf("arr[%d]=%d\n", i, arrint[i])
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
遍历数组
arr[0]=a
arr[1]=b
arr[2]=c
arr[0]=10
arr[1]=20
arr[2]=30
遍历map:
使用两个迭代参数:一个是key,第二个是value
使用单个迭代参数:key
map的遍历是随机的
package main
import "fmt"
func main() {
ForRangeLoop()
}
func ForRangeLoop() {
fmt.Println("遍历map")
m := map[string]string{
"key1": "value1",
"key2": "value2",
}
for key, value := range m {
fmt.Printf("key: %s, value: %s\n", key, value)
}
//写法2
for key := range m {
fmt.Printf("key: %s, value: %s\n", key, m[key])
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
遍历map
key: key1, value: value1
key: key2, value: value2
key: key1, value: value1
key: key2, value: value2
千万不要对迭代参数取地址!!!!
在内存里,迭代参数都是放在一个共同的地方的,循环一旦开始就确定下来了,一旦获取地址,那么拿到的永远都是这个地址。(这个问题在1.22版本中被修复)
package main
import "fmt"
type User struct {
Name string
}
func ForLoopBug() {
users := []User{
{
Name: "Tom",
},
{
Name: "Jerry",
},
}
m := make(map[string]*User, 2)
for _, user := range users {
m[user.Name] = &user
}
for key, value := range m {
fmt.Printf("key: %s, value: %v\n", key, value)
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

遍历channel:(待定补充)
3、switch
switch不需要break
package main
import "fmt"
func Switch(status int) string {
switch status {
case 0: //必须为常量表达式
return "init..."
case 1:
return "run..."
default: //default分支可以有可以没有,但是switch值必须是可比较的。
return "error..."
}
}
func main() {
fmt.Println(Switch(1))
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
run...
switch也可以没有value。
package main
import "fmt"
func Switch(age int) {
switch {
case age >=18 : //这种情况,case后面跟bool表达式
return "成年人"
case age < 6 :
return "小孩子"
}
}
func main() {
fmt.Println(Switch(1))
}
2
3
4
5
6
7
8
9
10
11
12
13
小孩子
4、select (并发相关)
# (9)Go内置类型
# 1)数组
语法:[cap]type
1.初始化要指定长度(或者叫做容量)
2.可以直接初始化
3.arr[i]的形式访问元素
4.len和cap操作用于获取数组长度
5.使用for range 来循环
package main
import "fmt"
func main() {
arr := [3]int{9, 8, 7}
fmt.Printf("arr: %v, len=%d, cap=%d ", arr, len(arr), cap(arr))
}
2
3
4
5
6
arr: [9 8 7], len=3, cap=3
# 2)切片
语法:[]type
1.直接初始化/make初始化:make([]type,length,capacity),且初始化时需要预估容量,否则容量太小超出需要扩容,容量太大会浪费内存;
2.arr[i]的形式访问元素
3.append追加元素,len获取元素数量(已经放了多少个),cap获取切片容量(最多能放的大小);若没有输入capacity,length和capacity值相同
4.推荐写法:s1:=make([]type,0,capacity)
5.使用for range 来循环
package main
import "fmt"
func main() {
slice := []int{9, 8, 7}
fmt.Printf("slice: %v, len=%d, cap=%d ", slice, len(slice), cap(slice))
}
2
3
4
5
6
slice: [9 8 7], len=3, cap=3
总结:遇事不决,选择切片!

如何理解切片?
- 最直观的对比:ArrayList,基于数组的List的实现,切片的底层也是数组。
和ArrayList的区别:
(I)切片操作时有限的,不支持随机增删(即没有add、delete方法,需要自己写代码)
(II)只有append操作
(III)切片支持子切片操作,和原本的切片共享底层数组

什么是子切片?
通过[start:end]形式获取到的都是子切片。(数组和切片都可以,注意左闭右开)
1、arr[start:end],获得[start,end)之间的元素。
2、arr[:end],获得[0,end)之间的元素。
3、arr[start:],获得[start,len(arr))之间的元素。
4、容量即start开始往后,包括原切片底层数组的元素个数。
package main
import "fmt"
func main() {
s1 := []int{2, 4, 6, 8, 10}
s2 := s1[1:3]
fmt.Printf("s2: %v, len=%d, cap=%d ", s2, len(s2), cap(s2))
s4 := s1[:3]
fmt.Printf("s4: %v, len=%d, cap=%d ", s4, len(s4), cap(s4))
s3 := s1[2:]
fmt.Printf("s3: %v, len=%d, cap=%d ", s3, len(s3), cap(s3))
}
2
3
4
5
6
7
8
9
10
11
s2: [4 6], len=2, cap=4
s4: [2 4 6], len=3, cap=5
s3: [6 8 10], len=3, cap=3
切片扩容
重新分配一段连续内存,而后把原本的数据拷贝回去。
package main
import "fmt"
func main() {
s1 := []int{1, 2, 3, 4}
fmt.Printf("s1: %v, len=%d, cap=%d\n", s1, len(s1), cap(s1))
s1 = append(s1, 5)
fmt.Printf("s1: %v, len=%d, cap=%d\n", s1, len(s1), cap(s1))
}
2
3
4
5
6
7
8
9
s1: [1 2 3 4], len=4, cap=4
s1: [1 2 3 4 5], len=5, cap=8

# 3)map
语法:map[type]type
1.直接初始化/make初始化:make(map[type]type,length),需要记得预估容量
2.使用中括号赋值
3.读取元素:有两个返回值,第一个是值,第二个是着元素是否存在。如果只用一个返回值,那么就是对应的元素;如果元素不存在,那么就是对应类型的零值
4.len获取长度,delete删除方法
5.使用for遍历,第一个是key,第二个是value
注意:map的遍历是随机的,也就是说遍历两遍输出的结果可能会不一样
package main
import "fmt"
func main() {
m1 := map[string]string{
"key1": "value1",
}
fmt.Printf("map: %v, len=%d", m1, len(m1))
}
2
3
4
5
6
7
8
map: map[key1:value1], len=1
# 3、内存共享问题
核心:共享数组
子切片和切片究竟会不会互相影响,就抓住一点:它们是不是还共享数组?,即若他们结构没有变化,那肯定是共享的;但是结构发生变化(扩容),可能不是共享了
package main
import "fmt"
func main() {
s1 := []int{1, 2, 3, 4}
s2 := s1[1:]
fmt.Printf("s1: %v, len=%d, cap=%d\n", s1, len(s1), cap(s1))
fmt.Printf("s2: %v, len=%d, cap=%d\n", s2, len(s2), cap(s2))
s2[0] = 99 //s2 改了,s1也跟着改
fmt.Printf("s1: %v, len=%d, cap=%d\n", s1, len(s1), cap(s1))
fmt.Printf("s2: %v, len=%d, cap=%d\n", s2, len(s2), cap(s2))
s2 = append(s2, 100)//s2 扩容,s2容量由3变成了6
fmt.Printf("s1: %v, len=%d, cap=%d\n", s1, len(s1), cap(s1))
fmt.Printf("s2: %v, len=%d, cap=%d\n", s2, len(s2), cap(s2))
s2[1] = 1999//s2 改了,s1不跟着改
fmt.Printf("s1: %v, len=%d, cap=%d\n", s1, len(s1), cap(s1))
fmt.Printf("s2: %v, len=%d, cap=%d\n", s2, len(s2), cap(s2))
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
s1: [1 2 3 4], len=4, cap=4
s2: [2 3 4], len=3, cap=3
s1: [1 99 3 4], len=4, cap=4
s2: [99 3 4], len=3, cap=3
s1: [1 99 3 4], len=4, cap=4
s2: [99 3 4 100], len=4, cap=6
s1: [1 99 3 4], len=4, cap=4
s2: [99 1999 4 100], len=4, cap=6
# 4、comparable概念
在switch里面,值必须是可比较的
在map里面,key也必须是可比较的
所以可比较的(comparable)在Go里面指:在编译/运行的时候,能够判断出元素是不是相等的
基本类型和string都是可比较的
如果元素是可比较的,那么该数组是可比较的
详细说明文档:https://go.dev/ref/spec#Comparaison_operators (opens new window)