二丫讲梵 二丫讲梵
首页
  • 最佳实践
  • 迎刃而解
  • Nginx
  • Php
  • Zabbix
  • AWS
  • Prometheus
  • Grafana
  • CentOS
  • Systemd
  • Docker
  • Rancher
  • Ansible
  • Ldap
  • Gitlab
  • GitHub
  • Etcd
  • Consul
  • RabbitMQ
  • Kafka
  • MySql
  • MongoDB
  • OpenVPN
  • KVM
  • VMware
  • Other
  • ELK
  • K8S
  • LLM
  • Nexus
  • Jenkins
  • 随写编年
  • 家人物语
  • 追忆青春
  • 父亲的朋友圈
  • 电影音乐
  • 效率工具
  • 博客相关
  • Shell
  • 前端实践
  • Vue学习笔记
  • Golang学习笔记
  • Golang编程技巧
  • 学习周刊
  • Obsidian插件周刊
关于
友链
  • 本站索引

    • 分类
    • 标签
    • 归档
  • 本站页面

    • 导航
    • 打赏
  • 我的工具

    • 备忘录清单 (opens new window)
    • json2go (opens new window)
    • gopher (opens new window)
    • 微信MD编辑 (opens new window)
    • 国内镜像 (opens new window)
    • 出口IP查询 (opens new window)
    • 代码高亮工具 (opens new window)
  • 外站页面

    • 开往 (opens new window)
    • ldapdoc (opens new window)
    • HowToStartOpenSource (opens new window)
    • vdoing-template (opens new window)
GitHub (opens new window)

二丫讲梵

行者常至,为者常成
首页
  • 最佳实践
  • 迎刃而解
  • Nginx
  • Php
  • Zabbix
  • AWS
  • Prometheus
  • Grafana
  • CentOS
  • Systemd
  • Docker
  • Rancher
  • Ansible
  • Ldap
  • Gitlab
  • GitHub
  • Etcd
  • Consul
  • RabbitMQ
  • Kafka
  • MySql
  • MongoDB
  • OpenVPN
  • KVM
  • VMware
  • Other
  • ELK
  • K8S
  • LLM
  • Nexus
  • Jenkins
  • 随写编年
  • 家人物语
  • 追忆青春
  • 父亲的朋友圈
  • 电影音乐
  • 效率工具
  • 博客相关
  • Shell
  • 前端实践
  • Vue学习笔记
  • Golang学习笔记
  • Golang编程技巧
  • 学习周刊
  • Obsidian插件周刊
关于
友链
  • 本站索引

    • 分类
    • 标签
    • 归档
  • 本站页面

    • 导航
    • 打赏
  • 我的工具

    • 备忘录清单 (opens new window)
    • json2go (opens new window)
    • gopher (opens new window)
    • 微信MD编辑 (opens new window)
    • 国内镜像 (opens new window)
    • 出口IP查询 (opens new window)
    • 代码高亮工具 (opens new window)
  • 外站页面

    • 开往 (opens new window)
    • ldapdoc (opens new window)
    • HowToStartOpenSource (opens new window)
    • vdoing-template (opens new window)
GitHub (opens new window)
  • Shell编程

  • Go编程笔记

  • 前端编程笔记

  • Go学习笔记

    • 基础部分

      • 基础知识
      • 数据类型
      • fmt的格式化输入输出
      • 运算符
      • 流程控制
      • 认识函数
        • 1,普通参数列表。
        • 2,不定参数类型。
        • 3,不定参数的传递。
        • 1,单个返回值。
        • 2,多个返回值。
        • 1,认识递归函数。
        • 2,计算数字累加和。
        • 1,认识函数类型。
        • 2,多态。
        • 1,认识匿名函数与闭包。
        • 2,闭包捕获外部变量的特点。
        • 1,了解 defer。
        • 2,多个 defer 的执行顺序。
        • 3,defer 与匿名函数结合使用。
        • 1,局部变量。
        • 2,全局变量。
        • 3,不同作用域的同名变量。
      • 包的操作
      • 项目工程-在同级目录
      • 指针(pointer)
      • 数组(array)
      • 切片(slice)
      • 字典(map)
      • 结构体(struct)
      • 匿名组合
      • 方法
      • 接口
      • error接口
      • panic
      • recover
      • 字符串操作
      • 字符串转换
      • 正则表达式
      • JSON处理
      • 文件的操作
      • goroutine
      • channel
      • select
      • sync-lock锁
      • 网络概述
      • Socket编程
      • 发文件与聊天室
      • http编程
      • 爬虫实战
      • 单元测试了解
    • web框架

    • orm框架

  • Vue-21年学习笔记

  • Vue-22年重学笔记

  • 编程世界
  • Go学习笔记
  • 基础部分
二丫讲梵
2021-07-07
目录

认识函数

文章发布较早,内容可能过时,阅读注意甄别。

函数构成代码执行的逻辑结构。在 Go 语言中,函数的基本组成为:关键字 func,函数名,参数列表,返回值,函数体和返回语句。

Go 语言函数定义格式如下:

func FuncName(/*参数列表*/) (01 type1, 02 type2/*返回类型*/) {
	//函数体
	return v1, v2 //返回多个值
}
1
2
3
4

函数定义说明:

  • func:函数有关键字 func 声明。
  • FuncName:函数名称,根据约定,函数名首字母小写即为 private,大写即为 public。
  • 参数列表:函数可以有 0 个或多个参数列表,参数格式为:变量名 类型,如果有多个参数,通过逗号分隔,不支持默认参数
  • 返回类型:
  • 上面返回值声明了两个变量名 01 和 02(命令返回参数),这个不是必须,可以只有类型没有变量名。
  • 如果只有一个返回值且不声明返回值变量,那么你可以省略,包括返回的括号。
  • 如果没有返回值,那么就直接省略最后的返回信息。
  • 如果有返回值,那么必须在函数的内部添加 return 语句。

# 1,无参数无返回值。

package main

import "fmt"

//无参无返回值函数的的定义
func MyFunc() {
	a := 666
	fmt.Println("a =", a)
}

//被调用函数放在上边或者下边都是一样的,程序从main函数进入,然后调用MyFunc函数结束。

func main() {
	//无参无返回值函数的调用:函数名()
	MyFunc()
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 2,有参无返回值。

# 1,普通参数列表。

package main

import "fmt"

/*
有参无返回值函数的定义,普通参数列表
定义函数时,在函数名后面()定义的参数叫形参
参数传递,只能由实参传递给形参,不能反过来,这是一个单项传递
*/

func MyFunc01(a int) {
	fmt.Println("a =", a)
}

func MyFunc02(a int, b int) {
	fmt.Printf("a = %d, b = %d\n", a, b)
}

//或者写成
func MyFunc03(a, b int) {
	fmt.Printf("a = %d, b = %d\n", a, b)
}

func MyFunc04(a int, b string, c float64) {
}

func MyFunc05(a, b string, c float64, d, e int) {

}

//推荐如下这种写法,比较清晰好认。
func MyFunc06(a string, b string, c float64, d int, e int) {

}

func main() {
	//有参无返回值函数调用方式: 函数名(所需参数)
	//点用函数传递的参数叫实参
	MyFunc01(666)
	MyFunc02(666, 777)
}
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

# 2,不定参数类型。

package main

import "fmt"

//之前定义的,都是普通参数,或者叫做固定参数,示例如下:
func MyFunc01(a int, b int) {

}

//接下来是不定参数类型
//...int 类似这样的类型,就是不定参数类型
//注意:不定参数,一定(只能)是作为形参中最后一个参数存在
func MyFunc02(args ...int) { //传递的参数可以是0个或多个
	fmt.Printf("len(args) = %d\n", len(args)) //表示获取用户传递参数的个数

	//如何打印用户输入的参数呢,可以通过for循环来执行
	// for i := 0; i < len(args); i++ {
	// 	fmt.Printf("args[%d] = %d\n", i, args[i])
	// }
	//或者使用range进行打印,代码如下
	for i, date := range args { //表示遍历参数列表
		fmt.Printf("args[%d] = %d\n", i, date)
	}
}

// func MyFunc03(a int,b int, test ...int){
// 	//这个是正确的演示
// }

// func MyFunc04(a int, test ... int, b int){
// 	//这个是错误的演示,无论是放中间,还是放在开头,都是不允许的
// }

func main() {
	MyFunc02()
	MyFunc02(1)
	MyFunc02(1, 2, 3)
}
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

# 3,不定参数的传递。

package main

import "fmt"

/*
1,先通过主函数传递了四个值给test函数中的不定参数args。
*/
func myfunc(tmp ...int) {
	for _, date := range tmp {
		fmt.Printf("date = %d\n", date)
	}
}

func test(args ...int) {
	//如果把全部元素传递给myfunc,可用如下方式
	//myfunc(args...)

	//只想把某两个元素传递下去,如何写
	myfunc(args[2:]...) //表示把args[0]~args[2](不包括args[2])对应的元素传递过去
	myfunc(args[:2]...) //表示从args[2](包括args[2]本身)开始,后边的都传递过去
}

func main() {
	test(1, 2, 3, 4)
}
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

# 3,无参有返回值。

# 1,单个返回值。

package main

import "fmt"

//无参有返回值,一个返回值的示例
//有返回值的函数需要通过return关键字中断函数,通过return返回
func myfunc() int {
	return 666
}

func MyFunc01() (test int) {
	return 777
}

//给返回值定义一个名为test的变量,再通过给变量赋值,然后return出去,是go推荐写法,这种写法比较常用
func MyFunc02() (test int) {
	test = 888
	return
}

func main() {
	//标准写法
	var a int
	a = myfunc()
	fmt.Println("a =", a)
	//也可以用自动推到类型的写法
	b := myfunc()
	fmt.Println("b =", b)

	c := MyFunc01()
	fmt.Println("c =", c)

	d := MyFunc02()
	fmt.Println("d =", d)
}
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

# 2,多个返回值。

package main

import "fmt"

func MyFunc01() (int, int, int) {
	return 1, 2, 3

}

//常用写法
func MyFunc02() (a, b, c int) {
	a, b, c = 4, 5, 6
	return
}

func main() {
	a, b, c := MyFunc02()
	fmt.Printf("a = %d,b = %d, c = %d\n", a, b, c)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 4,有参有返回值。

package main

import "fmt"

//函数定义
func MaxAndMin(a, b int) (max, min int) {
	if a > b {
		max = a
		min = b
	} else {
		max = b
		min = a
	}
	return //有返回值的函数,必须通过return关键字返回
}

func main() {
	//函数调用。
	max, min := MaxAndMin(10, 20)
	fmt.Printf("max = %d, min = %d\n", max, min)

	//通过匿名变量丢弃某个值
	a, _ := MaxAndMin(5, 2)
	fmt.Println("a =", 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

# 5,普通函数调用流程。

在函数当中,程序执行的先后顺序是,先调用,后返回。

package main

import "fmt"

func funcc(c int) {
	fmt.Println("c =", c)
}

func funcb(b int) {
	funcc(b - 1)
	fmt.Println("b =", b)
}

func funca(a int) {
	funcb(a - 1)
	fmt.Println("a =", a)
}

func main() {
	funca(3)
	fmt.Println("main")
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

可能函数不太容易看,那么可以通过画图来一一解析:

m_256a8666dcd097572d1a2bf54d0d67fb_r

# 6,递归函数调用流程。

# 1,认识递归函数。

递归函数是指函数可以直接或者间接调用自身。

递归函数通常有相同的结构:一个跳出条件和一个递归体。所谓跳出条件就是根据传入的参数判断是否要终止递归,而递归体则是函数自身所做的一些处理。

package main

import "fmt"

//递归,就是在运行过程中调用自己
func test(a int) {
	//必须要设置退出条件,否则将会进入无限循环
	if a == 1 {
		fmt.Println("a =", a)
		return //终止函数调用
	}
	//函数调用自身
	test(a - 1)
	fmt.Println("a =", a)
}

func main() {
	test(3)
	fmt.Println("main")
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

通过画图将运行流程解析一下。

m_ba101555cb25011557392f832a169670_r

使用文字详细表述一下执行流程,注意文字表述比画图的步骤稍有精简,其实是把有些步骤合起来了。

  • 1,通过 main 入口函数进入程序。
  • 2,调用函数 test,并赋值变量 a 为 3.
  • 3,程序来到函数 test,此时 a=3,不符合判断条件,所以往下走,调用函数 test,并赋值变量 a 为(a-1),其实就是 3-1
  • 4,程序再次来到函数 test,此时 a=2,不符合判断条件,所以往下走,调用函数 test,并赋值变量 a 为(a-1),其实就是 2-1
  • 5,程序再次来到函数 test,此时 a=1,符合判断条件,所以进入到判断中,打印此时 a 的值,为 1,然后 return 跳出。
  • 6,跳出后,函数回到第 4 步,然后打印此时 a 的值,为 2,完成本次运算,跳出。
  • 7,跳出后,函数回到第 3 步,然后打印此时 a 的值,为 3,完成本次运算,跳出。
  • 8,跳出后,函数回到第 2 步,然后打印一个字符串 main。

其中针对第 6,7 两步,再简单分析如下:

前边的 5 步,都还比较容易理解,但是来到第 6 步,自己就有点晕了,不知道为什么还会返回去进行打印,后来又翻上去,看了看上边普通函数调用的例子,就有一点点明白了,其实到第 6 步(也就是第五步的完成),相当于程序完成了第 4 步的函数调用,结合先调用,后返回的理论,那么此时刚好调用结束了,该进行打印了。同理,到第 7 步(也就是第 6 步的完成),相当于程序完成了第 3 步的函数调用,完成之后,打印彼时的值,然后同理往后推,直到程序完成最终的逻辑规划。

# 2,计算数字累加和。

昨天的时候,曾经使用过 for 循环来实现,现在可以不回头看文档,自己动手敲一遍 for 循环的累加和。

package main

import "fmt"

func main() {
	sum := 0
	for i := 1; i <= 100; i++ {
		sum = sum + i
	}
	fmt.Println("1加到100的和是:", sum)
}
1
2
3
4
5
6
7
8
9
10
11

现在来理解,大概就不是十分困难了,这是计算的从 1 加到 100 的和,还可以写一下从 100 加到 1 的和,如下:

package main

import "fmt"

func main() {
	sum := 0
	for i := 100; i >= 1; i-- {
		sum = sum + i
	}
	fmt.Println("100加到1的和是:", sum)
}
1
2
3
4
5
6
7
8
9
10
11

那么,现在可以应该可以尝试使用递归函数来实现一下了,还是从 1 加到 100 开始。

package main

import "fmt"

func test(i int) int {
	if i == 100 {
		return 100
	}
	return i + test(i+1)
}

func main() {
	var sum int
	sum = test(1)
	fmt.Println("1加到100的和是:", sum)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

如果不借助于画图,先来读一读函数所执行的流程。

1,通过 main 入口函数进入程序。 2,定义一个变量 sum,让他等于函数 test(),且传递参数使 i=1。 3,来到函数 test 中,此时 i=1,首然后进入 if 判断,显然不等于 100,接着往下,得到 i+test(i+1),实际上就是 1+test(1+1),此时 1 保留,后边的 test(1+1)再次进入函数。 4,再次来到函数 test 中,此时 i=2,首然后进入 if 判断,显然不等于 100,接着往下,得到 i+test(i+1),实际上就是 2+test(2+1),此时 2 保留,后边的 test(2+1)再次进入函数。 5,再次来到函数 test 中,此时 i=3,首然后进入 if 判断,显然不等于 100,接着往下,得到 i+test(i+1),实际上就是 3+test(3+1),此时 3 保留,后边的 test(3+1)再次进入函数。 6,再次来到函数 test 中,此时 i=4,首然后进入 if 判断,显然不等于 100,接着往下,得到 i+test(i+1),实际上就是 4+test(4+1),此时 4 保留,后边的 test(4+1)再次进入函数。 ...... 如此往复循环 99 次,直到最后一次。 7,再次来到函数 test 中,此时 i=100,首然后进入 if 判断,等于 100,然后 return 一个 100,而前边那么多次,已经累积为 1+2+3...+99,再加上这 100,就等于最终结果了。

接下来再尝试一下从 100 加到 1 的累加和。

package main

import "fmt"

func test01(i int) int {
	if i == 1 {
		return 1
	}
	return i + test01(i-1)
}

func main() {
	var sum int
	sum = test01(100)
	fmt.Println("100加到1的和是:", sum)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

这个相加的过程以及调用,与上边基本上是一致的。

# 7,函数类型。

# 1,认识函数类型。

在 go 语言中,函数也是一种数据类型,可以用过 type 来定义它,它的类型就是所拥有的相同的参数,相同的返回值的一种类型。

package main

import "fmt"

//两数相加
func Add(a, b int) int {
	return a + b
}

//两数相减
func Minus(a, b int) int {
	return a - b
}

//函数也是一种数据类型,通过type可以给函数起一个名字,然后通过这个名字来对函数进行调用。
//FuncType是一个函数类型
type FuncType func(int, int) int //没有函数名字,没有{}

func main() {
	//传统方式调用函数
	var result int
	result = Add(8, 5)
	fmt.Println("result =", result)

	//现在通过刚刚定义的函数类型名称来进行调用
	var ftest FuncType     //定义一个变量ftest,其类型是刚刚定义的FuncType
	ftest = Add            //然后可以将函数名赋给变量ftest,从而让ftest具有对应名称的函数能力
	result = ftest(10, 20) //等价于 restult = Add(10,20)
	fmt.Println("result1 =", result)

	//同样,也可以将另外一个函数名赋给一个变量。
	ftest = Minus
	result = ftest(1, 1) //等价于 restult = Minus(1,1)
	fmt.Println("result2 =", result)
}
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

如上是一种传统定义以及调用的例子,还可以使用下边的方式进行定义与调用。

package main

import "fmt"

type FuncType func(int, int) int //声明一个函数类型

//函数中有一个参数类型为函数类型:f FuncType
func Calc(a, b int, f FuncType) (result int) {
	result = f(a, b)
	return
}

func Add(a, b int) int {
	return a + b
}

func main() {
	//函数调用,第三个参数为函数名字,此函数的参数,返回值必须和FuncType类型一致
	a := Calc(3, 3, Add)
	fmt.Println("a=", a)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

那么这种函数类型,究竟有什么用呢,直接调用难道不是更简便么。接下来,就能看到它的作用了。

# 2,多态。

函数类型可以应用于多态当中,在进入正式的示例之前,先来看一个普通的加法计算的代码。

package main

import "fmt"

//两数相加
func Add(a, b int) int {
	return a + b
}
//主程序函数调用加法函数
func Calc(a, b int) (result int) {
	fmt.Println("Calc")
	result = Add(a, b)
	return
}

func main() {
	a := Calc(3, 3)
	fmt.Println("a=", a)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

接着再来看另外一种写法:

package main

import "fmt"

type FuncType func(int, int) int

//两数相加
func Add(a, b int) int {
	return a + b
}

//两数相减
func Minus(a, b int) int {
	return a - b
}

//两数相乘
func Mul(a, b int) int {
	return a * b
}

/*
计算器,可以进行四则运算
回调函数,函数有一个参数是函数类型,这个函数就是回调函数。在下边的例子中,参数ftest的类型就是上边定义的函数类型(FuncType)
多态,多种形态,调用同一个接口,不同的表现,可以实现不同的结果。
*/

func Calc(a, b int, ftest FuncType) (result int) {
	fmt.Println("Calc")
	result = ftest(a, b)
	return
}

func main() {
	a := Calc(3, 3, Mul)
	fmt.Println("a=", 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
37

简单说明。

一开始也不是很能理解,但是练习了几个例子,稍微发现了其中的一些奥妙。从 main 入口处定义开始理解,Calc(3, 3, Mul)套到函数上,注意后边的 Mul,等同于 ftest 的位置,那么替换一下之后,就会发现,与上边刚刚写的那个加法的例子,其实基本上一样了。

# 8,匿名函数与闭包。

所谓闭包就是一个函数“捕获”了和它在同一作用域的其他常量和变量。这就意味着当闭包被调用的时候,不管在程序什么地方调用,闭包能够使用这些常量或者变量。它不关心这些捕获了的变量和常量是否已经超出了作用域,所以只有闭包还在使用它,这些变量就还会存在。

在 Go 语言里,所有的匿名函数(Go 语言规范中称之为函数字面量)都是闭包。匿名函数是指不需要定义函数名的一种函数实现方式。

# 1,认识匿名函数与闭包。

package main

import "fmt"

func main() {
	a := 10
	str := "mike"

	//匿名函数,没有函数名字,以下只是函数的定义
	f1 := func() { //自动推导类型,将匿名函数运算赋值给f1
		fmt.Printf("a =%d, str =%s\n", a, str)
	}
	f1() //使用这种方式来进行调用。

	//给一个匿名函数类型起个别名,这种写法不常用。
	type FuncType func() //函数没有参数,没有返回值
	var f2 FuncType
	f2 = f1
	f2()

	//定义匿名函数的同时并调用
	func() {
		fmt.Printf("a =%d, str =%s\n", a, str)
	}() //后面的()表示调用这个匿名函数

	//带参数的匿名函数
	f3 := func(i, j int) {
		fmt.Printf("i =%d, j =%d\n", i, j)
	}
	f3(3, 5)
	//另外一种写法
	func(i, j int) {
		fmt.Printf("i =%d, j =%d\n", i, j)
	}(5, 3)

	//匿名函数,有参有返回值
	x, y := func(i, j int) (max, min int) {
		if i > j {
			max = i
			min = j
		} else {
			max = j
			min = i
		}
		return
	}(6, 8)
	fmt.Printf("x = %d, y = %d\n", x, y)
}
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

# 2,闭包捕获外部变量的特点。

package main

import "fmt"

func main() {
	a := 10
	str := "mike"
	func() {
		//闭包以引用的方式捕获外部的变量
		a = 666
		str = "go"
		fmt.Printf("a = %d, str = %s\n", a, str)
	}()

	fmt.Printf("a = %d, str = %s\n", a, str)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

结果输出:

a = 666, str = go
a = 666, str = go
1
2

相当于在匿名函数中,更改了变量的值,那么到匿名函数外部之后,值也随之更改。

为了验证这一特点,现在可以做一个对比。

先通过一个普通的函数类型看看情况:

package main

import "fmt"

func test01() int {
	var a int //当一个变量没有初始化时,默认是0
	a++
	return a * a	//函数调用完毕,a自动释放。
}
func main() {
	fmt.Println(test01())
	fmt.Println(test01())
	fmt.Println(test01())
	fmt.Println(test01())
	fmt.Println(test01())
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

输出结果为:

$ go run 15_闭包的特点.go

1
1
1
1
1
1
2
3
4
5
6
7

注意上边那句话,调用完成,变量的值自动释放,才会得到如上结果。

再来看另外一个例子:

package main

import "fmt"
//函数的返回值是一个匿名函数,返回一个函数类型
func test02() func() int {
	var a int //当一个变量没有初始化时,默认是0
	return func() int {
		a++
		return a * a
	}
}
func main() {
	//上边的返回值为一个匿名函数,返回一个函数类型,因此这里通过一个变量f来调用返回的匿名函数(闭包函数)
	//此时,它不关心这些捕获了的变量或常量是否已经超出了作用域,只要闭包还在使用它,这些变量就会一直存在。
	f := test02()
	fmt.Println(f())
	fmt.Println(f())
	fmt.Println(f())
	fmt.Println(f())
	fmt.Println(f())
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

输出结果:

$ go run 15_闭包的特点.go

1
4
9
16
25
1
2
3
4
5
6
7

同时也要注意上边的话,每一次调用闭包,那么上次的值都会存下来,正如结果所示的,每次调用,a 的初始值都是上次经过闭包之后所得到的新值。

# 9,延迟调用之 defer。

# 1,了解 defer。

关键字 defer 用于延迟一个函数或者方法(或者当前所创建的匿名函数)的执行,延迟到函数的最后。注意,defer语句只能出现在函数或者方法的内部。

package main

import "fmt"

func main() {
	//defer延迟调用,表示在main函数结束之前调用,也就是放在最后执行
	defer fmt.Println("aaaaaaa")
	fmt.Println("bbbbbbb")
}
1
2
3
4
5
6
7
8
9

defer 语句经常被用于处理那些成对的操作,比如打开、关闭,连接、断开连接,加锁、释放锁,,等,通过 defer,不论函数逻辑多复杂,都能保证在任何执行路径下,资源被释放,释放资源的 defer 语句,应该直接跟在请求资源的语句后。

# 2,多个 defer 的执行顺序。

如果一个函数中有多个 defer 语句,他们会以先进后出(也就是先定义的,后执行)的顺序执行,哪怕函数或某个延迟调用会发生错误,这些调用依旧会被执行。

package main

import "fmt"

func test(x int) {
	fmt.Println(100 / x) //x为0时,产生异常
}

func main() {

	defer fmt.Println("aaaaaaa")
	defer fmt.Println("bbbbbbb")
	defer test(0)
	defer fmt.Println("ccccccc")
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

结果如下:

$ go run 17_多个defer执行顺序.go

ccccccc
bbbbbbb
aaaaaaa
panic: runtime error: integer divide by zero

goroutine 1 [running]:
main.test(0x0)
        C:/Users/Administrator/Desktop/gocode/src/day2/17_多个defer执行顺序.go:6 +0xa9
main.main()
        C:/Users/Administrator/Desktop/gocode/src/day2/17_多个defer执行顺序.go:15 +0x15e
exit status 2
1
2
3
4
5
6
7
8
9
10
11
12
13

# 3,defer 与匿名函数结合使用。

先看一个没有参数的例子。

package main

import "fmt"

func main() {
	a := 10
	b := 20
	defer func() {
		fmt.Printf("a = %d, b = %d\n", a, b)
	}()

	a = 100
	b = 200
	fmt.Printf("外部:a = %d, b = %d\n", a, b)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

结果输出:

$ go run 18_defer和匿名函数结合使用.go

外部:a = 100, b = 200
a = 100, b = 200
1
2
3
4

接着给匿名函数定义两个参数,看看结果。

package main

import "fmt"

func main() {
	a, b := 10, 20
	defer func(a, b int) {
		fmt.Printf("a = %d, b = %d\n", a, b)
	}(a, b)  //函数执行时,到达defer语句处,虽然暂不执行,但是参数已经传递,结果到最后输出。

	a = 100
	b = 200
	fmt.Printf("外部:a = %d, b = %d\n", a, b)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

结果输出:

$ go run 18_defer和匿名函数结合使用.go

外部:a = 100, b = 200
a = 10, b = 20
1
2
3
4

# 10,获取命令行参数。

package main

import (
	"fmt"
	"os"
)

func main() {
	list := os.Args
	n := len(list)
	fmt.Println("参数个数为:", n)
}
1
2
3
4
5
6
7
8
9
10
11
12

执行一下看看:

$ go run 19_获取命令行参数.go 1 2 3
参数个数为: 4
1
2

可以分别打印一下这些参数:

package main

import (
	"fmt"
	"os"
)

func main() {
	list := os.Args
	n := len(list)
	fmt.Println("参数个数为:", n)

	for i := 0; i < n; i++ {
		fmt.Printf("list[%d] = %s\n", i, list[i])
	}
	//迭代打印
	for i, data := range list {
		fmt.Printf("list[%d] = %s\n", i, data)
	}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

运行结果:

$ go build 19_获取命令行参数.go

$ ./19_获取命令行参数.exe 1 2 3

参数个数为: 4
list[0] = C:\Users\Administrator\Desktop\gocode\src\day2\19_获取命令行参数.exe
list[1] = 1
list[2] = 2
list[3] = 3
list[0] = C:\Users\Administrator\Desktop\gocode\src\day2\19_获取命令行参数.exe
list[1] = 1
list[2] = 2
list[3] = 3
1
2
3
4
5
6
7
8
9
10
11
12
13

# 11,作用域。

# 1,局部变量。

package main

import "fmt"

func test() {
	a := 10
	fmt.Println("a =", a)
}

/*
作用域:就是变量产生作用的范围。
定义在{}里面的变量就是局部变量,同时也只在{}里面有效。
执行到定义变量那句话,才开始给变量分配空间,离开作用域之后自动释放
*/

func main() {
	fmt.Println("a =", a)

	{
		i := 10
		fmt.Println("i = ", i)
	}
	fmt.Println("i = ", i)
	if b := 3; b == 3 {
		fmt.Println("b = ", b)
	}
	fmt.Println("b = ", b)
}
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

# 2,全局变量。

package main

import "fmt"

//全局变量是指定义在函数外部的量
//全局变量在任何地方都能使用

var a int

func main() {
	a = 10
	fmt.Println("a =", a)
	test()
}

func test() {
	fmt.Println("test a =", a)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 3,不同作用域的同名变量。

package main

import "fmt"

var a byte //全局变量
//1,不同作用域,允许定义同名变量
//2,使用变量的原则,就近原则。

func main() {
	var a int //局部变量
	fmt.Printf("1: %T\n", a)

	{
		var a float64
		fmt.Printf("2: %T\n", a)
	}
	test()
}

func test() {
	fmt.Printf("3: %T\n", a)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 12,可变参数

//可变参数
func add(a...int) int {	//0个或多个可变参数

}

func add(a int, arg...int) int { //1个或多个可变参数

}

func add(a, b int, arg...int) int { //2个或多个可变参数

}
//注意:其中arg是一个slice,可通过arg[index]来获取对应参数,通过len(arg)来判断参数的个数
1
2
3
4
5
6
7
8
9
10
11
12
13

可变参数的应用:

package main

import "fmt"

// 函数可变参数

func f(a ...interface{}) {
	// a 是一个切片
	fmt.Printf("type:%T  value:%#v\n", a, a)
}
func main() {
	var s = []interface{}{1, 3, 5, 7, 9}
	f(s)    // 表示把s这个切片当成一个整体,作为函数参数a这个切片的第一个元素
	f(s...) //表示把s这个切片中的数据展开,来逐个作为函数参数a这个切片的元素
}

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

如上代码输出结果:

type:[]interface {}  value:[]interface {}{[]interface {}{1, 3, 5, 7, 9}}
type:[]interface {}  value:[]interface {}{1, 3, 5, 7, 9}
1
2
微信 支付宝
#go
上次更新: 2024/07/04, 22:40:37
流程控制
包的操作

← 流程控制 包的操作→

最近更新
01
学习周刊-总第216期-2025年第25周
06-20
02
睡着的人不关灯
06-12
03
学习周刊-总第215期-2025年第24周
06-12
更多文章>
Theme by Vdoing | Copyright © 2017-2025 | 点击查看十年之约 | 浙ICP备18057030号
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式