【Golang】:函数和包

发布于:2025-08-19 ⋅ 阅读:(15) ⋅ 点赞:(0)

目录

1. 函数

1.1 函数定义

1.2 函数传参

1.3 函数返回值

1.4 可变参数

1.5 函数类型

1.6 匿名函数

1.7 defer 延迟调用

1.8 闭包(Closure)

2. 包

2.1. 基本概念

2.2 使用方式

2.3 init 函数


1. 函数

1.1 函数定义

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

注意: Go中的函数不支持函数重载

package main

import "fmt"

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

	fmt.Println(add(1, 2))

}

1.2 函数传参

参数传递的方式有两种:

  • 值传递:传参时传递的是值的拷贝,函数内部对参数的修改不会影响到原始数据,值类型参数默认采用的就是值传递,包括基本数据类型、数组和结构体。
  • 引用传递:传参时传递的是地址的拷贝,在函数内部对参数的修改会影响到原始数据,引用类型参数默认采用引用传递,包括指针、切片、管道、接口等。
package main

import "fmt"

// 值传递,无法改变内部的数据
func swap(a int, b int) {
	temp := a
	a = b
	b = temp
}
func main() {
	var a int = 100
	var b int = 200
	fmt.Println("a: ", a, "b: ", b)     // a:  100 b:  200
	swap(a, b)
	fmt.Println("a: ", a, "b: ", b)     // a:  100 b:  200

}

引用传参:

package main

import "fmt"

func swap(a *int, b *int) {
	temp := *a
	*a = *b
	*b = temp
}
func main() {
	var a int = 100
	var b int = 200
	fmt.Println("a: ", a, "b: ", b) // a:  100 b:  200
	swap(&a, &b)
	fmt.Println("a: ", a, "b: ", b) // a:  200 b:  100

}

1.3 函数返回值

1. 返回多个值

Go中函数支持返回多个值,通过返回值列表指明各个返回值的类型即可。如下:

package main

import "fmt"

func GetSumAndSub(a int, b int) (int, int) { // 返回多个值
	add := a + b
	sub := a - b
	return add, sub
}

func main() {
	add, sub := GetSumAndSub(10, 20)
	fmt.Println(add) // 30
	fmt.Println(sub) // -10
}

2. 忽略返回值

如果函数返回多个值,在接收时,可以通过_(占位符)忽略不需要的返回值。如下:

package main

import "fmt"

func GetSumAndSub(a int, b int) (int, int) { // 返回多个值
	add := a + b
	sub := a - b
	return add, sub
}

func main() {
	_, sub := GetSumAndSub(10, 20)
	fmt.Println(sub) // -10
}

3. 返回值命名

Go中函数支持在返回值列表给返回值命名,这时函数在返回时无需在return后指明需要返回的值,可以避免返回顺序出错。如下:

package main

import "fmt"

func GetSumAndSub(a int, b int) (add int, sub int) { // 返回多个值
	add = a + b
	sub = a - b
	return
}

func main() {
	add, sub := GetSumAndSub(10, 20)
	fmt.Println(add) // 30
	fmt.Println(sub) // -10
}

1.4 可变参数

参数数量不确定时使用:

  • 如果一个函数的形参列表中有可变参数,则可变参数需要放在形参列表的最后。
package main

import "fmt"

func sum(nums ...int) int {
	total := 0
	for _, v := range nums {
		total += v
	}
	return total
}

func main() {
	fmt.Println(sum(1))               // 1
	fmt.Println(sum(1, 2))            // 3
	fmt.Println(sum(1, 2, 3))         // 6
	fmt.Println(sum(1, 2, 3, 4))      // 10
}

1.5 函数类型

在Go中函数也是一种数据类型,可以将其赋值给一个变量,然后通过该变量即可对函数进行调用。如下:

package main

import "fmt"

func add(num1 int, num2 int) int {
	return num1 + num2
}

func main() {
	sumFunc := add
	fmt.Printf("类型:%T, 值:%d", sumFunc, sumFunc(1, 2))    // 类型:func(int, int) int, 值:3
}

类型定义和类型别名

1. 类型定义

  • 这里 MyInt 是一个新类型,它的底层类型是 int,但是编译器认为 intMyInt 是两个不同的类型。

  • 所以直接赋值会报错,必须 显式类型转换

package main

import "fmt"

func main() {
	type myInt int

	var a myInt = 100
	var b int = 200
	fmt.Printf("类型:%T, 值:%d\n", a, a) // 类型:main.myInt, 值:100
	fmt.Printf("类型:%T, 值:%d\n", b, b) // 类型:int, 值:200

	// a = b     // 错误,
	a = myInt(b)
	fmt.Printf("类型:%T, 值:%d\n", a, a) // 类型:main.myInt, 值:200
	fmt.Printf("类型:%T, 值:%d\n", b, b) // 类型:int, 值:200
}

2. 类型别名

  • 这里 MyInt 只是 int别名,两者完全等价,编译器认为它们就是同一个类型。

  • 因此可以直接赋值,不需要转换

package main

import "fmt"

func main() {
	type myInt = int

	var a myInt = 100
	var b int = 200
	fmt.Printf("类型:%T, 值:%d\n", a, a) // 类型:int, 值:100
	fmt.Printf("类型:%T, 值:%d\n", b, b) // 类型:int, 值:200

	a = b                             // 可以直接赋值
	fmt.Printf("类型:%T, 值:%d\n", a, a) // 类型:int, 值:200
	fmt.Printf("类型:%T, 值:%d\n", b, b) // 类型:int, 值:200
}

    在Go中将函数作为形参也是常见的用法,这时结合自定义数据类型给函数类型取别名,能有效提高代码的可读性。如下:

    package main
    
    import "fmt"
    
    func Sum(a int, b int) int {
    	return a + b
    }
    
    type SumType func(int, int) int // 自定义数据类型
    
    func MyFunc(f SumType, num1 int, num2 int) int {
    	return f(num1, num2)
    }
    
    func main() {
    	result := MyFunc(Sum, 10, 20)
    	fmt.Printf("值:%d\n", result) // 值:30
    }
    

    1.6 匿名函数

    如果希望某个函数只使用一次,可以考虑使用匿名函数,定义时直接使用,不必命名:

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

    可以赋值给变量,进行使用:

    package main
    
    import "fmt"
    
    func main() {
    	// 定义匿名函数并调用
    	sum1 := func(a int, b int) int {
    		return a + b
    	}(1, 2)
    	fmt.Printf("类型:%T, 值:%d\n", sum1, sum1) // 类型:int, 值:3
    
    	// 定义匿名函数
    	sum2 := func(a int, b int) int {
    		return a + b
    	}
    	fmt.Printf("类型:%T, 值:%d\n", sum2, sum2(100, 200)) // 类型:func(int, int) int, 值:300
    }
    

    1.7 defer 延迟调用

    1. defer 的作用:用来延迟执行某些操作,通常是释放资源

    • 文件关闭(file.Close()
    • 解锁(mu.Unlock()
    • 数据库连接释放
    • 网络连接关闭

    这样写可以避免忘记释放资源,即使函数发生 panicdefer 语句依然会执行。

    2. 执行顺序

    • 当程序执行到 defer 时,不会立刻执行,而是把该语句 压入 defer 栈

    • 当函数返回时,Go 会按照 后进先出(LIFO) 的顺序执行这些语句。

    • defer 语句的参数会在声明时就被求值,而不是在函数结束时。

    package main
    
    import "fmt"
    
    func demo() {
    	defer fmt.Println("A")
    	defer fmt.Println("B")
    	fmt.Println("C")
    }
    
    func test() {
    	x := 10
    	defer fmt.Println("defer x =", x) // 保存的是 x=10
    	x = 20
    	fmt.Println("x =", x)
    }
    
    func main() {
    	demo()         // C B A
    	test()         // x = 20  defer x = 10
    }
    

    常见应用场景

    • 在Go中通常会在创建资源后,通过defer语句将资源关闭,由于defer语句会在当前函数执行完毕后再执行,因此在defer语句之后仍然可以使用创建的资源。
    • 在其他语言中,资源释放时机是一个常见的问题,而Go中的defer机制就使得资源的创建和释放可以成对存在,程序员再也不用担心资源释放时机的问题了。
    func FileOperation(filename string) (err error) {
    	file, err := os.Open(filename) // 打开文件
    	if err != nil {
    		fmt.Printf("open file error, err = %v\n", err)
    		return
    	}
    	defer file.Close() // 关闭文件
    
    	// 进行文件操作...
    
    	return
    }

    注意事项(常见坑)

    • 多个 defer 的执行顺序是反向的(栈结构)

    • defer 的参数在声明时就确定了,不会随着变量变化而改变

    • defer 与 return 结合时,如果 defer 修改了返回值(需要命名返回值),可能导致结果不同。

    package main
    
    import "fmt"
    
    func f() (x int) {
    	defer func() { x++ }()
    	return 3
    }
    
    func main() {
    	x := f()
    	fmt.Println("值:%d\n", x)      // 4
    }
    

      1.8 闭包(Closure)

      1. 什么是闭包?

      • 闭包 = 函数 + 外部变量引用环境

      • 它允许函数“记住”并操作其外部作用域中的变量,即使该作用域已经结束。

      package main
      
      import "fmt"
      
      func add(a int) func(int) int {
      	var sum = a
      	return func(x int) int {
      		sum += x
      		return sum
      	}
      }
      
      func main() {
      	var posSum = add(10)
      	fmt.Printf("类型:%T\n", posSum)    // 类型:func(int) int
      	fmt.Printf("值:%d\n", posSum(10))  // 值:20
      	fmt.Printf("值:%d\n", posSum(20))  // 值:40
      	fmt.Printf("值:%d\n", posSum(30))  // 值:70
      
      }
      
      • sum 是外部变量,func(x int) int 是内部函数。

      • 即使 add() 执行完毕,sum 依然被闭包函数捕获并存储在内存中。

      注意:闭包记住环境,变量延长寿命;引用而非拷贝,循环要格外小心。

      2. 包

      2.1. 基本概念

      1. 包 = 代码的最小组织单元

      2. 每个 Go 源文件都必须声明一个 package

      3. 同一个目录下的 .go 文件必须属于同一个包(除非是 main 包)。

      4. 首字母大写:对外可见(公有)。

      5. 首字母小写:仅包内可见(私有)。

      6. 可管理性:大项目通常会按功能划分多个包,例如:modelscontrollersservices

      2.2 使用方式

      包的使用方式可以分为四部:打包导入包给包取别名使用包

      1. 打包

      // 在 .go 文件的第一行写上
      package 包名
      
      // 注意:同一目录下的 .go 文件必须属于同一个包。

      2. 导入包

      // 在需要使用的地方通过 import 引入
      import "test_go/hello"

      3. 给包取别名

      注意:取了别名就只能用别名

      package main
      
      import (
      	"fmt"
      	h "test_go/hello"     // 可以避免包名冲突,或缩短使用
      )
      
      func main() {
      	fmt.Println(h.Add(1, 2))
      }
      

      4. 使用包

      // 通过 包名.标识符 调用
      // 注意:只有 首字母大写 的函数/变量/类型才能被外部包访问。
      package main
      
      import (
      	"fmt"
      	h "test_go/hello"
      )
      
      func main() {
      	fmt.Println(h.Add(1, 2))
      }
      

      2.3 init 函数

      1. 基本概念

      • 每个 Go 源文件都可以包含 一个或多个 init 函数。

      • init 函数在 程序运行前自动调用,且 main() 之前执行

      • 不能被显式调用,即你不能在代码里写 init()

      2. 执行顺序

      1. 包级变量初始化:先初始化包里的全局变量。

      2. 执行 init 函数:包中可能有多个 init,会按照它们在源码中出现的顺序执行,但这并不会产生重定义报错,因为init函数在编译阶段会被编译器处理为特殊的符号,确保所有init函数被正确执行而不会发生冲突。

      3. 导入包的 init

        • 如果 main 包依赖其它包,会先初始化依赖包(递归执行)。

        • 即:先初始化被导入的包,再初始化当前包

      4. 最后才执行 main()

      依赖包变量初始化 → 依赖包 init() → 当前包变量初始化 → 当前包 init() → main()
      
      package main
      
      import (
      	"fmt"
      )
      
      var num = initNum()
      
      func initNum() int {
      	fmt.Println("初始化全局变量 num")
      	return 100
      }
      func init() {
      	fmt.Println("init1() 被调用")
      }
      func init() {
      	fmt.Println("init2() 被调用")
      }
      
      func main() {
      	fmt.Println("main() 被调用")
      	fmt.Println("num =", num)
      }
      
      // 初始化全局变量 num
      // init1() 被调用
      // init2() 被调用
      // main() 被调用
      // num = 100
      

      如果 main 包导入了其他包,main包初始化之前,会先对其导入的包进行初始化。

      package main
      
      import (
          "fmt"
          h "test_go/hello"
      )
      
      var num = initNum()
      
      func initNum() int {
          fmt.Println("初始化全局变量 num")
          return 100
      }
      func init() {
          fmt.Println("init() 被调用")
      }
      
      func main() {
          fmt.Println("main() 被调用")
          fmt.Println("num =", num)
          fmt.Println(h.Add(1, 2))
      }
      /*
      hello.go中init() 被调用
      初始化全局变量 num
      init() 被调用
      main() 被调用
      num = 100
      3
      */


      网站公告

      今日签到

      点亮在社区的每一天
      去签到