8、函数

函数

函数构成了代码执行的逻辑结构,在Go语言中,函数的基本组成为:关键字 func、函数名、参数列表、返回值、函数体、返回语句,每一个程序都包含很多的函数,函数是基本的代码块。

因为Go语言是编译型语言,所以函数编写的顺序是无关紧要的,鉴于可读性的需求,最好把main()函数写在文件的前面,其他函数按照一定逻辑顺序进行编写(例如函数被调用的顺序)。

Go语言里面拥三种类型的函数:

普通函数

定义

函数声明包括函数名参数列表返回值列表(可省略)以及函数体

func 函数名 (参数列表) (返回值列表) {
    return 函数体
}

如果一个函数在声明时,包含返回值列表,那么该函数必须以 return 语句结尾,除非函数明显无法运行到结尾处。

在函数中,实参通过值传递的方式进行传递,因此函数的形参是实参的拷贝,对形参进行修改不会影响实参,但是,如果实参包括引用类型,如指针、slice(切片)、map、function、channel 等类型,实参可能会由于函数的间接引用被修改。

参数列表

同类型参数

多个同类型参数可以省略前面的参数的类型声明,此时的参数a类型为string,参数b、c类型为int

func funcname(a string, b, c int) {}

可变参数

本质上是一个切片,也就是[]int,可变参数必须位于参数列表的最后

func funcname(args ...int) {}

我们也可以使用slice...的语法将一个切片传给可变参数的函数

package main

import "fmt"

func main() {
	s := []string{"lucy", "tom"}
	multiParam(s...)
}

func multiParam(args ...string) {
	fmt.Println(args)
}

返回值

注意:在函数有多个返回值时,只要有一个返回值有指定命名,其他的也必须有命名。如果返回值有有多个返回值必须加上括号; 如果只有一个返回值并且有命名也需要加上括号。

package main

import "fmt"

func main() {
	fmt.Println(add(1, 2)) // 3
	fmt.Println(red(1, 2)) // -1
	x, y := getPoint()
	fmt.Println(x, y) //52 13
}

func add(a int, b int) int {
	return a + b
}
// 函数声明时,可以给返回值声明一个变量,在函数体内进行操作
func red(a int, b int) (c int) {
	c = a - b
	return
}
// 函数支持返回多个返回值
func getPoint() (int, int) {
	return 52, 13
}

函数变量

在Go语言中,函数也是一种类型,可以和其他类型一样保存在变量中

package main

import "fmt"

func main() {
	// 声明一个函数类型的变量
	var f1 func(int, int) int
	// 将函数add赋值给f2
	f1 = add
	// 自动类型推导
	f2 := add
	fmt.Println(f1(1, 2)) // 3
	fmt.Println(f2(1, 2)) // 3
}

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

匿名函数

匿名函数是指不需要定义函数名的一种函数实现方式,由一个不带函数名的函数声明和函数体组成,匿名函数可以声明在另一个函数的函数体中

func (参数列表) (返回参数列表) {
    函数体
}

匿名函数的用途非常广泛,它本身就是一种值,可以方便地保存在各种容器中实现回调函数和操作封装。

// 定义的同时进行调用
func(a int, b int) {
  fmt.Println(a + b)
}(1, 2)

// 匿名函数赋值给变量
f := func(a int, b int) int {
    return a + b
}
fmt.Println(f(1, 2))

作为回调函数

package main

import "fmt"

func main() {
	names := []string{"tom", "lucy", "lily"}
	f := func(name string) {
		fmt.Printf("%s,Hello\n", name)
	}
	// 调用函数,并传入回调函数
	handlerNames(names, f)
}

// 遍历名单,对名字执行操作
func handlerNames(names []string, f func(string)) {
	for _, k := range names {
		f(k)
	}
}

闭包

Go语言中闭包是引用了另一个函数局部变量的函数,被引用的局部变量和闭包函数一同存在,即使已经离开了局部变量的环境也不会被释放或者删除,在闭包中可以继续使用这个局部变量。

同一个函数与不同引用环境组合,可以形成不同的实例。

img

一个函数类型就像结构体一样,可以被实例化,函数本身不存储任何信息,只有与引用环境结合后形成的闭包才具有记忆性

函数是编译期静态的概念,而闭包是运行期动态的概念。

例如,实现一个累加器

package main

import "fmt"

func main() {
	f := add()
	fmt.Println(f()) //1
	fmt.Println(f()) //2
	fmt.Println(f()) //3
	fmt.Println(f()) //4
}

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

defer关键字

Go语言的 defer 语句会将其后面跟随的语句进行延迟处理,在 defer所属的函数即将返回时,将延迟处理的语句按 defer声明的逆序进行执行。也就是说,先被 defer 的语句最后被执行;最后被 defer 的语句,最先被执行。

在同一个函数中,defer可以看作为栈操作,也就是先注册的defer语句在栈底,所以后进先出

关键字 defer 的用法类似于面向对象编程语言Java的 finally 语句块,它一般用于释放某些已分配的资源,典型的例子就是对一个互斥解锁,或者关闭一个文件。

package main

import "fmt"

func main() {
	fmt.Println("start")
	defer fmt.Println("end")
	defer fmt.Println(3)
	defer fmt.Println(2)
	defer fmt.Println(1)
	/*
		start
		1
		2
		3
		end
	*/
}

defer执行匿名函数

package main

import "fmt"

func main() {
	defer func() {
		fmt.Println("hello")
		fmt.Printf("world")
	}()
}

即时求值的变量快照

程序执行到defer语句的时候(而不是defer语句执行的时候),如果defer后调用的函数需要传参,那么就会把变量值传递给函数。

package main

import "fmt"

func main() {
	name := "go"
	defer func(n string) {
		fmt.Println(n) // go
	}(name)
	name = "python"
}

但是,如果defer后面是无参的匿名函数,那么程序无参可传,就会继续往下执行。直到defer语句执行的时候,才会去获取变量值。

package main

import "fmt"

func main() {
	name := "go"
	defer func() {
		fmt.Println(name) // python
	}()
	name = "python"
}

defer和return

在Go语言中,return语句在defer前执行

package main

import "fmt"

func main() {
	compare()
}

func compare() int {
	defer deferFunc()
	return returnFunc()
}

func deferFunc() int {
	fmt.Println("defer")
	return 0
}

func returnFunc() int {
	fmt.Println("return")
	return 0
}

/*
	return
	defer
*/

虽然return语句是在defer语句前执行,但是对于底层,return语句并不是原子操作,它分为给返回值赋值和RET指令两步。defer语句的执行时机就在返回值赋值操作后,RET指令执行前。

image-20240119164319340

package main

import "fmt"

func main() {
	fmt.Println(ff()) // 0
	fmt.Println(tt()) // 1
}

/*
ff()的return过程如下:
1、返回值赋值:returnVariable = (a = 0)
2、执行defer:a++
3、RET指令执行,函数结束
第二步a++实际是改变了局部变量a,而并没有改变返回值returnVariable,所以返回值还是0
*/
func ff() int {
	a := 0
	defer func() {
		a++
	}()
	return a
}

/*
tt()的return过程如下:
1、返回值赋值:a = 0(根据Go语言规则,类型声明默认赋值为零值)
2、执行defer:a++
3、RET指令执行,函数结束
由于是命名返回值,所以返回值就是变量a,第二步a++改变了返回值a,所以返回值是1
*/
func tt() (a int) {
	defer func() {
		a++
	}()
	return
}

内置函数

Go 语言拥有一些不需要进行导入操作就可以使用的内置函数。它们有时可以针对不同的类型进行操作,例如:lencapappend,或必须用于系统级的操作,例如:panic。因此,它们需要直接获得编译器的支持。

append            -- 用来追加元素到数组、slice中,返回修改后的数组、slice
close             -- 主要用来关闭channel
delete            -- 从map中删除key对应的value
panic             -- 停止常规的goroutine  (panic和recover:用来做错误处理)
recover           -- 允许程序定义goroutine的panic动作
real              -- 返回complex的实部   (complex、real imag:用于创建和操作复数)
imag              -- 返回complex的虚部
make              -- 用来分配内存,返回Type本身(只能应用于slice, map, channel)
new               -- 用来分配内存,主要用来分配值类型指针,比如*int、*string。
cap               -- capacity是容量的意思,用于返回某个类型的最大容量(只能用于切片和 map)
copy              -- 用于复制和连接slice,返回复制的数目
len               -- 来求长度,比如string、array、slice、map、channel ,返回长度
print、println     -- 底层打印函数,在开发中建议使用 fmt 包

函数式编程

函数式编程是一种强调用函数计算的编程范式,它通过组合纯函数、避免状态共享和可变数据以及使用声明式风格来构建软件。虽然Go不是一种纯函数式语言,但它提供了许多支持函数式编程风格的特性。

函数式编程的核心概念

函数式编程的基本原则

函数式编程的优势

函数式编程语言的谱系

Go语言中的函数式编程特性

尽管Go主要是一种命令式语言,但它提供了多种支持函数式编程的特性。

Go语言中的一等函数

在Go中,函数是一等公民(first-class citizens),这意味着函数可以:

package main

import (
    "fmt"
    "strings"
)

// 定义一个函数类型
type StringProcessor func(string) string

// 接受函数作为参数
func processString(s string, processor StringProcessor) string {
    return processor(s)
}

// 返回函数的高阶函数
func createPrefixer(prefix string) StringProcessor {
    return func(s string) string {
        return prefix + s
    }
}

func main() {
    // 函数赋值给变量
    toUpper := strings.ToUpper
    
    // 函数作为参数
    result1 := processString("hello", toUpper)
    fmt.Println(result1) // 输出: HELLO
    
    // 使用返回的函数
    addGo := createPrefixer("Go ")
    result2 := addGo("Programming")
    fmt.Println(result2) // 输出: Go Programming
    
    // 匿名函数
    result3 := processString("hello world", func(s string) string {
        return strings.ReplaceAll(s, " ", "-")
    })
    fmt.Println(result3) // 输出: hello-world
}

匿名函数和闭包

闭包在Go中有许多实际应用:

package main

import (
    "fmt"
)

// 返回一个计数器函数的工厂函数
func createCounter(initial int) func() int {
    // count变量被闭包捕获
    count := initial
    
    // 返回增加计数的闭包
    return func() int {
        count++
        return count
    }
}

// 使用闭包实现带有私有状态的函数
func createMultiplier(factor int) func(int) int {
    return func(value int) int {
        return value * factor
    }
}

func main() {
    // 创建并使用计数器
    counter1 := createCounter(0)
    counter2 := createCounter(10)
    
    fmt.Println(counter1()) // 1
    fmt.Println(counter1()) // 2
    fmt.Println(counter2()) // 11
    fmt.Println(counter2()) // 12
    fmt.Println(counter1()) // 3
    
    // 创建并使用乘法器
    double := createMultiplier(2)
    triple := createMultiplier(3)
    
    fmt.Println(double(5))  // 10
    fmt.Println(triple(5))  // 15
}

函数选项模式

函数选项模式是Go中一种流行的函数式编程模式,用于配置复杂对象:

函数选项模式的优势:

package main

import (
	"fmt"
	"time"
)

// MyServer 结构体
type MyServer struct {
	host    string
	port    int
	timeout time.Duration
	maxConn int
	tls     bool
}

// MyServerOption 是配置 MyServer 的函数
type MyServerOption func(*MyServer)

// WithHost 配置 host
func WithHost(host string) MyServerOption {
	return func(server *MyServer) {
		server.host = host
	}
}

// WithPort 配置 port
func WithPort(port int) MyServerOption {
	return func(server *MyServer) {
		server.port = port
	}
}

// WithTimeout 配置 timeout
func WithTimeout(timeout time.Duration) MyServerOption {
	return func(server *MyServer) {
		server.timeout = timeout
	}
}

// WithMaxConn 配置 maxConn
func WithMaxConn(maxConn int) MyServerOption {
	return func(server *MyServer) {
		server.maxConn = maxConn
	}
}

// WithTls 配置 tls
func WithTls(tls bool) MyServerOption {
	return func(server *MyServer) {
		server.tls = tls
	}
}

// NewMyServer 创建一个新的 MyServer
func NewMyServer(options ...MyServerOption) *MyServer {
	// 配置默认值
	myServer := &MyServer{
		host:    "127.0.0.1",
		port:    8080,
		timeout: 30 * time.Second,
		maxConn: 100,
		tls:     false,
	}
	// 应用配置
	for _, op := range options {
		op(myServer)
	}
	return myServer
}

func main() {
	// 使用默认值
	ms1 := NewMyServer()
	// 使用自定义配置
	ms2 := NewMyServer(
		WithHost("192.168.0.1"),
		WithPort(80),
		WithMaxConn(60),
	)
	fmt.Println(*ms1)
	fmt.Println(*ms2)
}

不可变数据的模拟

虽然Go没有内置的不可变数据类型,但可以通过约定和封装来模拟不可变性:

package main

import "fmt"

// 不可变Point
type Point struct {
    x, y int
}

// 创建新Point的构造函数
func NewPoint(x, y int) Point {
    return Point{x, y}
}

// Add返回一个新Point,而不修改原Point
func (p Point) Add(other Point) Point {
    return Point{
        x: p.x + other.x,
        y: p.y + other.y,
    }
}

// Scale返回一个新Point,原Point不变
func (p Point) Scale(factor int) Point {
    return Point{
        x: p.x * factor,
        y: p.y * factor,
    }
}

// 不可变的字符串处理函数
func Reverse(s string) string {
    runes := []rune(s)
    for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 {
        runes[i], runes[j] = runes[j], runes[i]
    }
    return string(runes)
}

// 不可变的切片操作
func AppendWithoutModifying(slice []int, elements ...int) []int {
    // 创建新切片而非修改原切片
    newSlice := make([]int, len(slice), len(slice)+len(elements))
    copy(newSlice, slice)
    return append(newSlice, elements...)
}

func main() {
    // 不可变Point示例
    p1 := NewPoint(5, 10)
    p2 := NewPoint(3, 7)
    
    p3 := p1.Add(p2)
    p4 := p1.Scale(2)
    
    fmt.Println("p1:", p1) // 原点不变
    fmt.Println("p3:", p3) // 新的点
    fmt.Println("p4:", p4) // 新的点
    
    // 不可变字符串操作
    s1 := "Hello"
    s2 := Reverse(s1)
    
    fmt.Println("s1:", s1) // 原字符串不变
    fmt.Println("s2:", s2) // 新字符串
    
    // 不可变切片操作
    slice1 := []int{1, 2, 3}
    slice2 := AppendWithoutModifying(slice1, 4, 5)
    
    fmt.Println("slice1:", slice1) // 原切片不变
    fmt.Println("slice2:", slice2) // 新切片
}

高阶函数在Go中的应用

高阶函数是函数式编程的核心特性之一,它们接受函数作为参数或返回函数作为结果。Go支持高阶函数,使得我们可以编写更灵活和抽象的代码。

函数变换(map、filter、reduce)

函数式编程中最常见的高阶函数是map、filter和reduce。虽然Go的标准库没有直接提供这些函数,但我们可以轻松实现它们:

package main

import (
    "fmt"
    "strings"
)

// Map对切片中的每个元素应用函数,返回新切片
func Map[T, U any](slice []T, f func(T) U) []U {
    result := make([]U, len(slice))
    for i, v := range slice {
        result[i] = f(v)
    }
    return result
}

// Filter返回满足条件的元素切片
func Filter[T any](slice []T, f func(T) bool) []T {
    var result []T
    for _, v := range slice {
        if f(v) {
            result = append(result, v)
        }
    }
    return result
}

// Reduce将切片归约为单个值
func Reduce[T, U any](slice []T, initial U, f func(U, T) U) U {
    result := initial
    for _, v := range slice {
        result = f(result, v)
    }
    return result
}

func main() {
    // 原始数据
    numbers := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
    words := []string{"apple", "banana", "cherry", "date", "elderberry"}
    
    // 使用Map
    doubled := Map(numbers, func(n int) int {
        return n * 2
    })
    fmt.Println("Doubled:", doubled)
    
    uppercased := Map(words, strings.ToUpper)
    fmt.Println("Uppercased:", uppercased)
    
    // 使用Filter
    evens := Filter(numbers, func(n int) bool {
        return n%2 == 0
    })
    fmt.Println("Even numbers:", evens)
    
    longWords := Filter(words, func(s string) bool {
        return len(s) > 5
    })
    fmt.Println("Long words:", longWords)
    
    // 使用Reduce
    sum := Reduce(numbers, 0, func(acc, n int) int {
        return acc + n
    })
    fmt.Println("Sum:", sum)
    
    concatenated := Reduce(words, "", func(acc string, s string) string {
        if acc == "" {
            return s
        }
        return acc + ", " + s
    })
    fmt.Println("Concatenated:", concatenated)
    
    // 组合使用
    sumOfEvenDoubles := Reduce(
        Map(
            Filter(numbers, func(n int) bool { return n%2 == 0 }),
            func(n int) int { return n * 2 },
        ),
        0,
        func(acc, n int) int { return acc + n },
    )
    fmt.Println("Sum of doubled even numbers:", sumOfEvenDoubles)
}

装饰器模式

装饰器模式允许我们在不改变函数接口的情况下增强其功能。这在中间件、日志记录和性能监控等场景中很有用:

package main

import (
    "fmt"
    "log"
    "time"
)

// 定义函数类型
type StringFunc func(string) string

// 日志装饰器
func LogDecorator(fn StringFunc, name string) StringFunc {
    return func(s string) string {
        log.Printf("Calling %s with input: %s", name, s)
        result := fn(s)
        log.Printf("%s returned: %s", name, result)
        return result
    }
}

// 计时装饰器
func TimingDecorator(fn StringFunc) StringFunc {
    return func(s string) string {
        start := time.Now()
        result := fn(s)
        elapsed := time.Since(start)
        log.Printf("Function took %s", elapsed)
        return result
    }
}

// 重试装饰器
func RetryDecorator(fn StringFunc, attempts int) StringFunc {
    return func(s string) (result string) {
        for i := 0; i < attempts; i++ {
            result = fn(s)
            if result != "" { // 假设空结果表示失败
                return
            }
            log.Printf("Attempt %d failed, retrying...", i+1)
        }
        return
    }
}

// 一些示例函数
func Reverse(s string) string {
    runes := []rune(s)
    for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 {
        runes[i], runes[j] = runes[j], runes[i]
    }
    return string(runes)
}

func Uppercase(s string) string {
    return strings.ToUpper(s)
}

func main() {
    // 装饰简单函数
    decoratedReverse := LogDecorator(Reverse, "Reverse")
    decoratedReverse = TimingDecorator(decoratedReverse)
    
    fmt.Println(decoratedReverse("Hello, World!"))
    
    // 组合多个装饰器
    processString := RetryDecorator(
        TimingDecorator(
            LogDecorator(Uppercase, "Uppercase"),
        ),
        3,
    )
    
    fmt.Println(processString("test"))
}

中间件模式

中间件是Web开发中常见的一种特殊装饰器模式,它被用于HTTP处理器的链式处理:

package main

import (
	"fmt"
	"log"
	"net/http"
	"time"
)

// 定义处理器类型
type Handler func(http.ResponseWriter, *http.Request)

// 将自定义Handler转换为http.HandlerFunc
func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	h(w, r)
}

// 记录请求的中间件
func LoggingMiddleware(next Handler) Handler {
	return func(w http.ResponseWriter, r *http.Request) {
		start := time.Now()
		log.Printf("Started %s %s", r.Method, r.URL.Path)

		next(w, r)

		log.Printf("Completed %s %s in %v", r.Method, r.URL.Path, time.Since(start))
	}
}

// 恢复panic的中间件
func RecoveryMiddleware(next Handler) Handler {
	return func(w http.ResponseWriter, r *http.Request) {
		defer func() {
			if err := recover(); err != nil {
				log.Printf("Panic: %v", err)
				http.Error(w, "Internal Server Error", http.StatusInternalServerError)
			}
		}()

		next(w, r)
	}
}

// 认证中间件
func AuthMiddleware(next Handler) Handler {
	return func(w http.ResponseWriter, r *http.Request) {
		// 简化的认证检查
		authToken := r.Header.Get("Authorization")
		if authToken == "" {
			http.Error(w, "Unauthorized", http.StatusUnauthorized)
			return
		}

		// 通过认证,继续处理
		next(w, r)
	}
}

// 应用中间件的辅助函数
func ApplyMiddleware(h Handler, middlewares ...func(Handler) Handler) Handler {
	for i := len(middlewares) - 1; i >= 0; i-- {
		h = middlewares[i](h)
	}
	return h
}

// 处理函数
func HelloHandler(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintln(w, "Hello, World!")
}

func AdminHandler(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintln(w, "Admin Area")
}

func PanicHandler(w http.ResponseWriter, r *http.Request) {
	panic("Something went wrong!")
}

func main() {
	// 应用中间件
	http.Handle("/", ApplyMiddleware(
		HelloHandler,
		LoggingMiddleware,
		RecoveryMiddleware,
	))

	http.Handle("/admin", ApplyMiddleware(
		AdminHandler,
		LoggingMiddleware,
		RecoveryMiddleware,
		AuthMiddleware,
	))

	http.Handle("/panic", ApplyMiddleware(
		PanicHandler,
		LoggingMiddleware,
		RecoveryMiddleware,
	))

	log.Println("Server starting on :8080")
	log.Fatal(http.ListenAndServe(":8080", nil))
}