Chapter007 Golang函数

上一章 Chapter006 Golang程序流程控制
下一章 Chapter008 golang中数组和切片

一、基本语法
func 函数名(形参列表)(返回值列表){
	执行语句,表示函数的输入
	return 返回列表
}

案例

/* 函数返回两个数的最大值 */
func max(num1 int, num2 int) int {
   /* 声明局部变量 */
   var result int

   if (num1 > num2) {
      result = num1
   } else {
      result = num2
   }
   return result 
}
二、包的引出和使用原理
  1. 包的引出:把自定义函数统一放在一个或几个文件中调用 例如文件:utils.go 让其它文件调用
    bd.go专门定义对数据库的操作函数…
    ①解决同一文件使用不同包的相同函数名函数
    ②使项目更加简洁
    ③控制函数的访问范围即作用域
  2. 包的基本概念:go的每个文件都属于一个包,go是以包的形式来管理文件好项目目录结构
  3. 打包基本语法
package 包名
  1. 引包
import "包路径"

从之前安装定义的代码工作路径往下写,从src开始定位
5. 调用

包名.函数名()

函数名跨包使用函数名首字母要大写,因为小写代表私用

三、包使用快速入门
  1. 工程目录示例
    在这里插入图片描述
    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. 代码底层
    在这里插入图片描述
    (1)在调用一个函数时,回个函数分配一个新的空间,编译器会通过自身的处理让这个新的空间和其他栈空间分开
    (2)在函数的每个对应的栈中,数据是空的,不会混淆
    (3)当一个函数调用完毕(执行完毕后)程序会销毁这个函数对应的栈空间
  2. return
    Go函数支持返回多个值
func 函数名 (形参列表)(返回值列表){
	语句...
	return 返回值列表
}
func getSumAndSub(n1 int,n2 int)(int,int){
	...
}

接收方式案例:

 res1, res2 := getSumAndSub(1,2)

(1)返回多个值在接收时,如果想要忽略某个值,可以用 _ 符号表示占位忽略
函数方式案例:

 _, res2 := getSumAndSub(1,2)

(2)如果返回值只有一个,那么返回值列表()的括号可以不写

五、函数的递归调用
  1. 一个函数在函数体内有调用了自己本身
func test(n int){
	if n>2{
		n--
		test(n)
	}
	fmt.Println("n=",n)
}
六、函数的细节以及注意事项
  1. 传参
    基本数据类型和数组都是默认值传递,进行拷贝,不会影响原来的变量值,如果要改变原来的值,可以进行传入变量地址&的方式
  2. Go函数不支持重载
  3. 函数本身也是一种数据变量,可以赋值给一个变量,则该变量就是一个函数类型的数据变量了,通过该变量可以进行函数的调用。
func GetSum(n1 int,n2 int)int{
	return funvar(num1,num2)
}
  1. 函数可以作为形参被调用
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)
}

在这里插入图片描述

  1. Go可以自定义数据类型:
    可理解为取别名
基本语法: type 自定义数据类型名 数据类型
案例 type myInt int
案例 typr mySum func(int,int)int
  1. 支持对返回值命名
func getSumSub(n1 int n2 int)(sum int sub int){
	sum = n1 + n2
	sub = n1- n2
	return 
}
  1. 支持可变参数
//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()函数
  1. 说明:一般可用于程序开始执行之前对程序的初始化操作
  2. 执行顺序:全局变量——>init()函数——>主函数
    如果有引包,那么就是 引包[的全局变量——>init()函数——>主函数]——>全局变量——>init()函数——>主函数
  3. 举例说明
    文件结构
    在这里插入图片描述
    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)
	
八、匿名函数
  1. 说明:匿名函数即没有名字的函数,可以一次调用,也可以多次调用
  2. 一次调用:
//在调用时就直接调用,只调用一次
	res1 := func(n1 int, n2 int) int {
		return n1 + n2
	}(10, 20)
	fmt.Println("result=", res1)
  1. 多次调用:
//将匿名函数赋给一个变量,多次调用
	a := func(n1 int, n2 int) int {
		return n1 - n2
	}
	res2 := a(10, 30)
	fmt.Println("result2=", res2)
  1. 全局匿名函数:
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)
}
  1. 总代码:
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)
}

九、闭包
  1. 说明:闭包就是一个函数与其相关的环境所组成的一个整体(实体)
  2. 累加器案例:
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 中,那么变量作用域就在该代码块中

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

杰西啊杰西

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值