Go语言函数

函数

入门简单精通难

函数式编程

1、什么是函数

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

import "fmt"

func main() {
	// 调用函数,一定要加()
	print()
}

// 定义函数
/*
func 函数名(参数1,参数2,...) {
	// 函数的集合,一段代码的集合
	// 封装的思维,代码多了我们将一些功能代码抽离出来,方便复用
}
*/
func print() {
	fmt.Println("hello")
}

2、函数的具体定义

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

import "fmt"


func main() {
	// 函数调用
    a := max(113017245, 24560445)
	fmt.Println(a)
}


// 函数定义,两个参数,一个返回值
func max(num1, num2 int) int {
	if num1 > num2 {
		return num1
	} else {
		return num2
	}
}
 
// 多个返回值
package main

import "fmt"

func main() {
	zc, area := fun1(1, 2)
	fmt.Println(zc, area)
}

func fun1(len, wid float64) (float64, float64) {
	area := len * wid
	zc := (len + wid) * 2
	return zc, area
}

// 结合匿名函数使用
package main

import "fmt"

func main() {
	a, b := swap("abc", "def")
	fmt.Printf("a=%s,b=%s\n", a, b)
	c, _ := swap("123", "456")
	fmt.Printf("c=%s\n", c)
	_, d := swap("aaa", "bbb")
	fmt.Printf("d=%s", d)
}

func swap(a, b string) (string, string) {
	return b, a
}

3、可变参数

package main

import "fmt"

func main() {
	sum := getSum(112, 23, 45, 5, 2, 3, 4)
	fmt.Printf("sum=%d\n", sum)
}

func getSum(nums ...int) int {
	// 可变参数: 一个函数的参数类型确定,参数的个数不确定,可以使用可变参数
	// 可变参数如果有多个参数必须放在最后一个参数
	sum := 0
	for i := 0; i < len(nums); i++ {
		sum += nums[i]
	}
	return sum
}

4、函数中作用域

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() {

}

5、递归函数

package main

import "fmt"

func main() {
	/*
		定义:一个函数自己调用自己,就叫做递归函数
		注意:递归函数需要有一个出口,逐渐向出口靠近,没有出口就会形成死循环。
	*/
	sum := getsum(5)
	fmt.Printf("%d\n", sum)
}

func getsum(i int) int {
	sum := 0
	if i == 1 {
		sum = 1
	} else {
		sum = i + getsum(i-1)
	}
	return sum
}

6、defer延迟函数

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

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

import "fmt"

func main() {
	// defer  主要是用来处理善后的任务
	// 特点:多个defer,最后倒序输出
	f("1")
	fmt.Println("2")
	defer f("3")
	f("4")
	defer f("5")
	f("6")
	defer f("7")

}

func f(s string) {
	fmt.Println(s)
}
// 结果:
1
2
4
6
7
5
3
// defer传参的调用时机
package main

import "fmt"

func main() {
	n := 10
	fmt.Printf("main中的n=%d\n", n)
	defer f2(n) // 在此时函数已经被调用了,参数已经传递进去了,但是没有执行
	n++
	fmt.Printf("main中的n=%d\n", n)
}

func f2(n int) {
	fmt.Printf("函数中的n=%d\n", n)
}

// 结果:
main中的n=10
main中的n=11
函数中的n=10

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

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

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

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

善后工作:defer 处理异常。

7、函数的数据类型

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

import "fmt"

func main() {
	fmt.Printf("f3:%T\n", f3) // f3:func()
	fmt.Printf("f4:%T\n", f4) // f4:func(int) int
	// 函数在Go语言中本身也是一个数据类型,加了() 是调用函数,不加(), 函数也是一个变量,可以赋值给别人。
	// 函数的类型就等于该函数创建的类型,他也可以赋值给
	f5 := f4
	fmt.Println(f5(3))
	// var f6 func(int,int) int
}

func f3() {

}

func f4(a int) int {
	return a
}

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

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

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

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

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

8、匿名函数

package main

import (
	"fmt"
)

// 匿名函数:没有名字的函数
func main() {
	f10()
	f2 := f10
	f2()
	// 匿名函数理论上只能执行一次
	func() {
		fmt.Println("wo shi niming hanshu")
	}()

	// 将匿名函数进行赋值,就可以实现多次调用了
	f3 := func() {
		fmt.Println("wo shi ni ming han shu")
	}
	f3()

	// 匿名函数添加参数
	func(a, b int) {
		fmt.Printf("%d,%d\n", a, b)
	}(1, 2)

	// 将匿名函数的返回值给一个变量

	// 由于Go语言中的函数是一个特殊的变量,支持匿名操作
	// Go语言支持函数式编程
	a := func(a, b int) int {
		return a + b
	}(1, 2)
	fmt.Println(a)
}

func f10() {
	fmt.Println("wo shi f10")
}

9、回调函数

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

fun1()

fun2(fun1)

fun1 函数作为 fun2 函数的参数

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

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

package main

import "fmt"

func main() {
	r1 := yunsuan(2, 4, add)
	fmt.Println(r1)
	sub := func(a, b int) int {
		return a - b
	}

	// 将匿名函数作为函数参数
	r2 := yunsuan(4, 2, sub)
	fmt.Println(r2)

	// 能够直接传递匿名函数
	r3 := yunsuan(6, 2, func(a int, b int) int {
		if b == 0 {
			fmt.Println("除数不能为0")
			return 0
		}
		return a / b
	})
	fmt.Println(r3)
}

// 高阶函数
func yunsuan(a, b int, fun func(int, int) int) int {
	r := fun(a, b)
	return r
}

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

10、闭包函数

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

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

package main

import "fmt"

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

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

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

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

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

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

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

func main() {
	r1 := incre() // 返回的是一个 increment() 内存函数,还没有执行
	fmt.Println(r1())
	fmt.Println(r1())
	fmt.Println(r1())
	fmt.Println("---------------------------------")
	r2 := incre()
	fmt.Println(r2())
	fmt.Println(r1())
	// 这里的i 并没有随着 第二次创建就被销毁归0,而是在内层函数继续调用着。
	fmt.Println(r2())
}

func incre() func() int {
	i := 0
	fun := func() int {
		i++
		return i
	}
	return fun
}

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

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

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

11、函数中的参数传递

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

按照数据类型存储特点

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

引用类型:操作的是数据的地址,切片slice、map、chan.....

(1)、值传递

package main

import "fmt"

func main() {

	arr1 := [4]int{1, 2, 3, 4}
	fmt.Println("arr1修改之前的数据:", arr1)
	update(arr1)
	fmt.Println("arr1修改之后的数据:", arr1)
}

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

(2)、参数传递

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
}
posted @ 2024-11-13 21:18  Linux小菜鸟  阅读(9)  评论(0编辑  收藏  举报