Go语言中的函数(重点)

Go语言中的函数

入门简单精通难

函数式编程

什么是函数

  • 函数就是一段代码的集合
  • go语言中至少要有一个 main函数
  • 函数需要有一个名字,独立定义的情况下。见名知意
  • 函数可能需要有一个结果,也可能没有
func printHelloWorld() {
   fmt.Println("Hello,world")
}

在这里插入图片描述

函数和方法完全是不一样的东西,面向对象里面才有方法

函数的具体定义

  • 无参无返回值
  • 有一个参数的函数
  • 有两个 or 多个参数的函数
  • 有一个返回值的函数
  • 有两个 or 多个返回值的函数
package main

import "fmt"

/*
*
- 无参无返回值
- 有一个参数的函数
- 有两个 or 多个参数的函数
- 有一个返回值的函数
- 有两个 or 多个返回值的函数
*/
func main() {
   m := max(123123123, 12312312312312)
   fmt.Println(m)
}

/*
func 函数名(参数1,参数2) 返回值类型 {
   // 代码逻辑

   return xxx
}
*/
// 定义一个稍微复杂一点的函数
// 比大小的函数 max ,两个数字比大小
// max函数,需要两个参数,int 类型的,比完大小之后我希望返回大的那一个数值
func max(num1 int, num2 int) int {
   var result int
   if num2 > num1 {
      result = num2
   } else {
      result = num1
   }
   // return返回结果
   return result
}

在这里插入图片描述

多个返回值(重点)

package main

import "fmt"

func main() {
   a, b := swap("feige", "kuangshen")
   fmt.Println(a, " ", b)
}

// 返回多个返回值的函数定义
// 案例:交换两个string
// 有多个返回值的情况下,返回值用括号括起来
func swap(x string, y string) (string, string) {
   return y, x
}

多个返回的案例

package main

import "fmt"

func main() {
	_, _, _, r4 := fun2(2, 4)
	fmt.Println(r4)
}

// 周长、面积案例  (长方形 ... 长、宽)
// 返回多个值,需要括号,需要表明返回值类型,返回值也可以命名
// return的结果值命名和定义函数返回值的命名无关.
func fun1(len, wid float64) (zc float64, area float64) {
	area = len * wid
	zc = (len + wid) * 2
	fmt.Println("zc:", zc)
	fmt.Println("area:", area)
	// 1、return 如果直接定义了,那么返回结果按照 return 的顺序
	// 2、直接调用return不带结果,那么则返回 函数返回值定义的顺序进行结果返回。
	return
}

func fun2(len, wid float64) (float64, float64, float64, float64) {
	area := len * wid
	zc := (len + wid) * 2
	fmt.Println("zc:", zc)
	fmt.Println("area:", area)
	// 1、return 如果直接定义了,那么返回结果按照 return 的顺序
	// 2、直接调用return不带结果,那么则返回 函数返回值定义的顺序进行结果返回。
	return area, zc, 1, 3
}

工具:怎么用看自己

可变参数

package main

import "fmt"

func main() {
   fmt.Println(getSum())
}

// 可变参数: 一个函数的参数类型确定,参数的个数不确定,可以使用可变参数
// 可变参数如果有多个参数必须放在最后一个参数

// 求和 , 参数是可变的,int
func getSum(nums ...int) int {
   sum := 0
   fmt.Println("可变参数的长度为:", len(nums))
   for i := 0; i < len(nums); i++ {
      fmt.Println("可变参数", i, ":", nums[i])
      // 取出来
      //sum = sum + nums[i]
      sum += nums[i]
   }
   return sum
}

// 接收多个参数 nums 可变参数
// 使用下标来接收,下标是从0开始的
// nums : 100,200,300,400,500
// nums[0] = 100
// nums[1] = 200
// nums[2] = 300

// 了解传递了多少个数字  len()函数,获取可变参数的长度

参数的传递细节(留空)

  • 引用传递 ( 切片 )
  • 值传递 ( 数组 )

函数中作用域

package main

import "fmt"

// 函数作用域
// 局部变量
// 1、函数内部定义的变量,只能在函数内部调用
// 全部变量(全局变量)
// 1、函数外部定义的变量,默认我们定义在上面,方便文件统一查看和管理全局变量
var num int = 1

func main() {
	temp := 100
	// 定义一个只在 if 中生效的变量 if 临时变量(a,b := 1,2);条件判断{}
	// 小作用域可以用到大作用域中的变量,反之则不行。
	// 对于很多一次性的变量,都可以这么写
	if a := 1; a <= 10 {
		fmt.Println(temp)
		fmt.Println(num)
		fmt.Println(a)
		// 就近原则
		if a := 2; a <= 10 {
			fmt.Println(a)
		}
	}
	fmt.Println(num)
}

func f1() {
	fmt.Println(num)
}
func f2() {

}

递归函数(小难点)

package main

import "fmt"

/*
定义:一个函数自己调用自己,就叫做递归函数
注意:递归函数需要有一个出口,逐渐向出口靠近,没有出口就会形成死循环。
*/
func main() {
	// overflows 栈溢出
	sum := getSum2(1000000000)
	fmt.Println(sum)
}
func getSum2(n int) int {
	fmt.Println(n)
	if n == 1 {
		return 1
	}
	return getSum2(n-1) + n
}

// 假设 getSum(5)

// 求和 sum  1 + 2 + 3 + 4 + 5
// getSum(5)☟=15
//      getSum(4)☟=10 + 5
//          getSum(3)☟=6 + 4
//               getSum(2)☟=3 + 3
// //                getSum(1)=1 + 2

在这里插入图片描述

defer 延迟函数

package main

import "fmt"

// defer
func main() {
   f("1")
   fmt.Println("2")
   defer f("3")
   fmt.Println("4")
}

func f(s string) {
   fmt.Println(s)
}

在这里插入图片描述

defer函数或者方法:一个函数或方法的执行被延迟了

  • 你可以在函数中添加多个defer语句当函数执行到最后时,这些defer语句会按照逆序执行,最后该函数返回,特别是当你在进行一些打开资源的操作时i/o 流,遇到错误需要提前返回,在返回前你需要关闭相应的资源,不然很容易造成资源泄露等问题
  • 如果有很多调用 defer,那么 defer 是采用 后进先出(栈) 模式。
    在这里插入图片描述
package main

import "fmt"

// defer 作用:处理一些善后的问题,比如错误,文件、网络流关闭等等操作。
// 特点,多个defer的问题
// 你可以在函数中添加多个defer语句,当函数执行到最后时,这些defer语句会按照逆序执行
func main() {
   f("1")
   defer fmt.Println("2")
   defer f("3")
   fmt.Println("4")
   defer f("5")
   fmt.Println("6")
   defer f("7")
   fmt.Println("8")
}

func f(s string) {
   fmt.Println(s)
}

defer存在传递参数:

package main

import "fmt"

// defer传参的调用时机
func main() {
   n := 10
   fmt.Println("main n=", n)
   // 在defer的时候,函数其实已经被调用了,但是没有执行。参数是已经传递进去的了
   defer ff(n) // 问题,defer延迟执行函数,参数时什么时候传递进去
   n++
   fmt.Println("main end n=", n)
}

func ff(n int) {
   fmt.Println("ff函数中n=", n)
}

defer在文件流之后进行关闭操作

文件.open()  二进制流 建立了连接
defer 文件.close() // 关闭文件

//读写操作
//.......

defer:程序会报错: 异常(程序执行的时候突然报错了)、错误(我们开发的时候知道的预期错误)

善后工作:defer 处理异常。

函数的数据类型

  • func (xxxx,xxx) xxx,xxxx
  • 函数也是一种数据类型,可以定义函数类型的变量
package main

import "fmt"

// 函数是什么(数据类型)
func main() {
	a := 10.01
	fmt.Printf("%T\n", a) // 查看变量的类型
	b := [4]int{1, 2, 3, 4}
	fmt.Printf("%T\n", b) // 查看变量的类型
	c := true
	fmt.Printf("%T\n", c) // 查看变量的类型

	// 函数的类型
	func1()                   // 带了括号是函数的调用
	fmt.Printf("%T\n", func1) // 查看变量的类型 func()
	fmt.Printf("%T\n", func2) // 查看变量的类型 func(int) int
	// func(int, int) (int, int)
	// func(int, int, ...string) (int, int)
	//var fun3 func(int, int, ...string) (int, int)
	fun3 := func2
	r1, r2 := fun3(1, 2, "111")
	fmt.Println(r1, r2)
	// 函数在Go语言中本身也是一个数据类型,加了() 是调用函数,不加(), 函数也是一个变量,可以赋值给别人。

	// 函数的类型就等于该函数创建的类型,他也可以赋值给
}

// 无参无返回值的函数
func func1() {

}

// 有参有返回值的函数
func func2(a, b int, c ...string) (int, int) {
	return 0, 0
}

函数的本质

函数在Go语言中不是一个简单的调用或者接收结果的。

函数在Go中是一个符合类型,可以看做是一个特殊的变量。var 定义吗,赋值。类型相同即可

函数类型的样子 :var f1 函数名(参数) 结果

变量名:指向一段内存 (num --> 0x11111aaaa)

函数名:指向一段函数体的内存地址,是一种特殊类型的变量。我们可以将一个函数赋值给另外一个类型相同的函数
在这里插入图片描述

匿名函数

package main

import "fmt"

// 匿名变量 (没有名字的变量)
// 匿名函数 (没有名字的函数)
func main() {
   // 正常的调用
   f12()
   f2 := f12 // 函数赋值给另外一个函数
   f2()
   // f12  f2 本质指向了同一个内存空间,空间中的代码一致  {fmt.Println("我是f12函数")}

   // 匿名函数,在函数体后增加(),调用了这个函数,匿名函数只能一次。
   func() {
      fmt.Println("我是一个匿名函数")
   }()

   // 将匿名函数进行赋值,就可以实现多次调用。
   f3 := func() {
      fmt.Println("我是一个匿名函数")
   }
   f3()

   // 匿名函数是否可以添加参数和返回值
   func(a, b int) {
      fmt.Println("a,b")
   }(1, 2)

   // 将匿名函数的返回值定义给变量。
   r1 := func(a, b int) int {
      return a + b
   }(1, 2)
   fmt.Println(r1)

   // 由于Go语言中的函数是一个特殊的变量,支持匿名操作
   // Go语言支持函数式编程
   // - 将匿名函数作为另外一个函数的参数,回调函数
   // - 将匿名函数作为另外一个函数的返回值,可以形成闭包结构

}
func f12() {
   fmt.Println("我是f12函数")
}

在这里插入图片描述

回调函数

高阶函数:可以将一个函数作为另外一个函数的参数。

fun1()

fun2(fun1)

fun1 函数作为 fun2 函数的参数

fun2函数,叫做高阶函数,接收了另外一个函数作为参数的函数

fun1函数,叫做回调函数,作为另外一个函数的参数

package main

import "fmt"

// 回调函数
func main() {
	// 函数调用
	r1 := add(1, 2)
	fmt.Println(r1)
	// 高阶函数调用
	r2 := oper(1, 2, add)
	fmt.Println(r2)
	r3 := oper(1, 2, sub)
	fmt.Println(r3)
	// 匿名函数
	fun1 := func(a, b int) int {
		return a * b
	}
	r4 := oper(1, 2, fun1) // 调用匿名函数 *
	fmt.Println(r4)
	// 能够直接传递匿名函数
	r5 := oper(1, 2, func(a int, b int) int {
		if b == 0 {
			fmt.Println("除数不能为0")
			return 0
		}
		return a / b
	})
	fmt.Println(r5)
}

// 运算 (运算的数字,运算操作)
// 高阶函数,参数是接收另外一个函数
func oper(a, b int, fun func(int, int) int) int {
	fmt.Println(a, b, fun)
	r := fun(a, b)
	return r
}

func add(a, b int) int {
	return a + b
}
func sub(a, b int) int {
	return a - b
}

闭包

一个外层函数中,有内层函数,该内层函数中,会操作外层函数的局部变量
并且该外层函数的返回值就是这个内层函数。
这个内层函数和外层函数的局部变量,统称为闭包结构。

局部变量的生命周期就会发生改变,正常的局部变量会随着函数的调用而创建,随着函数的结束而销毁
但是闭包结构中的外层函数的局部变量并不会随着外层函数的结束而销毁,因为内层函数还在继续使用

package main

import "fmt"

// 是一种特殊的结构:闭包结构,违反了程序正常的生命周期。合法的使用。程序允许的一种特殊结构,变量作用域升级了。

// 什么时候用闭包: js (xxxxxxx.html   引用大量的第三方库:10个js库,js库中很多变量名是冲突的)
// js 很多框架都是闭包结构的,防止变量冲突,全局变量污染

// 我的代码里面的变量就不会和你代码里面的变量冲突了。解决一些变量作用域冲突的问题。

/*
闭包结构:
一个外层函数中,有内层函数,该内层函数中,会操作外层函数的局部变量并且该外层函数的返回值就是这个内层函数。

在闭包结构中:局部变量的生命周期就会发生改变,
正常的局部变量会随着函数的调用而创建,随着函数的结束而销毁
但是闭包结构中的外层函数的局部变量并不会随着外层函数的结束而销毁,因为内层函数还在继续使用.

// 由于垃圾回收期不会将闭包中的变量销毁,可能会造成内存泄漏。
*/

// 你的代码变量和你同事的变量冲突了,解决。 i 新建一个变量。 第三方库中的代码都是闭包结构实现的导出。
var i int = 10

func main() {
	r1 := increment()
	fmt.Println(r1) // 返回的是一个 increment() 内存函数,还没有执行
	// -- 执行这个内层函数
	//
	v1 := r1()
	fmt.Println(v1)
	v2 := r1()
	fmt.Println(v2)
	fmt.Println(r1())
	fmt.Println(r1())
	fmt.Println(r1())
	// 你写的代码是对的,但是结果不对,你的变量被污染了
	fmt.Println("--------------------------")

	// r2和r1指向同一个地址
	r2 := increment() // 再次调用的时候 ,i = 0
	v3 := r2()
	fmt.Println(v3) // 1

	//因为我们内层还是用i,还存在引用,系统不会销货这个i,保护,单独作用r1
	fmt.Println(r1()) // 6 	// 这里的i 并没有随着 第二次创建就被销毁归0,而是在内层函数继续调用着。
	fmt.Println(r2()) // 2
	// r1 名字 ----> 内存地址 &r1
	fmt.Printf("%p\n", &r1)
	fmt.Printf("%p\n", &r2)

}

// 自增函数
// increment() 函数返回值为  func() int 类型
func increment() func() int { // 外层函数,项目(很多的全局变量)
	// 定义一个局部变量
	i := 0
	// 在外层函数内部定义一个匿名函数,给变量自增并返回。
	fun := func() int {
		i++
		return i
	}
	return fun
}

如果我们想使用闭包结构来解决全局变量污染的问题,那我们就可以写一个闭包结构来创建执行的函数。

通过这个闭包结构创建的函数内部的变量,都在这个函数中作用,不会和其他函数冲突。

**闭包结果的返回值是一个函数。**这个函数可以调用闭包结构中的变量。

拓展 函数中的参数传递

函数的参数传递中。存在的问题:值传递、引用传递。

按照数据类型存储特点

值类型:int、string、bool、float64、array… 拷贝,创建的时候,拷贝一份

引用类型:操作的是数据的地址,切片slice、map、chan…
在这里插入图片描述

package main

import "fmt"

// 参数传递中值类型、引用类型的问题
func main() {
   // 定义一个数组
   arr1 := [4]int{1, 2, 3, 4}
   fmt.Println("arr1修改前的数据:", arr1) // 1 2 3 4
   update(arr1)
   fmt.Println("arr1修改前的数据:", arr1) // 1 2 3 4
   // 数组当做参数来传递的时候,参数是对当前数组做了一个拷贝。值传递类型

}

// 更新数组
func update(arr2 [4]int) {
   fmt.Println("arr2修改前的数据:", arr2) // 1 2 3 4
   arr2[0] = 100
   fmt.Println("arr2修改后的数据:", arr2) // 100 2 3 4
}

引用传递

package main

import "fmt"

// 参数传递中值类型、引用类型的问题
func main() {
   // 定义一个切片
   s1 := []int{1, 2, 3, 4}
   fmt.Println("s1修改前的数据:", s1) // 1 2 3 4
   updatee(s1)
   fmt.Println("s1修改后的数据:", s1) // 100 2 3 4
   // 如果参数是引用类型的,那么修改函数内的值,会影响函数外的值。因为两个变量指向同一个内存空间,修改任意一个都会导致另外一个发送变化
   
   // 值传递   每个变量参数都有自己的内存空间,拷贝
   // 引用传递 每个变量参数都指向同一个内存空间,指向,改变任意一个,其他的都会发生变化。

}

// 更新数组
func updatee(s2 []int) {
   fmt.Println("s2修改前的数据:", s2) // 1 2 3 4
   s2[0] = 100
   fmt.Println("s2修改后的数据:", s2) // 100 2 3 4
}

arr2修改前的数据:", arr2) // 1 2 3 4
arr2[0] = 100
fmt.Println(“arr2修改后的数据:”, arr2) // 100 2 3 4
}




**引用传递**

```go
package main

import "fmt"

// 参数传递中值类型、引用类型的问题
func main() {
   // 定义一个切片
   s1 := []int{1, 2, 3, 4}
   fmt.Println("s1修改前的数据:", s1) // 1 2 3 4
   updatee(s1)
   fmt.Println("s1修改后的数据:", s1) // 100 2 3 4
   // 如果参数是引用类型的,那么修改函数内的值,会影响函数外的值。因为两个变量指向同一个内存空间,修改任意一个都会导致另外一个发送变化
   
   // 值传递   每个变量参数都有自己的内存空间,拷贝
   // 引用传递 每个变量参数都指向同一个内存空间,指向,改变任意一个,其他的都会发生变化。

}

// 更新数组
func updatee(s2 []int) {
   fmt.Println("s2修改前的数据:", s2) // 1 2 3 4
   s2[0] = 100
   fmt.Println("s2修改后的数据:", s2) // 100 2 3 4
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值