5-1 函数章节

函数:

package 包的引入

  • 包的本质就是不同的目录,每个文件必须属于一个包,也就是说 go 是以包来单位来进行组织和管理的。
  • 建议是将包名和目录名保持一致,而文件名则是自定义,但是每个文件都要写明自己属于哪个包。
  • 在一个包中引用其他包时,引用的路径是GO中的PATH路径下src 为相对路径的
  • 一个包引入其他包时是名字是 包名.变量或函数 (). 首字母的大小写,决定了是私有还是公有的变量或函数。
  • package  包名 ;用来打包 ; 而import "包名" 用来引用包 
  • 包可以取别名 ,但是一旦指定别名 就只能使用别名了

import时需要指明包的路径对应的目录,只是该目录才是包名,并非完整的路径是包名。

  • 同一个包下不能有相同名称的函数和变量名,要保持全局唯一 
  • 如果你要编译成一个可执行文件,则该包名必须是main ,但是如果想定义为一个库,则名字是任意的。

函数的特点

  • 基本数据类型都是在栈区,而引用类型一般是放在堆区,但是编译器存在一个逃逸分析,因此这不是绝对的。
  • 在执行main 函数时,在栈区开辟一块空间属于main ,而当main 函数调用其他函数时,会在堆区在单独开辟一个test 函数区
  • 此两个区是逻辑隔离的。test 区中函数中的变量不会影响到 main的变量。当test 函数执行完成后,编译器会回收该区域的空间
  • return 是可以返回多个函数值的,这一点其他函数的没有的。当有多个返回值时,为了保证先后的顺序,可以类似形参的方式来定义返回值
func sum(a int, b int) (sum1 int, sub int) {
	sum1 = a + b
	sub = a - b
	return
}
  • 函数名调用函数名后面的小括号不能省略,哪怕是没有形参也不能省略

函数的递归

斐波拉契数 1 1 2 3 5 8

func f1(n int) int {
	switch {
	case n == 1 || n == 2:
		return 1
	case n < 1:
		return 0
	default: // 1 从未知到已知,必须从大到小,从右到左,n = n-1 +n-2
		return f1(n-1) + f1(n-2)
	}

  • 已知f(10)=1 求f(1) , 从未知向已知,从左到右 f(n) 和f(n+1)的关系 则 f(10)=1 f(9)= (f(10)+1)*2 -> f(n)=(f(n+1)+1)*2 

 

---

init 函数 

  • 每个源文件都可以包含一个init 函数,init 函数在main 函数之前执行,但是在全局变量之后执行

  • import 引入其他包之后的 执行顺序: 其他包的 全局变量---init 函数 ----main包 全局变量---init 函数---main函数 - 其他函数的调用过程中 加载其他私有或公有函数

匿名函数 :没有名字的函数

  • 有时我们需要仅仅执行一次的函数可以用匿名函数来实现。//注意除了 全局匿名函数外,其他都是在main 函数中来定义和申明另一个函数
  • 匿名函数有三种表现形式 : 方式1 可以在main函数中 定义同时使用,(10,20) 就是其使用部分。
package main
import "fmt"
func main() {

	a := func(n1 int, n2 int) int {
		return n1 + n2
	}(10, 20) //(n1,n2) 是调用部分,调用够将其返回值赋给一个变量a

	fmt.Println("a = ", a)
  • 方式2 main 函数中将其赋个一个变量来多次使用。对比方式1 只是少了调用部分 。此时该变量b 就是一个函数类型
package main
import "fmt"
func main() {

	a := func(n1 int, n2 int) int {
		return n1 + n2
	}(10, 20) //(n1,n2) 是调用部分,调用够将其返回值赋给一个变量a

	b := func(n1 int, n2 int) int {
		return n1 + n2
	} //对比a 只是少了调用部分 ,但是此时b类似函数的名字。调用时需要b(10,20)来调用

	fmt.Println("a = ", a, "b = ", b(10, 20))
}
  • 无论是方式1 还是方式2 都是在函数main 中进行的,正常情况下不允许在一个函数中定义另一个函数,但是匿名函数是例外 
  • 因此如果想在一个函数中定义另一个函数则可以使用匿名函数。
  • 全局匿名函数 本质上就是定义一个全局变量 来将匿名函数赋予该变量。全局变量要求是公有的因此首字母必须是大写。
  • 因为 在函数外不允许使用  a := 10 的方式来定义变量,因此只是使用 var a = 10 的方式来定义。同理 全局匿名函数也是如此。
  • 全局匿名函数相对第二种方式只是1 用了一个全局变量 来接收,2 是在函数外 定义和赋值给全局变量的。其他都一模一样。

 闭包 :一个函数和其引用的环境变量合成的一个整体就是闭包。

package main
import "fmt"

func Add() func(int) int { //此时的返回值是一个函数。上面的函数类型的定义。
	var n2 int = 10       //函数体部分 第一部分是变量的赋值

	return func(n1 int) int { //第二部分是 返回了一个函数的整体,还是一个匿名函数。
		n2 += n1
		return n2
	}
}

func main() {
	f1 := Add()   // 函数的调用,必将返回一个完整的函数体。而在函数体中引用了一个变量,该变量和返回的函数整体构成了一个闭包
	fmt.Println(f1(1)) // f1(1)是 10+1=11
	fmt.Println(f1(2)) //  11 +2 =13 
}

 一次调用反复使用时,当多次使用时,并不会进行变量的初始化,只是在调用时执行一次,因此其值是累加的。

  • 可以理解为 闭包是一个类 ,返回的函数是一个类的方法,而变量的定义是一个类的字段

  • string(93)  是将数字转换为对应的ASSIC码 符号 ,字符串的值是可以修改的 
	var a string = "hello" //字符串的值是可以修改的
	a += "1"
	fmt.Println(a)
  • 函数做为返回值来使用时,典型特点是只需要传递一次,因此使用了匿名函数,返回其他函数时会语法错误。
package main

import (
	"fmt"
	"strings"
)

//此时的闭包是一个后缀变量 一个放回的函数整体。函数作为返回值时,只能使用匿名函数
func f1(suffix string) func(string) string { //一次传递多次调用,只需要传递一次 suffix ->.jpg
	return func(s2 string) string { // 就可以实现多次来判断是否 包含这个。如果是函数的方式来使用,需要传递
		//两个参数,后缀和文件名,每次调用都需要传递两个参数,但是闭包只需要传递一次后缀名。
		if !strings.HasSuffix(s2, suffix) {
			s2 += suffix
		}
		return s2
	}

}

func main() {
	a1 := f1(".jpg")
	fmt.Println(a1("22jpg"))
}
  • 闭包的好处是只需要传递一次 后缀的参数,如果要用函数调用的方式则每次都需要传递后缀的参数。闭包可以保留上次引入的某个值,一次传入多次使用

defer函数 

  • 在函数的使用过程中,需要创建资源(如创建mysql 的连接、文件的句柄、锁等),在函数执行完成之后,需要及时的释放资源,此时通过defer 机制来释放资源
  • defer 语句在函数中时,在执行到defer 语句时会被压入到栈中,【先进后出 】待函数调用完成后,在将栈中语句执行,先进后出

  • defer 就是函数调用完成之后执行的延迟语句。而本质上新建立一个栈将defer 语句压入栈中。
  • defer 语句在压入栈中是将其变量的值 也拷贝进入了栈中,也就是说后面的变量,对栈中的值是没有改变的
func sum(num1 int, num2 int) int {

	defer fmt.Println("step 1 ", num1) //先压入栈 ,此时的变量值 num1 =10 也被压入了栈中
	defer fmt.Println("step 2 ", num2) //先压入栈 ,此时的值 num2 =20 也被压入了栈中。
	num1++
	num2++
	sum := num1 + num2
	fmt.Println("step 3 ", sum)
	return sum
}

func main() {
	a1 := sum(10, 20)          //函数的调用,先执行3 ,2 ,1, 4
	fmt.Println("step 4 ", a1) //last
}
  • defer 的最佳实践就是 可以在资源打开的同时,defer 资源关闭 ,这样在函数调用完成后将从栈中自动关闭资源,而不用去关心什么时候回收资源
  • 伪代码如下

值传递和引用类型传递  

  • 函数中的形参中的变量在传递的过程中 有两种 分别是值传递和引用类型传递
  • 值传递的本质是 值的copy ,而引用类型的本质是地址的copy .当数据量比较大时,明显指针的效率要高
  • 值传递的数据类型有  基本数据类型  数组  结构体三种 简称 基组结 ,而引用类型有  指针 -- 切片--映射 --管道--接口 

函数的作用域

  • 在函数的内定义的变量 无论首字母是否大写,都不能被其他函数所引用。只有在 函数外定义的变量称为全局变量,首字母小写可以在整个包中引用,首字母大写则可以跨包使用。
  • if 和for 中定义的变量的作用域仅仅是在 if for 中才有效。他的作用域是代码块。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值