## **函数的基本概念**
为完成某一功能的程序指令(语句)的集合,称为函数。
在 Go 中,函数分为: 自定义函数、系统函数(查看 Go 编程手册)
![](https://img.kancloud.cn/b2/cb/b2cb8c4b6749cdbbb036311c64172a21_644x260.png)
![](https://img.kancloud.cn/ea/1a/ea1a2cdf5dfb9bf55299ac484056e761_864x466.png)
#### **包的引出**
1) 在实际的开发中,我们往往需要在不同的文件中,去调用其它文件的定义的函数,比如 main.go 中,去使用 utils.go 文件中的函数,如何实现? \-》包
2) 现在有两个程序员共同开发一个 Go 项目,程序员 xiaoming 希望定义函数 Cal ,程序员 xiaoqiang 也想定义函数也叫 Cal。两个程序员为此还吵了起来,怎么办? -》包
包的本质实际上就是创建不同的文件夹,来存放程序文件。
![](https://img.kancloud.cn/52/c9/52c9c235b7c7d7288aa85aae449596cc_538x415.png)
#### **包的基本概念**
说明:go 的每一个文件都是属于一个包的,也就是说 go 是以包的形式来管理文件和项目目录结构 的
#### **包的三大作用**
区分相同名字的函数、变量等标识符
当程序文件很多时,可以很好的管理项目
控制函数、变量等访问范围,即作用域
#### **包的相关说明**
打包基本语法
package 包名
引入包的基本语法
import "包的路径"
包快速入门
-Go 相互调用函数,我们将 func Cal 定义到文件 utils.go , 将 utils.go 放到一个包中,当 其它文件需要使用到 utils.go 的方法时,可以 import 该包,就可以使用了. 【为演示:新建项目目录结 构】
![](https://img.kancloud.cn/20/74/207467d3e8e61c98d23a2d123b79a137_680x857.png)
![](https://img.kancloud.cn/ec/c9/ecc9220f3dcc57147f3a11292684e2db_807x474.png)
#### **包使用的注意事项和细节讨论**
1) 在给一个文件打包时,该包对应一个文件夹,比如这里的 utils 文件夹对应的包名就是 utils, 文件的包名**通常**和文件所在的文件夹名一致,**一般**为小写字母。
2) 当一个文件要使用其它包函数或变量时,需要先引入对应的包
引入方式 1:import "包名"
引入方式 2:
```
import (
"包名"
"包名"
)
```
package 指令在 文件第一行,然后是 import 指令。
在 import 包时,路径从 $GOPATH 的 src 下开始,不用带 src , 编译器会自动从 src 下开始引入
3) 为了让其它包的文件,可以访问到本包的函数,则该函数名的**首字母需要大写**,类似其它语言 的 public ,这样才能跨包访问。比如 utils.go 的
4) 在访问其它包函数,变量时,其语法是 包名.函数名, 比如这里的 main.go 文件中
5) 如果包名较长,Go 支持给包取别名, 注意细节:取别名后,原来的包名就不能使用了
说明: 如果给包取了别名,则需要使用别名来访问该包的函数和变量
6) 在同一包下,不能有相同的函数名(也不能有相同的全局变量名),否则报重复定义
7) 如果你要编译成一个可执行程序文件,就需要将这个包声明为 main , 即 package main .这个就 是一个语法规范,如果你是写一个库 ,包名可以自定义
![](https://img.kancloud.cn/5c/25/5c25e473f495f6193f53dc8576fd5b94_984x310.png)
## **函数的调用机制**
![](https://img.kancloud.cn/d1/e5/d1e5b9d2fac95e5a8453b4405f2a0388_965x512.png)
(1) 在调用一个函数时,会给该函数分配一个新的空间,编译器会通过自身的处理让这个新的空间和其它的栈的空间区分开来
(2) 在每个函数对应的栈中,数据空间是独立的,不会混淆
(3) 当一个函数调用完毕(执行完毕)后,程序会销毁这个函数对应的栈空间。
#### **return 语句**
![](https://img.kancloud.cn/a3/15/a315d7ee46acff06bc68053b55f45f75_992x253.png)
#### **函数的递归调用**
![](https://img.kancloud.cn/1b/9f/1b9fa2085c0a670b8a445dee436835cd_749x477.png)
![](https://img.kancloud.cn/7c/7a/7c7a364b8547c17e9c2f1c2a018bfc28_732x402.png)
![](https://img.kancloud.cn/90/f9/90f9f338b2e9a3b0a80b023ed560c822_704x402.png)
![](https://img.kancloud.cn/2b/07/2b07344115f16ef03ce10fdf00e0c028_734x423.png)
**函数递归需要遵守的重要原则**
1) 执行一个函数时,就创建一个新的受保护的独立空间(新函数栈)
2) 函数的局部变量是独立的,不会相互影响
3) 递归必须向退出递归的条件逼近,否则就是无限递归,死龟了:)
4) 当一个函数执行完毕,或者遇到 return,就会返回,遵守谁调用,就将结果返回给谁,同时当函数执行完毕或者返回时,该函数本身也会被系统销毁
**函数使用的注意事项和细节讨论**
1) 函数的形参列表可以是多个,返回值列表也可以是多个。
2) 形参列表和返回值列表的数据类型可以是值类型和引用类型。
3) 函数的命名遵循标识符命名规范,首字母不能是数字,首字母大写该函数可以被本包文件和其 它包文件使用,类似 public , 首字母小写,只能被本包文件使用,其它包文件不能使用,类似 privat
4) 函数中的变量是局部的,函数外不生效【案例说明】
5) **基本数据类型和数组**默认都是值传递的,即进行值拷贝。在函数内修改,不会影响到原来的值。
![](https://img.kancloud.cn/48/29/4829ed0306a54113ff880e4d09aa37cf_782x333.png)
6) 如果希望函数内的变量能修改函数外的变量(**指的是默认以值传递的方式的数据类型)**,可以传 入变量的地址&,函数内以指针的方式操作变量。从效果上看类似引用 。
![](https://img.kancloud.cn/62/56/6256860721c020a9c8d71768811c63e3_943x431.png)
7) Go 函数不支持函数重载(同一个函数根据形参的个数和类型返回不同的参数)
![](https://img.kancloud.cn/83/f8/83f862317edc0d8aee16aabea3a7a202_693x275.png)
8) 在 Go 中,函数也是一种数据类型,可以赋值给一个变量,则该变量就是一个函数类型的变量 了。通过该变量可以对函数调用
![](https://img.kancloud.cn/d9/7c/d97cf968b9c429a80a6d219e0e5d4766_762x343.png)
9) 函数既然是一种数据类型,因此在 Go 中,函数可以作为形参,并且调用
![](https://img.kancloud.cn/5b/26/5b262d5b5894f00e85495a97c384cb3a_770x242.png)
10) 为了简化数据类型定义,Go 支持自定义数据类型
基本语法:type 自定义数据类型名 数据类型 // 理解: 相当于一个别名
案例:type myInt int // 这时 myInt 就等价 int 来使用了.
![](https://img.kancloud.cn/4c/c2/4cc22dba259b2418dd9afaba4fced289_815x607.png)
11) 支持对函数返回值命名
![](https://img.kancloud.cn/47/33/4733deb059efa175170d343cbc3727e9_721x324.png)
12) 使用 _ 标识符,忽略返回值
13) Go 支持可变参数
![](https://img.kancloud.cn/26/71/2671b02fd2147183817ddd97d900d03f_940x386.png)
![](https://img.kancloud.cn/dd/93/dd938d8848c7754e488e9e70366350c6_843x380.png)
### **init 函数**
每一个源文件都可以包含一个 init 函数,该函数会在 main 函数执行前,被 Go 运行框架调用,也 就是说 init 会在 main 函数前被调用。
**inti 函数的注意事项和细节**
1) 如果一个文件同时包含全局变量定义,init 函数和 main 函数,则执行的流程全局变量定义\->init 函数\->main 函数
2) init 函数最主要的作用,就是完成一些初始化的工作,比如下面的案例
3) 细节说明: 面试题:案例如果 main.go 和 utils.go 都含有 变量定义,init 函数时,执行的流程 又是怎么样的呢?
![](https://img.kancloud.cn/71/db/71db28f492ca0a90ee476ef3b6552b73_980x325.png)
**匿名函数**
Go 支持匿名函数,匿名函数就是没有名字的函数,如果我们某个函数只是希望使用一次,可以考 虑使用匿名函数,匿名函数也可以实现多次调用。
匿名函数使用方式 1:
在定义匿名函数时就直接调用,这种方式匿名函数只能调用一次。
![](https://img.kancloud.cn/2f/a7/2fa718befc7aca0921303d8685b33b31_817x320.png)
匿名函数使用方式 2:
将匿名函数赋给一个变量(函数变量),再通过该变量来调用匿名函数 【案例演示】
![](https://img.kancloud.cn/09/61/0961249c147a7fd5fd0a3ae03173e91b_747x297.png)
**全局匿名函数**
如果将匿名函数赋给一个全局变量,那么这个匿名函数,就成为一个全局匿名函数,可以在程序 有效。
![](https://img.kancloud.cn/4f/91/4f91632ca61bde24cda5afe23dec2fc4_758x295.png)
### **闭包**
基本介绍:闭包就是一个函数和与其相关的引用环境组合的一个整体(实体)
![](https://img.kancloud.cn/3c/bf/3cbfb667d5b1998ca90ca889c6cf1d4a_846x467.png)
1) AddUpper 是一个函数,返回的数据类型是 fun (int) int
2) 闭包的说明
![](https://img.kancloud.cn/fb/6c/fb6c687b56b2bb2dc76dbb25d5d58c93_331x132.png)
返回的是一个匿名函数, 但是这个匿名函数引用到函数外的 n ,因此这个匿名函数就和 n 形成一个整体,构成闭包。
3) 大家可以这样理解: 闭包是类, 函数是操作,n 是字段。函数和它使用到 n 构成闭包。
4) 当我们反复的调用 f 函数时,因为 n 是初始化一次,因此每调用一次就进行累计。
5) 我们要搞清楚闭包的关键,就是要分析出返回的函数它使用(引用)到哪些变量,因为函数和它引 用到的变量共同构成闭包。
6) 对上面代码的一个修改,加深对闭包的理解
![](https://img.kancloud.cn/e7/6c/e76cebbaae6bcb59e1f54e89f5b54093_819x496.png)
#### **闭包的最佳实践**
1) 编写一个函数 makeSuffix(suffix string) 可以接收一个文件后缀名(比如.jpg),并返回一个闭包 2) 调用闭包,可以传入一个文件名,如果该文件名没有指定的后缀(比如.jpg) ,则返回 文件名.jpg , 如果已经有.jpg 后缀,则返回原文件名。
3) 要求使用闭包的方式完4) strings.HasSuffix , 该函数可以判断某个字符串是否有指定的后缀。
![](https://img.kancloud.cn/4f/cc/4fcc13c12173c8792eee605d1f32a688_927x492.png)
1) 返回的匿名函数和 makeSuffix (suffix string) 的 suffix 变量 组合成一个闭包,因为 返回的函数引用 到 suffix 这个变量
2) 我们体会一下闭包的好处,如果使用传统的方法,也可以轻松实现这个功能,但是传统方法需要每 次都传入 后缀名,比如 .jpg ,而闭包因为可以保留上次引用的某个值,所以我们传入一次就可以反复 使用。大家可以仔细的体会一把!
#### **函数的 defer**
在函数中,程序员经常需要创建资源(比如:数据库连接、文件句柄、锁等) ,为了在函数执行完 毕后,及时的释放资源,Go 的设计者提供 defer (延时机制)。
![](https://img.kancloud.cn/dc/6c/dc6cdc7055155df4483004e39d433935_777x414.png)
1) 当 go 执行到一个 defer 时,不会立即执行 defer 后的语句,而是将 defer 后的语句压入到一个栈 中\[我为了讲课方便,暂时称该栈为 defer 栈\], 然后继续执行函数下一个语句。
2) 当函数执行完毕后,在从 defer 栈中,依次从栈顶取出语句执行(注:遵守栈 先入后出的机制),所以同学们看到前面案例输出的顺序。
3) 在 defer 将语句放入到栈时,也会将相关的值拷贝同时入栈。请看一段代码:
**defer的最佳实践**
defer 最主要的价值是在,当函数执行完毕后,可以及时的释放函数创建的资源。看下模拟代码。。
![](https://img.kancloud.cn/92/0d/920d98df760d381f89ae0bfeccc1d64a_938x183.png)
1) 在 golang 编程中的通常做法是,创建资源后,比如(打开了文件,获取了数据库的链接,或者是 锁资源), 可以执行 defer file.Close() defer connect.Close()
2) 在 defer 后,可以继续使用创建资源.
3) 当函数完毕后,系统会依次从 defer 栈中,取出语句,关闭资源.
4) 这种机制,非常简洁,程序员不用再为在什么时机关闭资源而烦心。
#### **函数参数传递方式**
1) 值传递 2) 引用传递
其实,不管是值传递还是引用传递,传递给函数的都是变量的副本,不同的是,值传递的是值的 拷贝,引用传递的是地址的拷贝,一般来说,地址拷贝效率高,因为数据量小,而值拷贝决定拷贝的 数据大小,数据越大,效率越低。
**值类型和引用类型**
1) 值类型:基本数据类型 int 系列, float 系列, bool, string 、数组和结构体 struct
2) 引用类型:指针、slice 切片、map、管道 chan、interface 等都是引用类型
**值传递和引用传递使用特点**
一下讲过重新回顾
![](https://img.kancloud.cn/e8/1c/e81c38fb62e98bbcc28761cb26b00204_1112x531.png)
![](https://img.kancloud.cn/cb/d9/cbd99e6083cc36c57b462e8d96ec07f0_1099x481.png)
#### **变量作用域**
1) 函数内部声明/定义的变量叫局部变量,作用域仅限于函数内部
2) 函数外部声明/定义的变量叫全局变量,作用域在整个包都有效,如果其首字母为大写,则作用 域在整个程序有效
3) 如果变量是在一个代码块,比如 for / if 中,那么这个变量的的作用域就在该代码块
![](https://img.kancloud.cn/c3/c4/c3c42bd9e1dd68ea3c91d65443bee46e_723x406.png)
![](https://img.kancloud.cn/27/14/2714ce9856a971b0b2e3b0f11f3e83c8_852x240.png)
#### **字符串常用的系统函数**
1) 统计字符串的长度,按字节 len(str)
![](https://img.kancloud.cn/8e/eb/8eeb6ad7a26e331109b880bee6452d74_718x146.png)
2) 字符串遍历,同时处理有中文的问题 r := []rune(str)
![](https://img.kancloud.cn/bb/6e/bb6e68ed170964d02339833dd237013f_716x182.png)
3) 字符串转整数: n, err := strconv.Atoi("12")
![](https://img.kancloud.cn/95/78/9578b11377f885f6200656a958e6fe8a_708x199.png)
4) 整数转字符串 str = strconv.Itoa(12345)
![](https://img.kancloud.cn/21/f3/21f3cf35bad1b338923368dbe68b0095_691x94.png)
5) 字符串 转 \[\]byte: var bytes = []byte("hello go")
![](https://img.kancloud.cn/a7/a6/a7a6618ee0acc541d4eeeb6b8fd4a8d4_683x94.png)
6) \[\]byte 转 字符串: str = string(\[\]byte{97, 98, 99})
![](https://img.kancloud.cn/88/53/885396329b5bf86c7e72211fe0910f09_699x92.png)
7) 10 进制转 2, 8, 16 进制: str = strconv.FormatInt(123, 2) // 2-> 8 , 16
![](https://img.kancloud.cn/13/65/1365383170220b5fccb9609567811b1f_709x122.png)
8) 查找子串是否在指定的字符串中: strings.Contains("seafood", "foo") //true
![](https://img.kancloud.cn/ee/04/ee04455d525e9508a34b370279f391d6_763x87.png)
9) 统计一个字符串有几个指定的子串 : strings.Count("ceheese", "e") //4
![](https://img.kancloud.cn/c6/56/c656b9c268a06f88dcab2ab009a3dfcd_766x89.png)
10) 不区分大小写的字符串比较(==是区分字母大小写的): fmt.Println(strings.EqualFold("abc","Abc")) // true
![](https://img.kancloud.cn/50/df/50df902baa225f4c53bd6584529204cd_753x177.png)
11) 返回子串在字符串第一次出现的 index 值,如果没有返回\-1 : strings.Index("NLT\_abc", "abc") // 4
![](https://img.kancloud.cn/ba/fa/bafa3f4b4628b1b92fe99aca6e52a381_753x126.png)
12) 返回子串在字符串最后一次出现的 index,如没有返回\-1 : strings.LastIndex("go golang", "go")
![](https://img.kancloud.cn/da/90/da90d3bbc08ce5b2d7b8f0ef364fc731_758x151.png)
13) 将指定的子串替换成 另外一个子串: strings.Replace("go go hello", "go", "go 语言", n) n 可以指定你希望替换几个,如果 n=-1 表示全部替换
![](https://img.kancloud.cn/c0/69/c06935ece8843691b55a0889dc2cd78a_782x161.png)
14) 按 照 指 定 的 某 个 字 符 , 为 分 割 标 识 , 将 一 个 字 符 串 拆 分 成 字 符 串 数 组 :strings.Split("hello,wrold,ok", ",")
![](https://img.kancloud.cn/9c/75/9c752c66d6d90e6a0f737e379845d18e_700x194.png)
15) 将字符串的字母进行大小写的转换: strings.ToLower("Go") // go strings.ToUpper("Go") // GO
![](https://img.kancloud.cn/83/8e/838ee28a3384c2c852cb96c81f78f7c1_715x164.png)
16) 将字符串左右两边的空格去掉: strings.TrimSpace(" tn a lone gopher ntrn ")
![](https://img.kancloud.cn/0c/c0/0cc0250d7840b078fe3a8cdb39dfb028_728x70.png)
17) 将字符串左右两边指定的字符去掉 : strings.Trim("! hello! ", " !") // \["hello"\] //将左右两边 !和 " "去掉
![](https://img.kancloud.cn/72/8e/728e7ff31ff8d59fee25213f56e7fe4d_751x108.png)
18) 将字符串左边指定的字符去掉 : strings.TrimLeft("! hello! ", " !") // \["hello"\] //将左边 ! 和 " "去掉
19) 将字符串右边指定的字符去掉 :strings.TrimRight("! hello! ", " !") // \["hello"\] //将右边 ! 和 " "去掉
20) 判断字符串是否以指定的字符串开头: strings.HasPrefix("ftp://192.168.10.1", "ftp") // true
![](https://img.kancloud.cn/4f/12/4f1212395510fa9991f356b8ca9a21ff_824x144.png)
21) 判断字符串是否以指定的字符串结束: strings.HasSuffix("NLT\_abc.jpg", "abc") //false
#### **时间和日期相关函数**
1) 时间和日期相关函数,需要导入 time 包
2) time.Time 类型,用于表示时间
![](https://img.kancloud.cn/e5/10/e510740747d8b8c3db77fe6e0ad89ce2_934x285.png)
3) 如何获取到其它的日期信息
![](https://img.kancloud.cn/18/7c/187c91b4d20724370819e074fac7e50f_909x565.png)
4) 格式化日期时间
方式 1: 就是使用 Printf 或者 SPrintf
![](https://img.kancloud.cn/12/0e/120e347c8672e769abe8ba07f25c2a44_865x239.png)
方式二: 使用 time.Format() 方法完成
![](https://img.kancloud.cn/e0/f2/e0f21e4312933a48fafd5437cd70177c_816x219.png)
5) 时间的常量
![](https://img.kancloud.cn/02/77/02774340d138308359d0cb415ac180b8_747x384.png)
6) 结合 Sleep 来使用一下时间常量
![](https://img.kancloud.cn/20/ef/20efc4be2666758c7a3b25901398b630_811x350.png)
7) time 的 Unix 和 UnixNano 的方法
![](https://img.kancloud.cn/bc/da/bcda82cae8d4371c0f346f5b58483467_848x555.png)
#### **内置函数**
Golang 设计者为了编程方便,提供了一些函数,这些函数可以直接使用,我们称为 Go 的内置函 数。文档:https://studygolang.com/pkgdoc -> builtin
1) len:用来求长度,比如 string、array、slice、map、channel 2) new:**用来分配内存,主要用来分配值类型**,比如 int、float32,struct...返回的是指针 new
![](https://img.kancloud.cn/46/1e/461e69a4191c11c2e9219f3980521bbe_798x317.png)
![](https://img.kancloud.cn/f9/5d/f95dda59a10acc3e7152ba4f01771471_921x214.png)
3) make:用来分配内存,**主要用来分配引用类型**,比如 channel、map、slice。这个我们后面讲解
#### **错误处理**
1) Go 语言追求简洁优雅,所以,Go 语言不支持传统的 try…catch…finally 这种处理。
2) Go 中引入的处理方式为:defer, panic, recover
![](https://img.kancloud.cn/35/eb/35eb58c1820c2220058c16e22239898b_931x425.png)
3) 这几个异常的使用场景可以这么简单描述:Go 中可以抛出一个 panic 的异常,然后在 defer 中 通过 recover 捕获这个异常,然后正常处理
**自定义错误**
Go程序中,也支持自定义错误,使用errors.New和panic内置函数。
1)errors.New("错误说明"),会返回一个error类型的值,表示一个错误
2)panic内置函数,接收一个interface{}类型的值(也就是任何值了)作为参数。可以接收error类型的变量,输出错误信息,并退出程序.
![](https://img.kancloud.cn/e2/6b/e26b0de9ab709851345aebaafc76c53e_749x772.png)