函数构成了代码执行的逻辑结构,在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语言中闭包是引用了另一个函数局部变量的函数,被引用的局部变量和闭包函数一同存在,即使已经离开了局部变量的环境也不会被释放或者删除,在闭包中可以继续使用这个局部变量。
同一个函数与不同引用环境组合,可以形成不同的实例。
一个函数类型就像结构体一样,可以被实例化,函数本身不存储任何信息,只有与引用环境结合后形成的闭包才具有记忆性。
函数是编译期静态的概念,而闭包是运行期动态的概念。
例如,实现一个累加器
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
}
}
add()
中的匿名函数被函数add()
外的变量f
引用的时候,就创建了一个闭包。add()
中的变量i
始终存在,gc不会回收变量i
,因为函数add()
中的匿名函数执行需要依赖变量i
。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
*/
}
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"
}
在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
指令执行前。
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 语言拥有一些不需要进行导入操作就可以使用的内置函数。它们有时可以针对不同的类型进行操作,例如:len
、cap
和 append
,或必须用于系统级的操作,例如: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中,函数是一等公民(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支持高阶函数,使得我们可以编写更灵活和抽象的代码。
函数式编程中最常见的高阶函数是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))
}