本章纲要
一、基本语法
func 函数名(形参列表)(返回值列表){
执行语句,表示函数的输入
return 返回列表
}
案例
/* 函数返回两个数的最大值 */
func max(num1 int, num2 int) int {
/* 声明局部变量 */
var result int
if (num1 > num2) {
result = num1
} else {
result = num2
}
return result
}
二、包的引出和使用原理
- 包的引出:把自定义函数统一放在一个或几个文件中调用 例如文件:utils.go 让其它文件调用
bd.go专门定义对数据库的操作函数…
①解决同一文件使用不同包的相同函数名函数
②使项目更加简洁
③控制函数的访问范围即作用域 - 包的基本概念:go的每个文件都属于一个包,go是以包的形式来管理文件好项目目录结构
- 打包基本语法
package 包名
- 引包
import "包路径"
从之前安装定义的代码工作路径往下写,从src开始定位
5. 调用
包名.函数名()
函数名跨包使用函数名首字母要大写,因为小写代表私用
三、包使用快速入门
- 工程目录示例
main.go
package main
import (
"fmt"
_ "fmt"
"remoteProjects/基本语法/Chapter04/projectShow/utils"
)
func main() {
var num1 int = 64
var num2 int = 65
result := utils.Max(num1,num2)
fmt.Printf("%v",result)
}
util.go
package utils
import (
_ "fmt"
)
func Max(num1 int, num2 int) int {
/* 声明局部变量 */
var result int
if (num1 > num2) {
result = num1
} else {
result = num2
}
return result
}
2. 注意点及其细节
(1)错误:go run: cannot run non-main package
https://blog.csdn.net/cainiaobulan/article/details/100900267
(2)通常文件夹名要和包名相对于,比较容易引包
使用的时候引入地址是文件夹,但是在使用函数时是要包名
(3)打包指令放在第一行,在import
(4)写路径时不用写src,因为默认从那里开始
(5)引入还可以取别名,但是以后就要用新的了
utils "remoteProjects/基本语法/Chapter04/projectShow/utils"
(6)编译可执行文件时,就需要把这个包申明为main,即package main,只能有一个main文件,如果使用一个库,则包名可以自定义。一个项目只能有一个main,
在项目路径GOPATH下,不需要src ,编译时去到编译main包所在的文件夹
E:\go\Go_Work>go build -o bin\my.exe remoteProjects\基本语法\Chapter04\projectShow\main
(7)在同一包下不可有同一函数,即使是在同一文件夹下不同文件也是不可以的
四、函数调用机制底层剖析
- 代码底层
(1)在调用一个函数时,回个函数分配一个新的空间,编译器会通过自身的处理让这个新的空间和其他栈空间分开
(2)在函数的每个对应的栈中,数据是空的,不会混淆
(3)当一个函数调用完毕(执行完毕后)程序会销毁这个函数对应的栈空间 - return
Go函数支持返回多个值
func 函数名 (形参列表)(返回值列表){
语句...
return 返回值列表
}
func getSumAndSub(n1 int,n2 int)(int,int){
...
}
接收方式案例:
res1, res2 := getSumAndSub(1,2)
(1)返回多个值在接收时,如果想要忽略某个值,可以用 _ 符号表示占位忽略
函数方式案例:
_, res2 := getSumAndSub(1,2)
(2)如果返回值只有一个,那么返回值列表()的括号可以不写
五、函数的递归调用
- 一个函数在函数体内有调用了自己本身
func test(n int){
if n>2{
n--
test(n)
}
fmt.Println("n=",n)
}
六、函数的细节以及注意事项
- 传参
基本数据类型和数组都是默认值传递,进行拷贝,不会影响原来的变量值,如果要改变原来的值,可以进行传入变量地址&的方式 - Go函数不支持重载
- 函数本身也是一种数据变量,可以赋值给一个变量,则该变量就是一个函数类型的数据变量了,通过该变量可以进行函数的调用。
func GetSum(n1 int,n2 int)int{
return funvar(num1,num2)
}
- 函数可以作为形参被调用
func myFun(funvar fun(int,int) int,num1 int, num2 int){
return funvar(num1,num2)
}
整体函数:
package main
import "fmt"
func getSum(n1 int,n2 int)int{
//return funvar(num1,num2)
return n1+n2
}
//funvar随便取,定义形参类型为fun(int,int) int,因为getSum的函数类型是这个
func myFun(funvar func(int, int) int, num1 int, num2 int) int {
return funvar(num1,num2)
}
func main(){
a:=getSum
fmt.Printf("a的类型%T,getSum的类型%T\n",a,getSum)
res:=a(10,40)
fmt.Println("res=",res)
res2 := myFun(getSum,50,60)
fmt.Println("res2=",res2)
}
- Go可以自定义数据类型:
可理解为取别名
基本语法: type 自定义数据类型名 数据类型
案例 type myInt int
案例 typr mySum func(int,int)int
- 支持对返回值命名
func getSumSub(n1 int n2 int)(sum int sub int){
sum = n1 + n2
sub = n1- n2
return
}
- 支持可变参数
//0到多
func sum(args... int)sum int{
}
//1到多
func sum(n1 int, args...int)sum int{
}
说明:
(1)args[]是slice 切片,通过args[index]可以访问到多个值
(2)案例演示:编写一个函数sum,可以求出1到多个int 的和
(3)args也可以使用其他名称,比如vars
(4)可变参数要放在形参后面如:
func sum(n1 int, args...int)sum int{
}
七、init()函数
- 说明:一般可用于程序开始执行之前对程序的初始化操作
- 执行顺序:全局变量——>init()函数——>主函数
如果有引包,那么就是 引包[的全局变量——>init()函数——>主函数]——>全局变量——>init()函数——>主函数 - 举例说明
文件结构
utils.go
package utils
import (
"fmt"
_ "fmt"
)
var Age int
var Name string
//Age和Name都是全局变量,我们需要在main.go中使用
//但是,我们需要初始化Age和Name
func init(){
Age = 100
Name = "tom"
fmt.Println("utils的init函数")
}
main.go
package main
import (
"fmt"
"remoteProjects/基本语法/Chapter04/funcdemo02/utils"
_ "remoteProjects/基本语法/Chapter04/funcdemo02/utils"
//引包时要从src后开始引包
)
//初始化程序
func init(){
fmt.Println("init()")
}
//全局变量
var age = test()
func test() int{
fmt.Println("test()")
return 90
}
//主函数
func main() {
fmt.Println("main()...",age)
fmt.Println("Age=", utils.Age,"Name=", utils.Name)
八、匿名函数
- 说明:匿名函数即没有名字的函数,可以一次调用,也可以多次调用
- 一次调用:
//在调用时就直接调用,只调用一次
res1 := func(n1 int, n2 int) int {
return n1 + n2
}(10, 20)
fmt.Println("result=", res1)
- 多次调用:
//将匿名函数赋给一个变量,多次调用
a := func(n1 int, n2 int) int {
return n1 - n2
}
res2 := a(10, 30)
fmt.Println("result2=", res2)
- 全局匿名函数:
package main
import(
"fmt"
)
//全局匿名函数
var (
//fun1就是一个全局匿名函数
fun1 = func(n1 int, n2 int) int {
return n1 * n2
}
)
func main(){
//全局匿名函数
res3 := fun1(3, 4)
fmt.Println("result3=", res3)
}
- 总代码:
package main
import (
"fmt"
)
//全局匿名函数
var (
//fun1就是一个全局匿名函数
fun1 = func(n1 int, n2 int) int {
return n1 * n2
}
)
func main() {
//在调用时就直接调用,只调用一次
res1 := func(n1 int, n2 int) int {
return n1 + n2
}(10, 20)
fmt.Println("result=", res1)
//将匿名函数赋给一个变量,多次调用
a := func(n1 int, n2 int) int {
return n1 - n2
}
res2 := a(10, 30)
fmt.Println("result2=", res2)
//全局匿名函数
res3 := fun1(3, 4)
fmt.Println("result3=", res3)
}
九、闭包
- 说明:闭包就是一个函数与其相关的环境所组成的一个整体(实体)
- 累加器案例:
package main
import (
"fmt"
"strings"
)
//累加器
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))
fmt.Println(f(2))
}
结果:
3. 上例说明:
1)AddUpper是一个函数,返回的类型是func(int)int
2)闭包的说明
var n int =10
return func(x int) int {
n=n + x
return n
}
返回的是一个函数,但是这个匿名函数引用到函数外的n,匿名函数和n即形成一个整体,构成一个闭包。
3)可以理解为:闭包是一个类,函数是操作,n是字段。当我们反复调用函数 f 时,n是初始化一次,因此每调用一次就进行累积
4)关键是分析清楚使用(引用)了哪些变量,因为函数和它引用到的变量共同构成闭包
4.练习:判断输入文件名是否有制定后缀,没有则加上,有就原样返回。
说明:使用函数 strings.HasSuffix(name,suffix) 返回值为布尔值
package main
import (
"fmt"
"strings"
)
//判断文件名是否有制定后缀
func makeSuffix(suffix string) func (string) string{
return func (name string) string {
//如果没有制定后缀名,没有则加上
if strings.HasSuffix(name,suffix) == false{
return name + suffix
}
return name
}
}
func main() {
//测试makeSuffix
//返回一个闭包
f2:=makeSuffix(".jpg")
fmt.Println("文件名处理后",f2("winter"))
fmt.Println("文件名处理后",f2("summer"))
fmt.Println("文件名处理后",f2("spring.jpg"))
}
测试结果:
5.练习说明
1)返回的匿名函数和makeSuffix(suffix string) 的suffix变量组合成为一个闭包,因为返回的函数引用到suffix这个变量
2)使用传统方法也可以做到,但是需要每次都传入.jpg的值,使用 闭包只用传入参数一次
十、函数中的defer
1、说明
在函数中,程序员通常需要创建资源(比如文件句柄、锁、连接数据库等),为了在函数执行完毕后,及时地释放资源,Go提供了defer(延时机制)
2、快速入门
1)说明
当函数执行到defer时,暂时不执行,会将defer后面的语句压入到独立的栈中(defer栈),当函数执行完毕后,再从defer栈,按照先入后出的方式出栈
package main
import (
"fmt"
)
func sum(n1 int,n2 int) int {
defer fmt.Println("ok1 n1=",n1) //defer 3. ok1 n1=1
defer fmt.Println("ok2 n2=",n2) //defer 2. ok2 n2=20
res :=n1+n2
fmt.Println("ok3 res=",res) //1. ok3 res=30
return res
}
func main(){
sum(10,20)
}
3、defer细节说明
1)在defer 将语句压入到栈时,也会将相关的值(此处是n1=10,n2=20)拷贝同时入栈
如将上述函数加上两行代码
func sum(n1 int,n2 int) int {
defer fmt.Println("ok1 n1=",n1) //defer 3. ok1 n1=10
defer fmt.Println("ok2 n2=",n2) //defer 2. ok2 n2=20
n1++ //**************************
n2++ //**************************
res :=n1+n2 //此时res=32 ,但是上面defer语句还是输出10和20
fmt.Println("ok3 res=",res) //1. ok3 res=30
return res
}
输出结果:
4、最佳实践
1)创建资源后(打开文件或者数据库或者创建锁资源)就defer file.close()…
2)在defer后,可以继续使用创建资源
3)当创建完毕后,系统会依次从defer栈中,取出语句,关闭资源
4)这种机制非常简洁,程序员不用再为什么时候关闭资源而费心
十一、常用字符串函数 21个(系统函数)
https://blog.csdn.net/qq_43681877/article/details/104083633
十二、常用时间和日期的函数
https://blog.csdn.net/qq_43681877/article/details/104083959
十三、内置函数
https://blog.csdn.net/qq_43681877/article/details/104084162
十四、golang中错误处理机制
1、引言
2、对上面代码的总结
(1)在默认情况下,当发生错误时(panic),程序就默认退出(崩溃)
(2)如果我们希望当错误发生后,可以捕获到错误,并进行处理,保证程序可以继续执行,还可以再捕获到错误后,给管理员一个提示,例如邮件或者短信
3、错误处理机制
(1)Go中引入的错误处理方式为:defer、panic、recover
(2)这几个异常的使用场景可以这么简单描述:Go中可以抛出一个panic异常,然后在defer中通过这个recover捕获这个异常,然后再正常处理
package main
import (
"fmt"
)
func test(){
//使用defer + recover来捕获和处理异常
defer func() { //匿名函数
err := recover() //内置函数捕获异常
if err != nil{ //说明捕获到异常
//也可以 if err := recover();err != nil{
fmt.Println("err=",err)
//这里就可以将错误信息发送到管理员
}
}()
num1 := 10
num2 :=0
res :=num1 / num2
fmt.Println("res=",res)
}
func main() {
//测试
test()
fmt.Printf("main()后面的代码")
}
4、自定义错误
(1)errors.New(“错误说明”),会返回一个error类型的值,表示一个错误
(2)panic内置函数,接收一个interface{}类型(空接口类型)的值(也就是任何值了)作为参数,可以接受error类型的变量,输出错误信息,并退出程序。
(3)案例
package main
import (
"errors"
"fmt"
)
//函数去读取一个配置文件信息init.conf的信息
//如果文件名不正确,我们就返回一个自定义错误
func readConf(name string)(err error) {
if name == "config.ini"{
return nil
} else{
return errors.New("读取文件错误")
}
}
func test02() {
err := readConf("config.inio") //*******************************
if err != nil{
//读取文件发生错误,就输出这个错误并终止程序
panic(err)
}
fmt.Println("test02继续执行")
}
func main() {
//测试自定义错误
test02()
fmt.Printf("main()后面的代码")
}
十五、总结
1、函数参数传递的方式
(1)值传递类型
1)基本数据类型
2)数组
3)结构体
(2)引用类型
1)指针
2)slice切片
3)map
4)管道chanel
5)接口interface
2、变量作用域
(1)局部函数
函数内部定义就是局部变量,作用域仅限于函数内部
(2)全局变量
函数外部定义的就是全局变量,作用域在整个包都有效,如果首字母大写,那么作用域在整个程序都有效
(3)如果变量是在一个代码块,比如 for / if 中,那么变量作用域就在该代码块中