第10节 Go 中的函数

164 阅读5分钟

1. 函数的定义

Go中的函数分为:自定义函数系统函数

  • 函数的基本语法
func 函数名称 (形参列表) (返回值列表) {
    逻辑语句
    return 返回值列表
}
  • 示例
package main

import "fmt"

func main() {
	var a = 1
	var b = 2

	sum := add(a , b)
	fmt.Printf("a+b=%v\n",sum)
}

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

2. 函数传参方式

  包括 值传递引用传递。它们传递个函数的都是变量的副本,不同的是,值传递的是值的拷贝引用传递的是变量的内存地址拷贝

  • 值类型:基本数据类型 int 系列、float 系列、bool、string、数组 和 结构体 struct
  • 引用类型:指针、切片、map、管道、interface 等引用类型

image.png

3. 匿名函数

Go 支持匿名函数,匿名函数就是没有名字的函数

3.1 匿名函数方式一

  • 定义匿名函数是就直接调用,但它只能被调用一次
package main

import "fmt"

func main() {
        // 匿名函数
	result := func (n1 int, n2 int) int {
		return n1 + n2
	}(1, 2) // 匿名函数传参
	fmt.Printf("n1+n2=%v\n",result)
}

3.2 匿名函数方式二

package main

import "fmt"

func main() {
	add := func (n1 int, n2 int) int {
		return n1 + n2
	}

	fmt.Printf("n1+n2=%v\n", add(1,2))
	fmt.Printf("n1+n2=%v\n", add(3,4))
}        

3.3 全局匿名函数

package main

import "fmt"
// 全局匿名函数
var (
	testAdd = func(n1 int, n2 int) int {
		return n1 + n2
	}
)

func main() {
    res := testAdd(2, 4)
    fmt.Printf("n1+n2=%v\n", res)
}

4. init 函数

每一个go文件都可以包含一个init函数,该函数会在main函数执行前被调用!主要是完成一些初始化工作,比如配置文件加载等

package main

import "fmt"

func init() {
	fmt.Println("调用init函数!")
}

func main() {
	fmt.Println("执行main方法业务逻辑!")
}

image.png

  • 如果一个文件同时包含 全局变量定义init函数main函数,则执行顺序是:全局变量定义 --> init函数 --> main函数
package main

import "fmt"

var global = test()

func test() int {
	fmt.Println("全局变量")
	return 88
}

func init() {
	fmt.Println("调用init函数!")
}

func main() {
	fmt.Println("执行main方法业务逻辑!")
}

image.png

  • 在 main.go 和 utils.go 中都有 全局变量定义init函数main函数,则在 main.go 中

image.png

5. 内置函数

golang 中提供了一些内置函数,方便开发者使用

  • func len : 用来求长度,使用与 字符串、数组、切片、map、管道
func len(v Type) int
  • func new :用来分配值类型的内存地址,比如 int、float32、struct 等,返回的是指针
func new(Type) *Type

案例

package main

import "fmt"

func main() {
	n := 1
	// n的类型int ,n的值=1, n的内存地址=0xc00012a008
	fmt.Printf("n的类型%T ,n的值=%v, n的内存地址=%v\n", n, n, &n)

	n2 := new(int) // 返回值是 *int,即是一个 int 型指针 

	// 修改指针的值
	*n2 = 2
	// n2的类型*int ,n2的值=0xc00012a020, n2的内存地址=0xc000124020, n2指针指向的值=2
	fmt.Printf("n2的类型%T ,n2的值=%v, n2的内存地址=%v, n2指针指向的值=%v\n", n2, n2, &n2, *n2)
}

掘金-第 8 页.drawio (4).png

  • func make :用来分配引用类型的内存地址,比如 map、管道、切片
  • 其他的内置函数
func delete(m map[Type]Type1, key Type)

func copy(dst, src []Type) int

func append(slice []Type, elems ...Type) []Type

func close(c chan<- Type)

6. 函数使用细节

  • 函数的形参列表可以是多个,返回值列表也可以是多个
  • 函数首字母不能是数字,首字母大写的函数可以被当前 package 中文件和其他 package 中的文件使用。首字母小写的只能被当前 package 中的文件引用
  • 基本数据类型数组 的形参都是值传递,在函数内部修改参数,不会影响到原来的值
package main

import "fmt"

func add(n1 int) {
	n1 = n1 + 10
	fmt.Printf("n1=%v\n",n1)
}

func main() {
	num := 20
	add(num)
        // num 的值不会被改变
	fmt.Printf("num=%v\n",num)
}

image.png

如果要改变 num 的值该怎么办?可以传入变量的地址 &

package main

import "fmt"

func add(n1 *int) {
	*n1 = *n1 + 10
	fmt.Printf("n1=%v\n",n1)
}

func main() {
	num := 20
	add(&num)
	fmt.Printf("num=%v\n",num)
}

为什么能实现这个功能? 因为 num 和 n1 指向的都是同一个内存地址 image.png

  • Go中的函数不支持重载
  • Go中函数也是一种数据类型,可以赋值给一个变量,通过该变量可以对函数调用
package main

import "fmt"

func sub(n1 int, n2 int) int {
	return n2 - n1
}

func main() {
        // 函数赋值给一个变量 
	a := sub
	fmt.Printf("a的类型=%T, sub的类型=%T\n",a, sub)
        // 通过变量调用函数
	res := a(2, 10)
	fmt.Printf("res=%v\n",res)
}

image.png

  • Go中函数既然是一种变量,那么它就可以作为函数的形参进行传递并调用
package main

import "fmt"

func add(n1 int, n2 int) int{
	return n1 + n2
}

// 可以接受函数形参
func myFun(funvar func(int,int) int, n1 int, n2 int) int{
	return funvar(n1,n2)
}

func main() {
	res := myFun(add, 3, 5)
	// res=8
	fmt.Printf("res=%v\n", res)
}
  • Go 中支持对函数返回值命名
// 返回值的名称分表是 f 和 err
func ParseFloat(s string, bitSize int) (f float64, err error)
  • Go 中使用 _ 标识符忽略函数的返回值
  • Go 中支持可变参数
func add(args... int) int {
}

func sum(n1 int, args... int) int {
}

示例:

package main

import "fmt"

func main() {
	// 多参数传递
	res := add(1, 10, 20, 30)
	// res=61
	fmt.Printf("res=%v\n",res)
}

func add(n1 int, args... int) int {
	sum := n1
	for i := 0; i < len(args); i++ {
		sum += args[i]
	}
	return sum
}

7.闭包

闭包就是一个函数和*该函数相关的引用**组成的一个整体。举个例子:

package main

import (
	"fmt"
)

// Go中函数也是数据类型,所以 add 函数的返回值也是函数,即:func(int) int
func add() func(int) int {
	var n int = 1
	var str = "hello"
	// 返回了一个匿名函数
	return func(x int)  int {
		n = n + x

		str += "a"
		fmt.Println("str=",str)
		return n
	}
}

func main() {
	f := add()
	// 匿名函数的调用方式:通过函数变量来调用
	fmt.Println(f(1))  // 2
	fmt.Println(f(2))  // 4
}

image.png

  • 匿名函数引用到了匿名函数外的 n 变量,因此匿名函数和 n 就形成了一个整体,叫做闭包
// Go中函数也是数据类型,所以 add 函数的返回值也是函数,即:func(int) int
func add() func(int) int {
	var n int = 1
	// 返回了一个匿名函数
	return func(x int)  int {
		n = n + x
		return n
	}
}
  • 需要注意一点,这段代码里变量 nstr 都只被初始化了一次,所以看到了上图所示的 n 的累加输出结果
  • 对闭包可以换个角度理解:闭包是Java中的类,n 是类的属性,函数是类的方法,这样好理解对 n 的累加操作

7.1 使用场景

  • 实现一个功能,给所有的文件名统一加后缀,比如.jpg
    • 传入的文件名可以带有.jpg,也可以不带有.jpg
    • 最终要求返回统一的文件名称
package main

import (
	"fmt"
	"strings"
)

func main() {
	f := appendFileName(".jpg")
	fmt.Println("文件处理后的名称=", f("world"))
	fmt.Println("文件处理后的名称=", f("hello.jpg"))
}

func appendFileName(suffix string) func (string) string {
	return func (name string) string {
		// 如果 name 没有指定后缀,则加上并返回
		if(!strings.HasSuffix(name, suffix)) {
			return name + suffix
		}
		return name
	}
}