扫码订阅《 Go语言核心编程教程》或入驻星球,即可阅读文章!

GOLANG ROADMAP

阅读模式

  • 沉浸
  • 自动
  • 日常
首页
Go学习
  • Go学院

    • Go小课
    • Go视界
    • Go小考
    • Go实战
  • Go资源

    • 优质课程
    • 在线宝典
    • 资源下载
    • 帮找资源
训练营 🔥
  • Go体系课&实战训练营
  • 升值加薪陪跑训练营
Go求职
  • 求职刷题

    • 企业题库
    • 面试宝典
    • 求职面经
  • 求职服务

    • 内推互助
    • 求职助力
    • 内推公司
Go友会
  • 城市
  • 校园
推广返佣
  • 返佣排行
  • 返佣规则
  • 推广学院
实验区
  • Go周边
  • Go宝典

    • 推荐图书
    • 精品博文
  • Go开源

    • Go仓库
    • Go月刊
更多
  • 用户中心

    • 我的信息
    • 我的返佣
    • 我的消息
  • 玩转星球

    • 星球介绍
    • 星主权益
    • 吐槽专区
    • 成长记录
  • 合作交流

    • 商务合作
    • 讲师招募
    • 生态伙伴
author-avatar

GOLANG ROADMAP


首页
Go学习
  • Go学院

    • Go小课
    • Go视界
    • Go小考
    • Go实战
  • Go资源

    • 优质课程
    • 在线宝典
    • 资源下载
    • 帮找资源
训练营 🔥
  • Go体系课&实战训练营
  • 升值加薪陪跑训练营
Go求职
  • 求职刷题

    • 企业题库
    • 面试宝典
    • 求职面经
  • 求职服务

    • 内推互助
    • 求职助力
    • 内推公司
Go友会
  • 城市
  • 校园
推广返佣
  • 返佣排行
  • 返佣规则
  • 推广学院
实验区
  • Go周边
  • Go宝典

    • 推荐图书
    • 精品博文
  • Go开源

    • Go仓库
    • Go月刊
更多
  • 用户中心

    • 我的信息
    • 我的返佣
    • 我的消息
  • 玩转星球

    • 星球介绍
    • 星主权益
    • 吐槽专区
    • 成长记录
  • 合作交流

    • 商务合作
    • 讲师招募
    • 生态伙伴
  • 尚硅谷Go语言核心编程教程

    • 课程说明
    • 第1章 GOLANG 开山篇
    • 第2章 GOLANG 的概述
    • 第3章 GOLANG 变量
    • 第4章 运算符
    • 第5章 程序流程控制
    • 第6章 函数、包和错误处理
    • 第7章 数组与切片
    • 第8章 排序和查找
    • 第9章 map
    • 第10章 面向对象编程 ( 上 )
    • 第11章 面向对象编程 ( 下 )
    • 第12章 项目1:家庭收支记账软件项目
    • 第13章 项目2:客户信息关系系统
    • 第14章 文件操作
    • 第15章 单元测试
    • 第16章 goroutine和channel
    • 第17章 反射
    • 第18章 TCP 编程
    • 第19章 REDIS 的使用
    • 第20章 数据结构

扫码订阅《 Go语言核心编程教程》或入驻星球,即可阅读文章!

第6章 函数、包和错误处理


GOLANG ROADMAP

# 6.1 为什么需要函数

【点击观看视频】为什么需要函数

# 6.1.1 请大家完成这样一个需求:

输入两个数,再输入一个运算符(+,-,*,/),得到结果.。

# 6.1.2 使用传统的方法解决

package main
import (
	"fmt"
)

func main() {

	//请大家完成这样一个需求:
	//输入两个数,再输入一个运算符(+,-,*,/),得到结果.。
	//分析思路....
	var n1 float64 = 1.2
	var n2 float64 = 2.3
	var operator byte = '-'
    var res float64
	switch operator {
		case '+':
			res = n1 + n2
		case '-':
			res = n1 - n2
		case '*':
			res = n1 * n2
		case '/':
			res = n1 / n2
		default:
			fmt.Println("操作符号错误...")
	}
    fmt.Println("res=",res)
}	
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

分析一下上面代码问题

1 ) 上面的写法是可以完成功能, 但是代码冗余

2 ) 同时不利于代码维护

3 ) 函数可以解决这个问题

# 6.2 函数的基本概念

【点击观看视频】函数介绍和应用案例

为完成某一功能的程序指令(语句)的集合,称为函数。

在Go中,函数分为: 自定义函数、系统函数(查看Go编程手册)

# 6.3 函数的基本语法

//函数的基本语法
func 函数名(形参列表)(返回值列表){
    执行语句..
    return 返回值列表
}
1
2
3
4
5
  • 形参列表:表示函数的输入
  • 函数中的语句:表示为了实现某一功能代码块
  • 函数可以有返回值,也可以没有

# 6.4 快速入门案例

使用函数解决前面的计算问题。

package main
import (
	"fmt"
)

func cal(n1 float64, n2 float64, operator byte) float64 {

	var res float64
	switch operator {
		case '+':
			res = n1 + n2
		case '-':
			res = n1 - n2
		case '*':
			res = n1 * n2
		case '/':
			res = n1 / n2
		default:
			fmt.Println("操作符号错误...")
	}
	return res
}

func main() {

	//请大家完成这样一个需求:
	//输入两个数,再输入一个运算符(+,-,*,/),得到结果.。
	//分析思路....
	var n1 float64 = 1.2
	var n2 float64 = 2.3
	var operator byte = '+'
	result := cal(n1, n2 , operator) 
	fmt.Println("result~=", 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

# 6.5 包的引出

【点击观看视频】包的引出和使用原理

1 ) 在实际的开发中,我们往往需要在不同的文件中,去调用其它文件的定义的函数,比如main.go中,去使用utils.go 文件中的函数,如何实现? -》包

2 ) 现在有两个程序员共同开发一个Go项目,程序员xiaoming希望定义函数Cal,程序员xiaoqiang也想定义函数也叫Cal。两个程序员为此还吵了起来,怎么办?-》包

# 6.6.包的原理图

包的本质实际上就是创建不同的文件夹,来存放程序文件。

画图说明一下包的原理

image-20210113143823601

image-20210113143930931

# 6.7 包的基本概念

说明:go的每一个文件都是属于一个包的,也就是说go是以包的形式来管理文件和项目目录结构的

# 6.8 包的三大作用

区分相同名字的函数、变量等标识符

当程序文件很多时,可以很好的管理项目

控制函数、变量等访问范围,即作用域

# 6.9 包的相关说明

  • 打包基本语法
package 包名
1
  • 引入包的基本语法
import"包的路径"
1

# 6.10 包使用的快速入门

【点击观看视频】包的快速入门

包快速入门-Go相互调用函数,我们将funcCal 定义到文件utils.go, 将utils.go放到一个包中,当其它文件需要使用到utils.go 的方法时,可以import 该包,就可以使用了.【为演示:新建项目目录结构】 代码演示:

image-20210113144151451

utils.go文件

package utils 
import (
	"fmt"
)

var Num1 int = 300
//将计算的功能,放到一个函数中,然后在需要使用,调用即可
//为了让其它包的文件使用Cal函数,需要将C大小类似其它语言的public
func Cal(n1 float64, n2 float64, operator byte) float64 {

	var res float64
	switch operator {
		case '+':
			res = n1 + n2
		case '-':
			res = n1 - n2
		case '*':
			res = n1 * n2
		case '/':
			res = n1 / n2
		default:
			fmt.Println("操作符号错误...")
	}
	return res
}
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

main.go文件

package main
import (
	"fmt"
	"go_code/chapter06/fundemo01/utils"
)

func main() {
	fmt.Println("utils.go Num~=", utils.Num1)
	//请大家完成这样一个需求:
	//输入两个数,再输入一个运算符(+,-,*,/),得到结果.。
	//分析思路....
	var n1 float64 = 1.2
	var n2 float64 = 2.3
	var operator byte = '+'
	result := utils.Cal(n1, n2 , operator) 
	fmt.Println("result~=", result)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 6.11 包使用的注意事项和细节讨论

【点击观看视频】包使用注意事项和细节(1)
【点击观看视频】包使用注意事项和库文件(2)

1 ) 在给一个文件打包时,该包对应一个文件夹,比如这里的 utils 文件夹对应的包名就是utils,文件的包名通常和文件所在的文件夹名一致,一般为小写字母。

2 ) 当一个文件要使用其它包函数或变量时,需要先引入对应的包

  • 引入方式 1 :import "包名"

  • 引入方式 2 :

    import (
    	"包名"
    	"包名"
    )
    
    1
    2
    3
    4
  • package 指令在 文件第一行,然后是 import 指令。

  • 在import 包时,路径从 $GOPATH的 src 下开始,不用带src, 编译器会自动从src下开始引入

3 ) 为了让其它包的文件,可以访问到本包的函数,则该函数名的首字母需要大写,类似其它语言的public,这样才能跨包访问。比如 utils.go 的

//为了让其它包的文件使用Cal函数,需要将C大小类似其它语言的public
func Cal(n1 float64, n2 float64, operator byte) float64 {....
1
2

4 ) 在访问其它包函数,变量时,其语法是 包名.函数名, 比如这里的 main.go文件中

utils.Cal(n1, n2 , operator) 
1

5 ) 如果包名较长,Go支持给包取别名, 注意细节:取别名后,原来的包名就不能使用了

package main
import (
	"fmt"
	util "go_code/chapter06/fundemo01/utils"
)
1
2
3
4
5

说明: 如果给包取了别名,则需要使用别名来访问该包的函数和变量。

6.) 在同一包下,不能有相同的函数名(也不能有相同的全局变量名),否则报重复定义

7 ) 如果你要编译成一个可执行程序文件,就需要将这个包声明为 main, 即 packagemain.这个就 是一个语法规范,如果你是写一个库 ,包名可以自定义

image-20210113145001420

# 6.12 函数的调用机制

【点击观看视频】函数调用机制底层剖析

# 6.12.1 通俗易懂的方式的理解

image-20210113145030017

# 6.12.2 函数-调用过程

介绍:为了让大家更好的理解函数调用过程, 看两个案例,并画出示意图,这个很重要

1 ) 传入一个数+ 1

image-20210113145256044

对上图说明

( 1 ) 在调用一个函数时,会给该函数分配一个新的空间,编译器会通过自身的处理让这个新的空间和其它的栈的空间区分开来

( 2 ) 在每个函数对应的栈中,数据空间是独立的,不会混淆

( 3 ) 当一个函数调用完毕(执行完毕)后,程序会销毁这个函数对应的栈空间。

2 ) 计算两个数,并返回

package main
import (
	"fmt"
)

//一个函数 test
func test(n1 int) {

	n1 = n1 + 1
	fmt.Println("test() n1=", n1) //?输出结果= ?
}

//一个函数 getSum
func getSum(n1 int , n2 int) int {
	sum := n1 + n2
	fmt.Println("getSum sum = ", sum)  // 30
	//当函数有return语句时,就是将结果返回给调用者
	//即谁调用我,就返回给谁
	return sum 
}

func main() {
	n1 := 10
	//调用test
	test(n1)
	fmt.Println("main() n1=", n1)//?输出结果= ?

	sum := getSum(10, 20)
	fmt.Println("main sum = ", sum) // 30
}
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

# 6.12.3 return语句

基本语法和说明

// Go函数支持返回多个值,这一点是其他编程语言没有的
func 函数名(形参列表)(返回值类型列表){
    语句
    return 返回值列表
}

1
2
3
4
5
6

1.如果返回多个值,在接收时,希望忽略某个返回值,则使用_符号表示占位忽略

2.如果返回值只有一个,返回值类型列表可以不写()

案例演示 1:请编写函数,可以计算两个数的和和差,并返回结果。

package main
import (
	"fmt"
)

//请编写函数,可以计算两个数的和和差,并返回结果
func getSumAndSub(n1 int, n2 int) (int, int) {
	sum := n1 + n2
	sub := n1 - n2
	return sum, sub
}

func main() {

	//调用getSumAndSub
	res1, res2 := getSumAndSub(1, 2) //res1 = 3 res2 = -1
	fmt.Printf("res1=%v res2=%v\n", res1, res2)

	//希望忽略某个返回值,则使用 _ 符号表示占位忽略
	_, res3 =  getSumAndSub(3, 9)
	fmt.Println("res3=", res3)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

案例演示 2:一个细节说明: 希望忽略某个返回值,则使用 _ 符号表示占位忽略

package main
import (
	"fmt"
)

//请编写函数,可以计算两个数的和和差,并返回结果
func getSumAndSub(n1 int, n2 int) (int, int) {
	sum := n1 + n2
	sub := n1 - n2
	return sum, sub
}

func main() {
	//希望忽略某个返回值,则使用 _ 符号表示占位忽略
	_, res3 =  getSumAndSub(3, 9)
	fmt.Println("res3=", res3)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 6.13 函数的递归调用

【点击观看视频】递归调用流程分析

# 6.13.1 基本介绍

一个函数在函数体内又调用了本身,我们称为递归调用

# 6.13.2 递归调用快速入门

代码 1

package main
import (
	"fmt"
)

func test(n int) {
	if n > 2 {
		n--
		test(n)
	}
	fmt.Println("n=", n)
}

func main() {
	//看一段代码
	test(4) // ?通过分析来看下递归调用的特点
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

上面代码的分析图:

image-20210113151827141

代码 2

package main
import (
	"fmt"
)

func test2(n int) {
	if n > 2 {
		n-- //递归必须向退出递归条件逼进,否则就是无限循环调用
		test2(n)
	} else {
		fmt.Println("n=", n)
	}
}

func main() {
	test2(4) // ?通过分析来看下递归调用的特点
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

对上面代码分析的示意图:

image-20210113151952074

# 6.13.3 递归调用的总结

函数递归需要遵守的重要原则:

1 ) 执行一个函数时,就创建一个新的受保护的独立空间(新函数栈)

2 ) 函数的局部变量是独立的,不会相互影响

3 ) 递归必须向退出递归的条件逼近,否则就是无限递归,死龟了:)

4 ) 当一个函数执行完毕,或者遇到return,就会返回,遵守谁调用,就将结果返回给谁,同时当函数执行完毕或者返回时,该函数本身也会被系统销毁

# 6.13.4 递归课堂练习题

【点击观看视频】递归调用课堂练习(1)
【点击观看视频】递归调用课堂练习(2)

题 1 :斐波那契数

请使用递归的方式,求出斐波那契数 1 , 1 , 2 , 3 , 5 , 8 , 13

给你一个整数n,求出它的斐波那契数是多少?

思路:

1 ) 当n== 1 ||n== 2 , 返回 1

2 ) 当n>= 2 , 返回 前面两个数的和 f(n- 1 )+f(n- 2 )

代码:

package main
import (
	"fmt"
)

/*
请使用递归的方式,求出斐波那契数1,1,2,3,5,8,13...
给你一个整数n,求出它的斐波那契数是多少?
*/
func fbn(n int) int {
	if (n == 1 || n == 2) {
		return 1
	} else {
		return fbn(n - 1) + fbn(n - 2)
	}
}

func main() {
	res := fbn(3)
	//测试
	fmt.Println("res=", res)
	fmt.Println("res=", fbn(4)) // 3
	fmt.Println("res=", fbn(5)) // 5 
	fmt.Println("res=", fbn(6)) // 8 
}
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

题 2 :求函数值

已知 f( 1 )= 3 ;f(n)= 2 *f(n- 1 )+ 1 ;

请使用递归的思想编程,求出 f(n)的值?

思路:

直接使用给出的表达式即可完成

代码:

package main
import (
	"fmt"
)

/*
题2:求函数值已知 f(1)=3; f(n) = 2*f(n-1)+1; 请使用递归的思想编程,求出 f(n)的值?

*/
func f(n int) int {
	if n == 1 {
		return 3
	} else {
		return 2 * f(n - 1) + 1
	}
}
func main(){

	//测试一下
	fmt.Println("f(1)=", f(1))
	fmt.Println("f(5)=", f(5))
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

练习题 3

题 3 :猴子吃桃子问题

有一堆桃子,猴子第一天吃了其中的一半,并再多吃了一个!以后每天猴子都吃其中的一半,然后

再多吃一个。当到第十天时,想再吃时(还没吃),发现只有 1 个桃子了。问题:最初共多少个桃子?

思路分析:

1 ) 第 10 天只有一个桃子

2 ) 第 9 天有几个桃子 = (第 10 天桃子数量 + 1 )* 2

3 ) 规律: 第n天的桃子数据 peach(n)=(peach(n+ 1 )+ 1 )* 2

代码:

package main
import (
	"fmt"
)

//题3:猴子吃桃子问题有一堆桃子,猴子第一天吃了其中的一半,
//并再多吃了一个!以后每天猴子都吃其中的一半,然后再多吃一个。
//当到第十天时,想再吃时(还没吃),发现只有1个桃子了。问题:最初共多少个桃子?

//思路分析

/*
1)第10天只有一个桃子
2)第9天有几个桃子  =  (第10天桃子数量 + 1) * 2 
3)规律: 第n天的桃子数据  peach(n) = (peach(n+1) + 1) * 2

*/
//n 范围是  1 -- 10 之间
func peach(n int) int {
	if n > 10 || n < 1 {
		fmt.Println("输入的天数不对")
		return 0 //返回0表示没有得到正确数量
	}
	if n == 10 {
		return 1
	} else {
		return (peach(n + 1) + 1) * 2
	}
}

func main() {
	fmt.Println("第1天桃子数量是=", peach(1)) //1534
}
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

# 6.14 函数使用的注意事项和细节讨论

【点击观看视频】函数注意事项和细节(1)
【点击观看视频】函数注意事项和细节(2)
【点击观看视频】函数注意事项和细节(3)

1 ) 函数的形参列表可以是多个,返回值列表也可以是多个。

2 ) 形参列表和返回值列表的数据类型可以是值类型和引用类型。

3 ) 函数的命名遵循标识符命名规范,首字母不能是数字,首字母大写该函数可以被本包文件和其它包文件使用,类似public, 首字母小写,只能被本包文件使用,其它包文件不能使用,类似privat

4 ) 函数中的变量是局部的,函数外不生效【案例说明】

package main
import (
	"fmt"
)
//函数中的变量是局部的,函数外不生效
func test(){
    //n1 是 test函数的局部变量,只能在test中使用
    var n1 int = 10
}

func main() {	
	fmt.Println("n1=", n1) //报错,这里不能使用n1
}
1
2
3
4
5
6
7
8
9
10
11
12
13

5 ) 基本数据类型和数组默认都是值传递的,即进行值拷贝。在函数内修改,不会影响到原来的值。

package main
import (
	"fmt"
)

func test02(n1 int){
	n1 = n1 + 10
	fmt.Println("test02() n1=", n1)
}

func main() {	
	num := 20
	test02(num)
	fmt.Println("main() num=", num)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

6.) 如果希望函数内的变量能修改函数外的变量(指的是默认以值传递的方式的数据类型),可以传入变量的地址&,函数内以指针的方式操作变量。从效果上看类似引用 。

package main
import (
	"fmt"
)

// n1 就是 *int 类型
func test03(n1 *int) {
	fmt.Printf("n1的地址 %v\n",&n1)
	*n1 = *n1 + 10
	fmt.Println("test03() n1= ", *n1) // 30
}

func main() {
	num := 20
	fmt.Printf("num的地址=%v\n", &num)
	test03(&num)
	fmt.Println("main() num= ", num) // 30
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

image-20210113153051324

7 ) Go函数不支持函数重载

package main
import (
	"fmt"
)
//有两个test02不支持重载
func test02(n1 int) {	
	n1 = n1 + 10
	fmt.Println("test02() n1= ", n1)
}
//有两个test02不支持重载
func test02(n1 int , n2 int) {
	
}

func main() {

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

8 ) 在Go中,函数也是一种数据类型,可以赋值给一个变量,则该变量就是一个函数类型的变量了。通过该变量可以对函数调用

package main
import (
	"fmt"
)

//在Go中,函数也是一种数据类型,
//可以赋值给一个变量,则该变量就是一个函数类型的变量了。通过该变量可以对函数调用
func getSum(n1 int, n2 int) int {
	return n1 + n2
}

func main() {
	
	a := getSum
	fmt.Printf("a的类型%T, getSum类型是%T\n", a, getSum)

	res := a(10, 40) // 等价  res := getSum(10, 40)
	fmt.Println("res=", res)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

9 ) 函数既然是一种数据类型,因此在Go中,函数可以作为形参,并且调用

package main
import (
	"fmt"
)

//在Go中,函数也是一种数据类型,
//可以赋值给一个变量,则该变量就是一个函数类型的变量了。通过该变量可以对函数调用
func getSum(n1 int, n2 int) int {
	return n1 + n2
}

//函数既然是一种数据类型,因此在Go中,函数可以作为形参,并且调用
func myFun(funvar func(int, int) int, num1 int, num2 int ) int {
	return funvar(num1, num2)
}

func main() {
	//看案例
	res2 := myFun(getSum, 50, 60)
	fmt.Println("res2=", res2)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

10 ) 为了简化数据类型定义,Go支持自定义数据类型

基本语法:type 自定义数据类型名 数据类型 // 理解: 相当于一个别名

案例:typemyIntint // 这时 myInt 就等价 int 来使用了.

案例:typemySum func(int,int)int // 这时 mySum 就等价 一个 函数类型 func(int,int)int

举例说明自定义数据类型的使用:

package main
import (
	"fmt"
)


//在Go中,函数也是一种数据类型,
//可以赋值给一个变量,则该变量就是一个函数类型的变量了。通过该变量可以对函数调用

func getSum(n1 int, n2 int) int {
	return n1 + n2
}

//函数既然是一种数据类型,因此在Go中,函数可以作为形参,并且调用
func myFun(funvar func(int, int) int, num1 int, num2 int ) int {
	return funvar(num1, num2)
}

//再加一个案例
//这时 myFun 就是 func(int, int) int类型
type myFunType func(int, int) int

//函数既然是一种数据类型,因此在Go中,函数可以作为形参,并且调用
func myFun2(funvar myFunType, num1 int, num2 int ) int {
	return funvar(num1, num2)
}

func main() {
	
	// 给int取了别名 , 在go中 myInt 和 int 虽然都是int类型,但是go认为myInt和int两个类型
	type myInt int 

	var num1 myInt // 
	var num2 int
	num1 = 40
	num2 = int(num1) //各位,注意这里依然需要显示转换,go认为myInt和int两个类型
	fmt.Println("num1=", num1, "num2=",num2)

	//看案例
	res3 := myFun2(getSum, 500, 600)
	fmt.Println("res3=", res3)
}
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

11 )支持对函数返回值命名

package main
import (
	"fmt"
)

//支持对函数返回值命名
func getSumAndSub(n1 int, n2 int) (sum int, sub int){
	sub = n1 - n2
	sum = n1 + n2
	return
}

func main() {

	//看案例
	a1, b1 := getSumAndSub(1, 2)
	fmt.Printf("a=%v b=%v\n", a1, b1)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

12 ) 使用 _ 标识符,忽略返回值

package main
import (
	"fmt"
)

//支持对函数返回值命名
func cal(n1 int, n2 int) (sum int, sub int){
	sub = n1 - n2
	sum = n1 + n2
	return
}

func main() {
	//看案例
	res1, _ := cal(1, 2)
	fmt.Printf("res1 = %d", res1)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

13 ) Go支持可变参数

//支持0到多个参数
func sum(args...int){
}
//支持1到多个参数
func sum(n1 int,args... int) sum int{
}
1
2
3
4
5
6

说明:

  1. args是slice切片,通过args[index]可以访问到各个值
  2. 案例演示:编写一个函数sum,可以求出1到多个int的和
  3. 如果一个函数的形参列表中有可变的参数,则可变参数需要放到形参列表的最后

代码演示:

package main
import (
	"fmt"
)
//案例演示: 编写一个函数sum ,可以求出  1到多个int的和
//可以参数的使用
func sum(n1 int, args... int) int {
	sum := n1 
	//遍历args 
	for i := 0; i < len(args); i++ {
		sum += args[i]  //args[0] 表示取出args切片的第一个元素值,其它依次类推
	}
	return sum
}

func main() {
	//测试一下可变参数的使用
	res4 := sum(10, 0, -1, 90, 10,100)
	fmt.Println("res4=", res4)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 6.15 函数的课堂练习

【点击观看视频】函数课堂练习

题 1:代码有无错误,输出什么?

package main
import (
	"fmt"
)

func sum(n1,n2 float32) float32 {
    fmt.Printf("n1 type = %T\n",n1)
    return n1 + n2
}

func main() {
    fmt.Println("sum=",sum(1,2))
}
1
2
3
4
5
6
7
8
9
10
11
12
13

题 2:代码有无错误,为什么?

package main
import (
	"fmt"
)

type mySum func(int,int) int

func sum(n1 int,n2 int) int {
    return n1 + n2
}

func sum2(n1,n2,n3 int) int {
    return n1 + n2
}

func myFunc(funcVar mySum, num1 int, num2 int) int{
    return funcVar(num1,num2)
}

func main() {
    a := sum
    b := sum2
    fmt.Println(myFunc(a,1,2))//ok
    fmt.println(myFunc(b,1,2))//error,类型不匹配,不能把func sum2(n1,n2,n3 int) int赋给 func(int,int) int
}

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

题 3 :请编写一个函数 swap(n 1 *int,n 2 *int) 可以交换 n 1 和 n 2 的值

package main
import (
	"fmt"
)

//请编写一个函数 swap(n1 *int, n2 *int) 可以交换 n1 和 n2的值
func swap(n1 *int, n2 *int) {
	//定义一个临时变量
	t := *n1
	*n1 = *n2
	*n2 = t
}

func main() {	
	a := 10
	b := 20
	swap(&a, &b) //传入的地址
	fmt.Printf("a=%v, b=%v", a, b)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 6.16 INIT函数

【点击观看视频】init函数

# 6.16.1 基本介绍

每一个源文件都可以包含一个 init 函数,该函数会在main函数执行前,被Go运行框架调用,也 就是说init会在main函数前被调用。

# 6.16.2 案例说明:

package main
import (
	"fmt"
)

func init() {
    fmt.Println("init()")
}

func main() {	
    fmt.Println("main()")
}
1
2
3
4
5
6
7
8
9
10
11
12

输出的结果是:

init()
main()
1
2

# 6.16.3 inti函数的注意事项和细节

1 ) 如果一个文件同时包含全局变量定义, init 函数和 main 函数,则执行的流程全局变量定义 - >init函数 - >main 函数

package main
import (
	"fmt"
)

var age = test()
//为了看到全局变量是先被初始化的,我们这里先写函数
func test() int {
	fmt.Println("test()")//1
	return 90
}
// init函数,通常可以在init函数中完成初始化工作
func init() {
    fmt.Println("init()")//2
}

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

2 ) init函数最主要的作用,就是完成一些初始化的工作,比如下面的案例

package utils
import "fmt"
var Age int
var Name string

//Age 和 Name 全局变量,我们需要在main.go 使用
//但是我们需要初始化Age 和 Name

//init 函数完成初始化工作
func init() {
	fmt.Println("utils 包的  init()...")
	Age = 100
	Name = "tom~"
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
package main
import (
	"fmt"
	//引入包
	"go_code/chapter06/funcinit/utils"
	
)

var age = test()
//为了看到全局变量是先被初始化的,我们这里先写函数
func test() int {
	fmt.Println("test()")//1
	return 90
}
// init函数,通常可以在init函数中完成初始化工作
func init() {
    fmt.Println("init()")//2
}

func main() {	
    fmt.Println("main()...age=",age)//3
    fmt.Println("Age=",utils.Age,"Name=",utils.Name)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

3 ) 细节说明: 面试题:案例如果main.go 和 utils.go 都含有 变量定义,init函数时,执行的流程又是怎么样的呢?

image-20210113173901846

# 6.17 匿名函数

【点击观看视频】匿名函数

# 6.17.1 介绍

Go支持匿名函数,匿名函数就是没有名字的函数,如果我们某个函数只是希望使用一次,可以考 虑使用匿名函数,匿名函数也可以实现多次调用。

# 6.17.2 匿名函数使用方式 1

在定义匿名函数时就直接调用,这种方式匿名函数只能调用一次。 【案例演示】

package main
import (
	"fmt"
)
func main() {
	//在定义匿名函数时就直接调用,这种方式匿名函数只能调用一次

	//案例演示,求两个数的和, 使用匿名函数的方式完成
	res1 := func (n1 int, n2 int) int {
		return n1 + n2
	}(10, 20)

	fmt.Println("res1=", res1)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 6.17.3 匿名函数使用方式 2

将匿名函数赋给一个变量(函数变量),再通过该变量来调用匿名函数 【案例演示】

package main
import (
	"fmt"
)

func main() {

	//将匿名函数func (n1 int, n2 int) int赋给 a变量
	//则a 的数据类型就是函数类型 ,此时,我们可以通过a完成调用
	a := func (n1 int, n2 int) int {
		return n1 - n2
	}

	res2 := a(10, 30)
	fmt.Println("res2=", res2)
	res3 := a(90, 30)
	fmt.Println("res3=", res3)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 6.17.4 全局匿名函数

如果将匿名函数赋给一个全局变量,那么这个匿名函数,就成为一个全局匿名函数,可以在程序有效。

package main
import (
	"fmt"
)

var (
	//fun1就是一个全局匿名函数
	Fun1 = func (n1 int, n2 int) int {
		return n1 * n2
	}
)

func main() {

	//全局匿名函数的使用
	res4 := Fun1(4, 9)
	fmt.Println("res4=", res4)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 6.18 闭包

【点击观看视频】闭包的基本介绍

# 6.18.1 介绍

基本介绍:闭包就是一个函数和与其相关的引用环境组合的一个整体(实体)

# 6.18.2 案例演示:

package main
import (
	"fmt"
)

//累加器
func AddUpper() func (int) int {
	var n int = 10 
	return func (x int) int {
		n = n + x
		return n
	}
}

func main() {
	
	//使用前面的代码
	f := AddUpper()
	fmt.Println(f(1))// 11 
	fmt.Println(f(2))// 13
	fmt.Println(f(3))// 16

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

对上面代码的说明和总结

1 ) AddUpper 是一个函数,返回的数据类型是 fun(int)int

2 ) 闭包的说明

	var n int = 10 
	return func (x int) int {
		n = n + x
		return n
	}
1
2
3
4
5

返回的是一个匿名函数, 但是这个匿名函数引用到函数外的n,因此这个匿名函数就和n形成一个整体,构成闭包。

3 ) 大家可以这样理解: 闭包是类, 函数是操作,n是字段。函数和它使用到n构成闭包。

4 ) 当我们反复的调用f函数时,因为n是初始化一次,因此每调用一次就进行累计。

5 ) 我们要搞清楚闭包的关键,就是要分析出返回的函数它使用(引用)到哪些变量,因为函数和它引 用到的变量共同构成闭包。

6.) 对上面代码的一个修改,加深对闭包的理解

package main
import (
	"fmt"
	"strings"
)


//累加器
func AddUpper() func (int) int {
	var n int = 10 
	var str = "hello"
	return func (x int) int {
		n = n + x
		str += string(36) // => 36 = '$'   
		fmt.Println("str=", str) // 1. str="hello$" 2. str="hello$$" 3. str="hello$$$"
		return n
	}
}

func main() {
	
	//使用前面的代码
	f := AddUpper()
	fmt.Println(f(1))// 11 
	fmt.Println(f(2))// 13
	fmt.Println(f(3))// 16

}
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

# 6.18.3 闭包的最佳实践

【点击观看视频】闭包最佳实践和分析

请编写一个程序,具体要求如下

1 ) 编写一个函数 makeSuffix(suffixstring) 可以接收一个文件后缀名(比如.jpg),并返回一个闭包

2 ) 调用闭包,可以传入一个文件名,如果该文件名没有指定的后缀(比如.jpg),则返回 文件名.jpg, 如果已经有.jpg后缀,则返回原文件名。

3 ) 要求使用闭包的方式完成

4 ) strings.HasSuffix, 该函数可以判断某个字符串是否有指定的后缀。

代码:

package main
import (
	"fmt"
	"strings"
)

//
// 1)编写一个函数 makeSuffix(suffix string)  可以接收一个文件后缀名(比如.jpg),并返回一个闭包
// 2)调用闭包,可以传入一个文件名,如果该文件名没有指定的后缀(比如.jpg) ,则返回 文件名.jpg , 如果已经有.jpg后缀,则返回原文件名。
// 3)要求使用闭包的方式完成
// 4)strings.HasSuffix , 该函数可以判断某个字符串是否有指定的后缀。

func makeSuffix(suffix string) func (string) string {

	return func (name string) string {
		//如果 name 没有指定后缀,则加上,否则就返回原来的名字
		if !strings.HasSuffix(name, suffix)  {
			return name + suffix
		}

		return name
	}
}


func makeSuffix2(suffix string, name string)  string {


	//如果 name 没有指定后缀,则加上,否则就返回原来的名字
	if !strings.HasSuffix(name, suffix)  {
		return name + suffix
	}

	return name
	
}

func main() {
	//测试makeSuffix 的使用
	//返回一个闭包
	f2 := makeSuffix(".jpg") //如果使用闭包完成,好处是只需要传入一次后缀。
	fmt.Println("文件名处理后=", f2("winter")) // winter.jgp
	fmt.Println("文件名处理后=", f2("bird.jpg")) // bird.jpg

	fmt.Println("文件名处理后=", makeSuffix2("jpg", "winter")) // winter.jgp
	fmt.Println("文件名处理后=", makeSuffix2("jpg", "bird.jpg")) // bird.jpg
}
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

上面代码的总结和说明:

1 ) 返回的匿名函数和 makeSuffix(suffixstring) 的 suffix 变量 组合成一个闭包,因为 返回的函数引用到suffix这个变量

2 ) 我们体会一下闭包的好处,如果使用传统的方法,也可以轻松实现这个功能,但是传统方法需要每次都传入 后缀名,比如 .jpg,而闭包因为可以保留上次引用的某个值,所以我们传入一次就可以反复使用。大家可以仔细的体会一把!

# 6.19 函数的DEFER

【点击观看视频】defer的基本使用

# 6.19.1 为什么需要defer

在函数中,程序员经常需要创建资源(比如:数据库连接、文件句柄、锁等) ,为了在函数执行完毕后,及时的释放资源,Go的设计者提供defer(延时机制)。

# 6.19.2 快速入门案例

package main
import (
	"fmt"
)

func sum(n1 int, n2 int) int {
	
	//当执行到defer时,暂时不执行,会将defer后面的语句压入到独立的栈(defer栈)
	//当函数执行完毕后,再从defer栈,按照先入后出的方式出栈,执行
	defer fmt.Println("ok1 n1=", n1) //defer 3. ok1 n1 = 10
	defer fmt.Println("ok2 n2=", n2) //defer 2. ok2 n2= 20

	res := n1 + n2 // res = 30
	fmt.Println("ok3 res=", res) // 1. ok3 res= 30
	return res

}

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

执行后,输出的结果:

ok3 res= 30
ok2 n2= 20
ok1 n1 = 10
res= 30
1
2
3
4

# 6.19.3 defer的注意事项和细节

1 ) 当go执行到一个defer时,不会立即执行defer后的语句,而是将defer 后的语句压入到一个栈中[我为了讲课方便,暂时称该栈为defer栈],然后继续执行函数下一个语句。

2 ) 当函数执行完毕后,在从defer栈中,依次从栈顶取出语句执行(注:遵守栈 先入后出的机制),所以同学们看到前面案例输出的顺序。

3 ) 在defer 将语句放入到栈时,也会将相关的值拷贝同时入栈。请看一段代码:

package main
import (
	"fmt"
)

func sum(n1 int, n2 int) int {
	
	//当执行到defer时,暂时不执行,会将defer后面的语句压入到独立的栈(defer栈)
	//当函数执行完毕后,再从defer栈,按照先入后出的方式出栈,执行
	defer fmt.Println("ok1 n1=", n1) //defer 3. ok1 n1 = 10
	defer fmt.Println("ok2 n2=", n2) //defer 2. ok2 n2= 20
	//增加一句话
	n1++ // n1 = 11
	n2++ // n2 = 21
	res := n1 + n2 // res = 32
	fmt.Println("ok3 res=", res) // 1. ok3 res= 32
	return res

}

func main() {
	res := sum(10, 20)
	fmt.Println("res=", res)  // 4. res= 32
}
	
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

上面代码输出的结果如下:

ok3 res= 32
ok2 n2= 20
ok1 n1 = 10
res= 32
1
2
3
4

# 6.19.4 defer的最佳实践

【点击观看视频】defer注意事项和最佳实践

defer 最主要的价值是在,当函数执行完毕后,可以及时的释放函数创建的资源。看下模拟代码。。

func test(){
	//关闭文件资源
	file = openfile(文件名)
	defer file.close()
	//其他代码
}
1
2
3
4
5
6
func test(){
	//释放数据库资源
	connect = openDatabase()
	defer connect.close()
	//其他代码
}
1
2
3
4
5
6

说明

1 ) 在golang编程中的通常做法是,创建资源后,比如(打开了文件,获取了数据库的链接,或者是锁资源), 可以执行 defer file.Close() defer connect.Close()

2 ) 在defer后,可以继续使用创建资源.

3 ) 当函数完毕后,系统会依次从defer栈中,取出语句,关闭资源.

4 ) 这种机制,非常简洁,程序员不用再为在什么时机关闭资源而烦心。

# 6.20.函数参数传递方式

【点击观看视频】函数参数传递方式

# 6.20.1 基本介绍

我们在讲解函数注意事项和使用细节时,已经讲过值类型和引用类型了,这里我们再系统总结一下,因为这是重难点,值类型参数默认就是值传递,而引用类型参数默认就是引用传递。

# 6.20.2 两种传递方式

1 ) 值传递

2 ) 引用传递

其实,不管是值传递还是引用传递,传递给函数的都是变量的副本,不同的是,值传递的是值的拷贝,引用传递的是地址的拷贝,一般来说,地址拷贝效率高,因为数据量小,而值拷贝决定拷贝的数据大小,数据越大,效率越低。

# 6.20.3 值类型和引用类型

1 ) 值类型:基本数据类型 int 系列,float 系列,bool,string 、数组和结构体struct

2 ) 引用类型:指针、slice切片、map、管道chan、interface 等都是引用类型

# 6.20.4 值传递和引用传递使用特点

1 ) 值类型默认是值传递:变量直接存储值,内存通常在栈中分配

image-20210113192416699

2 ) 引用类型默认是引用传递:变量存储的是一个地址,这个地址对应的空间才真正存储数据(值),内存通常在堆上分配,当没有任何变量引用这个地址时,改地址对应的数据空间就成为一个垃圾,由GC来回收

image-20210113192704640

3 ) 如果希望函数内的变量能修改函数外的变量,可以传入变量的地址&,函数内以指针的方式操作变量。从效果上看类似引用 。这个案例在前面详解函数使用注意事项的

package main
import (
	"fmt"
)

// n1 就是 *int 类型
func test03(n1 *int) {
	fmt.Printf("n1的地址 %v\n",&n1)
	*n1 = *n1 + 10
	fmt.Println("test03() n1= ", *n1) // 30
}

func main() {
	num := 20
	fmt.Printf("num的地址=%v\n", &num)
	test03(&num)
	fmt.Println("main() num= ", num) // 30
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

image-20210113153051324

# 6.21 变量作用域

【点击观看视频】变量作用域

1 ) 函数内部声明/定义的变量叫局部变量,作用域仅限于函数内部

package main
import (
	"fmt"
)

func test() {
	//age 和 Name的作用域就只是在test函数内部
    age := 10
    Name := "tom"
}

func main() {
}
1
2
3
4
5
6
7
8
9
10
11
12
13

2 ) 函数外部声明/定义的变量叫全局变量,作用域在整个包都有效,如果其首字母为大写,则作用域在整个程序有效

package main
import (
	"fmt"
)

//函数外部声明/定义的变量叫全局变量,
//作用域在整个包都有效,如果其首字母为大写,则作用域在整个程序有效
var age int = 50
var Name string = "jack~"

//函数
func test() {
	//age 和 Name的作用域就只在test函数内部
	age := 10
	Name := "tom~"
	fmt.Println("age=", age) // 10
	fmt.Println("Name=", Name) // tom~
}

func main() {

	fmt.Println("age=", age) //  50
	fmt.Println("Name=", Name) // jack~
	test()
}
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 ) 如果变量是在一个代码块,比如 for/if中,那么这个变量的的作用域就在该代码块

package main
import (
	"fmt"
)

func main() {
	//如果变量是在一个代码块,比如 for / if中,那么这个变量的的作用域就在该代码块
	for i := 0; i <= 10; i++ {
		fmt.Println("i=", i)
	}

	var i int //局部变量
	for i = 0; i <= 10; i++ {
		fmt.Println("i=", i)
	}

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

# 6.21.1 变量作用域的课堂练习

【点击观看视频】变量作用域课堂练习
package main
import (
	"fmt"
)

var name = "tom" //全局变量

func test01(){
	fmt.Println(name) //tom
}


func test02(){ //编译器采用就近原则
	name := "jack"
	fmt.Println(name) //jack
}

func main() {
	fmt.Println(name) //tom
	test01() //tom
	test02() //jack
	test01() //tom		
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

思考:下面代码输出什么内容?

package main
import (
	"fmt"
)

var Age int = 20 //ok
Name := "tom" //error
func main() {
	fmt.Println("name",Name) 
}

//错误分析:Name := "tom" 等价于
//var Name string
//Name = "tom"
//赋值语句不能在函数体外,所以错
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 6.22 函数课堂练习(综合)

【点击观看视频】函数课堂练习题和作业

1 ) 函数可以没有返回值案例,编写一个函数,从终端输入一个整数打印出对应的金子塔

分析思路:就是将原来写的打印金字塔的案例,使用函数的方式封装,在需要打印时,直接调用即可。

package main
import (
	"fmt"
)


//将打印金字塔的代码封装到函数中
func printPyramid(totalLevel int) {

	//i 表示层数
	for i := 1; i <= totalLevel; i++ {
		//在打印*前先打印空格
		for k := 1; k <= totalLevel - i; k++ {
			fmt.Print(" ")
		}

		//j 表示每层打印多少*
		for j :=1; j <= 2 * i - 1; j++ {
				fmt.Print("*")
		}
		fmt.Println()
	}

}
func main() {
	//调用printPyramid函数,就可以打印金字塔
	//从终端输入一个整数打印出对应的金子塔
	var n int 
	fmt.Println("请输入打印金字塔的层数")
	fmt.Scanln(&n)
	printPyramid(n)
}
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

2 ) 编写一个函数,从终端输入一个整数( 1 — 9 ),打印出对应的乘法表

分析思路:就是将原来写的调用九九乘法表的案例,使用函数的方式封装,在需要打印时,直接调用即可

代码:

package main
import (
	"fmt"
)

//编写一个函数调用九九乘法表
func printMulti(num int) {

	//打印出九九乘法表
	//i 表示层数
	for i := 1; i <= num; i++ {
		for j := 1; j <= i; j++ {
			fmt.Printf("%v * %v = %v \t", j, i, j * i)
		}
		fmt.Println()
	} 

}

func main() {

	//从终端输入一个整数表示要打印的乘法表对应的数
	var num int
	fmt.Println("请输入九九乘法表的对应数")
	fmt.Scanln(&num)
	printMulti(num)
}
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

3 ) 编写函数,对给定的一个二维数组( 3 × 3 )转置,这个题讲数组的时候再完成

image-20210113194553017

# 6.23 字符串常用的系统函数

【点击观看视频】Go字符串函数详解(1)
【点击观看视频】Go字符串函数详解(2)
【点击观看视频】Go字符串函数详解(3)

说明:字符串在我们程序开发中,使用的是非常多的,常用的函数需要同学们掌握[带看手册或者官方编程指南]:

1 ) 统计字符串的长度,按字节 len(str)

package main
import (
	"fmt"
)
func main() {
	//golang的编码统一为utf-8(ascii的字符(字母和数字)占一个字节,汉字占3个字节)
    str := "hello北"
	fmt.Println("name",Name) //8
}
1
2
3
4
5
6
7
8
9

2 ) 字符串遍历,同时处理有中文的问题 r:=[]rune(str)

package main
import (
	"fmt"
)
func main() {	
    str := "hello北"
    r := []rune(str2)
    for i := 0;i < len(r);i++{
    	fmt.Printf("字符=%c\n",r[i]) 
    }
}
1
2
3
4
5
6
7
8
9
10
11

3 ) 字符串转整数: n,err:=strconv.Atoi(" 12 ")

package main

import (
	"fmt"
	"strconv"
)

func main() {
	n, err := strconv.Atoi("12")
    //n, err := strconv.Atoi("hello")
	if err != nil {
		fmt.Println("转换错误", err)
	} else {
		fmt.Println("转成结果是", n)
	}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

4 ) 整数转字符串 str=strconv.Itoa( 12345 )

package main

import (
	"fmt"
	"strconv"
)

func main() {
	str := strconv.Itoa(12345)
	fmt.Printf("str=%v, str=%T", str, str)
}
1
2
3
4
5
6
7
8
9
10
11

5 ) 字符串 转 []byte: varbytes=[]byte("hello go")

package main

import (
	"fmt"
)

func main() {
	var bytes = []byte("hello go")
	fmt.Printf("bytes=%v\n", bytes)
}
1
2
3
4
5
6
7
8
9
10

6.) []byte 转 字符串:str=string([]byte{ 97 , 98 , 99 })

package main

import (
	"fmt"
)

func main() {
	var str = string([]byte{97, 98, 99})
	fmt.Printf("str=%v\n", str)
}
1
2
3
4
5
6
7
8
9
10

7 ) 10 进制转 2 , 8 , 16.进制: str=strconv.FormatInt( 123 , 2 )// 2 - > 8 , 16

package main

import (
	"fmt"
	"strconv"
)

func main() {
	var str = strconv.FormatInt(123, 2)
	fmt.Printf("123对应的二进制是=%v\n", str)
	str = strconv.FormatInt(123, 16)
	fmt.Printf("123对应的16进制是=%v\n", str)
}
1
2
3
4
5
6
7
8
9
10
11
12
13

8 ) 查找子串是否在指定的字符串中:strings.Contains("seafood","foo")//true

package main

import (
	"fmt"
	"strings"
)

func main() {
	b1 := strings.Contains("seafood", "mary")
	b2 := strings.Contains("seafood", "foo")
	fmt.Printf("b1=%v\nb2=%v\n", b1, b2)
}
1
2
3
4
5
6
7
8
9
10
11
12

9 ) 统计一个字符串有几个指定的子串 : strings.Count("ceheese","e")// 4

package main

import (
	"fmt"
	"strings"
)

func main() {
	num := strings.Contains("ceheese", "e")
	fmt.Printf("num=%v\n", num)
}
1
2
3
4
5
6
7
8
9
10
11

10 ) 不区分大小写的字符串比较(==是区分字母大小写的):fmt.Println(strings.EqualFold("abc","Abc"))//true

package main

import (
	"fmt"
	"strings"
)

func main() {
	var b = strings.EqualFold("abc", "Abc")
	fmt.Printf("b=%v\n", b)           // true
	fmt.Println("结果", "abc" == "Abc") // false
}
1
2
3
4
5
6
7
8
9
10
11
12

11 )返回子串在字符串第一次出现的index值,如果没有返回- 1 :strings.Index("NLT_abc","abc")// 4

package main

import (
	"fmt"
	"strings"
)

func main() {
	index := strings.Index("NLT_abcabcabc", "abc")
	fmt.Printf("index=%v\n", index)
}
1
2
3
4
5
6
7
8
9
10
11

12 ) 返回子串在字符串最后一次出现的index,如没有返回- 1 :strings.LastIndex("gogolang","go")

package main

import (
	"fmt"
	"strings"
)

func main() {
	index := strings.LastIndex("go golang", "go")//3
	fmt.Printf("index=%v\n", index)
}
1
2
3
4
5
6
7
8
9
10
11

13 ) 将指定的子串替换成 另外一个子串:strings.Replace("gogohello","go","go语言",n)n可以指定你希望替换几个,如果n=- 1 表示全部替换

package main

import (
	"fmt"
	"strings"
)

func main() {
	str2 := "go go hello"
	str := strings.Replace(str2, "go", "北京", -1)
	fmt.Printf("str=%v str2=%v", str, str2)
}
1
2
3
4
5
6
7
8
9
10
11
12

14 ) 按照指定的某个字符,为分割标识,将一个字符串拆分成字符串数组:strings.Split("hello,wrold,ok",",")

package main

import (
	"fmt"
	"strings"
)

func main() {
	strArr := strings.Split("hello,world,ok", ",")
	for i := 0; i < len(strArr); i++ {
		fmt.Printf("str[%v]=%v\n", i, strArr[i])
	}

	fmt.Printf("strArr=%v\n", strArr)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

15 ) 将字符串的字母进行大小写的转换:strings.ToLower("Go")//gostrings.ToUpper("Go")//GO

package main

import (
	"fmt"
	"strings"
)

func main() {
	str := "goLang Hello"
	//str = strings.ToLower(str)
	str = strings.ToUpper(str)
	fmt.Printf("str=%v\n", str)
}
1
2
3
4
5
6
7
8
9
10
11
12
13

16.) 将字符串左右两边的空格去掉: strings.TrimSpace("tnalonegopherntrn ")

package main

import (
	"fmt"
	"strings"
)

func main() {
	str := strings.TrimSpace(" tn a lone gopher ntrn ")
	fmt.Printf("str=%q\n", str)
}
1
2
3
4
5
6
7
8
9
10
11

17 ) 将字符串左右两边指定的字符去掉 : strings.Trim("!hello!","!") //["hello"]//将左右两边! 和 ""去掉

package main

import (
	"fmt"
	"strings"
)

func main() {
	str := strings.Trim("! he!llo! ", " !")
	fmt.Printf("str=%q\n", str)
}
1
2
3
4
5
6
7
8
9
10
11

18 ) 将字符串左边指定的字符去掉 : strings.TrimLeft("!hello!","!") //["hello"]//将左边! 和 " "去掉

19 ) 将字符串右边指定的字符去掉 :strings.TrimRight("!hello!","!") //["hello"]//将右边! 和 " "去掉

20 ) 判断字符串是否以指定的字符串开头:strings.HasPrefix("ftp:// 192. 168. 10. 1 ","ftp")//true

package main

import (
	"fmt"
	"strings"
)

func main() {
	b := strings.HasPrefix("ftp://192.168.0.1", "ftp")
	fmt.Printf("b=%v\n", b)
}
1
2
3
4
5
6
7
8
9
10
11

21 ) 判断字符串是否以指定的字符串结束:strings.HasSuffix("NLT_abc.jpg","abc")//false

# 6.24 时间和日期相关函数

【点击观看视频】Go时间和日期函数详解(1)
【点击观看视频】Go时间和日期函数详解(2)

# 6.24.1 基本的介绍

说明:在编程中,程序员会经常使用到日期相关的函数,比如:统计某段代码执行花费的时间等等。

1 ) 时间和日期相关函数,需要导入 time包

image-20210113222534558

2 ) time.Time 类型,用于表示时间

package main

import (
	"fmt"
	"time"
)

func main() {
	//看看日期和时间相关函数和方法使用
	//1. 获取当前时间
	now := time.Now()
	fmt.Printf("now=%v now type=%T\n", now, now)
}
1
2
3
4
5
6
7
8
9
10
11
12
13

3 ) 如何获取到其它的日期信息

package main
import (
	"fmt"
	"time"
)
func main() {
	//看看日期和时间相关函数和方法使用
	//1. 获取当前时间
	now := time.Now()
	fmt.Printf("now=%v now type=%T\n", now, now)

	//2.通过now可以获取到年月日,时分秒
	fmt.Printf("年=%v\n", now.Year())
	fmt.Printf("月=%v\n", now.Month())
	fmt.Printf("月=%v\n", int(now.Month()))
	fmt.Printf("日=%v\n", now.Day())
	fmt.Printf("时=%v\n", now.Hour())
	fmt.Printf("分=%v\n", now.Minute())
	fmt.Printf("秒=%v\n", now.Second())
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

4 ) 格式化日期时间

方式 1 : 就是使用Printf 或者 SPrintf

package main
import (
	"fmt"
	"time"
)

func main() {
	//格式化日期时间
	fmt.Printf("当前年月日 %d-%d-%d %d:%d:%d \n", now.Year(), 
	now.Month(), now.Day(), now.Hour(), now.Minute(), now.Second())

	dateStr := fmt.Sprintf("当前年月日 %d-%d-%d %d:%d:%d \n", now.Year(), 
	now.Month(), now.Day(), now.Hour(), now.Minute(), now.Second())

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

方式二: 使用 time.Format() 方法完成:

package main
import (
	"fmt"
	"time"
)

func main() {
	//格式化日期时间的第二种方式
	fmt.Printf(now.Format("2006-01-02 15:04:05"))
	fmt.Println()
	fmt.Printf(now.Format("2006-01-02"))
	fmt.Println()
	fmt.Printf(now.Format("15:04:05"))
	fmt.Println()

	fmt.Printf(now.Format("2006"))
	fmt.Println()
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

对上面代码的说明:

" 2006./ 01 / 0215 : 04 : 05 " 这个字符串的各个数字是固定的,必须是这样写。

" 2006./ 01 / 0215 : 04 : 05 " 这个字符串各个数字可以自由的组合,这样可以按程序需求来返回时间和日期

5 ) 时间的常量

const(
 Nanosecond Duration= 1 //纳秒
 Microsecond = 1000 *Nanosecond //微秒
 Millisecond = 1000 *Microsecond//毫秒
 Second = 1000 *Millisecond//秒
 Minute = 60 *Second//分钟
 Hour = 60 *Minute//小时
)
1
2
3
4
5
6
7
8

常量的作用:在程序中可用于获取指定时间单位的时间,比如想得到 100 毫秒

100 *time.Millisecond
1

6.) 结合Sleep来使用一下时间常量

package main
import (
	"fmt"
	"time"
)

func main() {
	//需求,每隔1秒中打印一个数字,打印到100时就退出
	//需求2: 每隔0.1秒中打印一个数字,打印到100时就退出
	i := 0
	for {
		i++
		fmt.Println(i)
		//休眠
		//time.Sleep(time.Second)
		time.Sleep(time.Millisecond * 100)
		if i == 100 {
			break
		}
	}

	//Unix和UnixNano的使用
	fmt.Printf("unix时间戳=%v unixnano时间戳=%v\n", now.Unix(), now.UnixNano())

}
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

7 ) time的Unix和UnixNano的方法

image-20210113223340801

package main
import (
	"fmt"
	"time"
)

func main() {
	//Unix和UnixNano的使用
	fmt.Printf("unix时间戳=%v unixnano时间戳=%v\n", now.Unix(), now.UnixNano())
}
1
2
3
4
5
6
7
8
9
10

# 6.24.2 时间和日期的课堂练习

【点击观看视频】Go时间函数课堂练习

编写一段代码来统计 函数test 03 执行的时间

package main
import (
	"fmt"
	"time"
	"strconv"
)

func test03() {

	str := ""
	for i := 0; i < 100000; i++ {
		str += "hello" + strconv.Itoa(i)
	}
}

func main() {
	//在执行test03前,先获取到当前的unix时间戳
	start := time.Now().Unix()
	test03()
	end := time.Now().Unix()
	fmt.Printf("执行test03()耗费时间为%v秒\n", end-start)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 6.25 内置函数

【点击观看视频】Go内置函数(buildin)

# 6.25.1 说明:

Golang设计者为了编程方便,提供了一些函数,这些函数可以直接使用,我们称为Go的内置函数。文档:https://studygolang.com/pkgdoc->builtin

1 ) len:用来求长度,比如string、array、slice、map、channel

2 ) new:用来分配内存,主要用来分配值类型,比如int、float 32 ,struct返回的是指针

举例说明 new 的使用:

package main
import (
	"fmt"
)

func main() {

	num1 := 100
	fmt.Printf("num1的类型%T , num1的值=%v , num1的地址%v\n", num1, num1, &num1)

	num2 := new(int) // *int
	//num2的类型%T => *int
	//num2的值 = 地址 0xc04204c098 (这个地址是系统分配)
	//num2的地址%v = 地址 0xc04206a020  (这个地址是系统分配)
	//num2指向的值 = 100
	*num2  = 100
	fmt.Printf("num2的类型%T , num2的值=%v , num2的地址%v\n num2这个指针,指向的值=%v", 
		num2, num2, &num2, *num2)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

上面代码对应的内存分析图:

image-20210113223826843

3 ) make:用来分配内存,主要用来分配引用类型,比如channel、map、slice。这个我们后面讲解。

# 6.26 错误处理

【点击观看视频】Go错误处理机制

# 6.26.1 看一段代码,因此错误处理

package main
import (
	"fmt"
)

func test() {
	num1 := 10
	num2 := 0
	res := num1 / num2
	fmt.Println("res=", res)
}
func main() {
	//测试
	test()
	fmt.Println("main()下面的代码...")
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

对上面代码的总结

1 ) 在默认情况下,当发生错误后(panic),程序就会退出(崩溃.)

2 ) 如果我们希望:当发生错误后,可以捕获到错误,并进行处理,保证程序可以继续执行。还可以在捕获到错误后,给管理员一个提示(邮件,短信。。。)

3 ) 这里引出我们要将的错误处理机制

# 6.26.2 基本说明

1 ) Go语言追求简洁优雅,所以,Go语言不支持传统的 trycatchfinally 这种处理。

2 ) Go中引入的处理方式为: defer , panic , recover

3 ) 这几个异常的使用场景可以这么简单描述:Go中可以抛出一个panic的异常,然后在defer中通过recover捕获这个异常,然后正常处理

# 6.26.3 使用defer+recover来处理错误

package main
import (
	"fmt"
	"time"
)

func test() {
	//使用defer + recover 来捕获和处理异常
	defer func() {
		err := recover()  // recover()内置函数,可以捕获到异常
		if err != nil {  // 说明捕获到错误
			fmt.Println("err=", err)
		}
	}()
	num1 := 10
	num2 := 0
	res := num1 / num2
	fmt.Println("res=", res)
}

func main() {
	//测试
	test()
	for {
		fmt.Println("main()下面的代码...")
		time.Sleep(time.Second)
	}
}
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

# 6.26.4 错误处理的好处

进行错误处理后,程序不会轻易挂掉,如果加入预警代码,就可以让程序更加的健壮。看一个

案例演示:

package main
import (
	"fmt"
	"time"
)

func test() {
	//使用defer + recover 来捕获和处理异常
	defer func() {
		err := recover()  // recover()内置函数,可以捕获到异常
		if err != nil {  // 说明捕获到错误
			fmt.Println("err=", err)
			//这里就可以将错误信息发送给管理员....
			fmt.Println("发送邮件给admin@sohu.com~")
		}
	}()
	num1 := 10
	num2 := 0
	res := num1 / num2
	fmt.Println("res=", res)
}

func main() {
	//测试
	test()
	for {
		fmt.Println("main()下面的代码...")
		time.Sleep(time.Second)
	}
}
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

# 6.26.5 自定义错误

【点击观看视频】Go的自定义错误

# 6.26.6 自定义错误的介绍

Go程序中,也支持自定义错误, 使用errors.New 和 panic 内置函数。

1 ) errors.New("错误说明"), 会返回一个error类型的值,表示一个错误

2 ) panic内置函数 ,接收一个interface{}类型的值(也就是任何值了)作为参数。可以接收error类型的变量,输出错误信息,并退出程序.

# 6.26.7 案例说明

package main
import (
	"fmt"
	_ "time"
	"errors"
)
//函数去读取以配置文件init.conf的信息
//如果文件名传入不正确,我们就返回一个自定义的错误
func readConf(name string) (err error) {
	if name == "config.ini" {
		//读取...
		return nil
	} else {
		//返回一个自定义错误
		return errors.New("读取文件错误..")
	}
}

func test02() {

	err := readConf("config2.ini")
	if err != nil {
		//如果读取文件发送错误,就输出这个错误,并终止程序
		panic(err)
	}
	fmt.Println("test02()继续执行....")
}
	

func main() {
	//测试自定义错误的使用
	test02()
	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
23
24
25
26
27
28
29
30
31
32
33
34
【点击观看视频】Go函数课后练习布置
  • 6.1 为什么需要函数
  • 6.1.1 请大家完成这样一个需求:
  • 6.1.2 使用传统的方法解决
  • 6.2 函数的基本概念
  • 6.3 函数的基本语法
  • 6.4 快速入门案例
  • 6.5 包的引出
  • 6.6.包的原理图
  • 6.7 包的基本概念
  • 6.8 包的三大作用
  • 6.9 包的相关说明
  • 6.10 包使用的快速入门
  • 6.11 包使用的注意事项和细节讨论
  • 6.12 函数的调用机制
  • 6.12.1 通俗易懂的方式的理解
  • 6.12.2 函数-调用过程
  • 6.12.3 return语句
  • 6.13 函数的递归调用
  • 6.13.1 基本介绍
  • 6.13.2 递归调用快速入门
  • 6.13.3 递归调用的总结
  • 6.13.4 递归课堂练习题
  • 6.14 函数使用的注意事项和细节讨论
  • 6.15 函数的课堂练习
  • 6.16 INIT函数
  • 6.16.1 基本介绍
  • 6.16.2 案例说明:
  • 6.16.3 inti函数的注意事项和细节
  • 6.17 匿名函数
  • 6.17.1 介绍
  • 6.17.2 匿名函数使用方式 1
  • 6.17.3 匿名函数使用方式 2
  • 6.17.4 全局匿名函数
  • 6.18 闭包
  • 6.18.1 介绍
  • 6.18.2 案例演示:
  • 6.18.3 闭包的最佳实践
  • 6.19 函数的DEFER
  • 6.19.1 为什么需要defer
  • 6.19.2 快速入门案例
  • 6.19.3 defer的注意事项和细节
  • 6.19.4 defer的最佳实践
  • 6.20.函数参数传递方式
  • 6.20.1 基本介绍
  • 6.20.2 两种传递方式
  • 6.20.3 值类型和引用类型
  • 6.20.4 值传递和引用传递使用特点
  • 6.21 变量作用域
  • 6.21.1 变量作用域的课堂练习
  • 6.22 函数课堂练习(综合)
  • 6.23 字符串常用的系统函数
  • 6.24 时间和日期相关函数
  • 6.24.1 基本的介绍
  • 6.24.2 时间和日期的课堂练习
  • 6.25 内置函数
  • 6.25.1 说明:
  • 6.26 错误处理
  • 6.26.1 看一段代码,因此错误处理
  • 6.26.2 基本说明
  • 6.26.3 使用defer+recover来处理错误
  • 6.26.4 错误处理的好处
  • 6.26.5 自定义错误
  • 6.26.6 自定义错误的介绍
  • 6.26.7 案例说明