爱摸鱼的Demon
首页
前端知识
后端技术
工程实践
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 友情链接
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

爱摸鱼的Demon

我的地盘,欢迎光临。
首页
前端知识
后端技术
工程实践
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 友情链接
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • C#

  • Golang

    • Go

      • GO环境安装
      • Hello World GO版
      • GO变量、数据类型与方法
      • GO的基础语法
        • 1、递归(Recursive)
        • 2、函数编程入门
          • (1)方法作为变量
          • (2)局部变量
          • (3)方法作为返回值
          • (4)立即执行函数
          • (5)闭包(closure)
          • (6)不定参数
          • (7)defer
          • (8)控制结构
          • (9)Go内置类型
          • 1)数组
          • 2)切片
          • 3)map
        • 3、内存共享问题
        • 4、comparable概念
      • 并发安全与锁
      • channel(通道、管道)
      • 反射
      • 文件目录
      • Golang中的接口(Interface)
    • Gin

    • GORM

  • 后端技术
  • Golang
  • Go
DemonW-X
2025-09-29
目录

GO的基础语法

# 1、递归(Recursive)

一个方法内部可以调用他自己。

package main
import "fmt"

func Recursive(n int){
  if(n > 10){
    return
  }
  else
    Recursive(n + 1)
}
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)
}
1
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")
}
1
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()
}
1
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()
}
1
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())
}
1
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) //此处不可以加...

}
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

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()
}

1
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
}

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
}
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

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
}
1
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 "太近啦"
  }
}

1
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)
	}
}
1
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++
	}
}
1
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)
	}
}
1
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++
	}
}
1
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++
	}
}
1
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()
}
1
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()
}
1
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])
	}
}
1
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])
	}
}

1
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)
	}
}
1
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))
}
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))
}
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))
}
1
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))
}
1
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))
}
1
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))
}
1
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))
}
1
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))
}
1
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)

编辑 (opens new window)
GO变量、数据类型与方法
并发安全与锁

← GO变量、数据类型与方法 并发安全与锁→

最近更新
01
Gorm之事务
11-13
02
Gorm之关联进阶版
11-13
03
Gorm之根据外键关联表
11-13
更多文章>
Theme by Vdoing | Copyright © 2022-2025 爱摸鱼的Demon | 桂ICP备2024034950号 | 桂公网安备45142202000030
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式