包解决了同名函数的问题,Go 的每一个文件都有所属的包,通过包来管理项目目录结构的。
如果需要编译为一个可执行程序,需要一个名为 main 的包;如果是写一个库,就没有这个限制
包
的作用:
函数分为自定义函数和系统函数,函数是为了提取公用代码,提高代码复用性。
func 函数名 (形参列表) (返回值列表) {
...
return 返回值列表
}
函数的注意事项:
func main() {
a := getSum
res := a(1, 2)
fmt.Println(res)
}
func getSum(n1 int, n2 int) int {
return n1 + n2
}
func main() {
res := sum(getSum, 1, 2)
fmt.Println(res)
}
// 定义了一个形参 sumFuc 来接收传入的函数
func sum(sumFuc func(int, int) int, n1 int, n2 int) int {
return sumFuc(n1, n2)
}
func getSum(n1 int, n2 int) int {
return n1 + n2
}
func main() {
// 自定义了一个 myInt 类型
type myInt int
// 声明一个 myInt 类型的变量并赋值
var i myInt = 10
}
或者上面 8 的例子可以修改为:
func main() {
res := sum(getSum, 1, 2)
fmt.Println(res)
}
// 定义一个自定义类型
type mySumFucType func(int, int) int
// 这里使用自定义类型 mySumFucType
func sum(sumFuc mySumFucType, n1 int, n2 int) int {
return sumFuc(n1, n2)
}
func getSum(n1 int, n2 int) int {
return n1 + n2
}
func getSumAndSub(n1 int, n2 int) (int, int) {
sum := n1 + n2
sub := n1 - n2
return sum, sub
}
func main() {
sum, sub := getSumAndSub(10, 5)
fmt.Println(sum, sub)
sum1, _ := getSumAndSub(10, 5)
fmt.Println(sum1)
}
func getSumAndSub2(n1 int, n2 int) (sum int, sub int) {
sum = n1 + n2
sub = n1 - n2
return
}
func main() {
sum, sub := getSumAndSub2(10, 5)
fmt.Println(sum, sub)
}
sum1, _ := getSumAndSub(10, 5)
fmt.Println(sum1)
// 0~n 个参数
func sum(args... int) sum int {
...
}
// 1~n个参数
func sum(n1 int, args... int) int {
...
}
案例:
func sumC(n1 int, args ...int) (sum int) {
sum = n1
for i := 0; i < len(args); i++ {
sum += args[i]
}
return
}
func main() {
res := sumC(10)
fmt.Println(res)
res2 := sumC(10, 1, 2, -1)
fmt.Println(res2)
}
func getSum2(n1, n2 int) int {
return n1 + n2
}
编写一个函数 swap(n1 *int, n2 *int),交换 n1 和 n2 的值
func swap(n1 *int, n2 *int) {
tmp := *n1
*n1 = *n2
*n2 = tmp
}
func main() {
a := 10
b := 20
swap(&a, &b)
fmt.Printf("a=%d, b=%d", a, b)
}
每一个源文件都可以包含一个 init 函数,该函数会在 main 函数执行前被调用。一般用于初始化工作,比如初始化数据库连接
如果一个文件同时包含全局变量定义、init 函数、main 函数,则执行顺序为:全局变量定义 - init 函数 - main 函数。如果 import 的包中有 init 函数,会先执行。
匿名函数即定义函数的时候没有名字。一般用于定义的时候直接调用,只调用一次。同时也可以将匿名函数赋值给一个变量,再通过变量进行调用。
res := func(n1 int, n2 int) int {
return n1 + n2
}(10, 20)
fmt.Prinfln(res)
a := func(n1,n2 int) int {
return n1 + n2
}
res := a(10, 20)
res1 := a(1, 2)
fmt.Prinfln(res, res1)
package main
import "fmt"
// 如果变量名为 Fun1,则可以在其他包中访问
var fun1 = func(n1, n2 int) int {
return n1 * n2
}
func main() {
res := fun1(1, 2)
fmt.Println(res)
}
// 定义一个累加器,返回一个函数类型
func AddUpper() func(int) int {
var n int = 10
return func(x int) int {
n += x
return n
}
}
func main() {
f := AddUpper()
fmt.Println(f(1)) // 11
// 执行完上一步之后,f 函数的成员变量 n = 11
fmt.Println(f(2)) // 13
// 这里,n = 13
fmt.Println(f(3)) // 16
}
AddUpper 函数的返回值是一个函数类型,这个匿名函数引用到了函数外的变量 n,因此这个函数就和 n 形成了一个整体,构成闭包。变量 n 属于函数 AddUpper,只会初始化一次。
编写一个函数 makeSuffix(suffix string),可以接收一个文件后缀名(比如 .jpg),并返回一个闭包,如果该文件名没有指定的后缀,就加上,否则直接返回
func makeSuffix(suffix string) func(string) string {
return func(name string) string {
if !strings.HasSuffix(name, suffix) {
return name + suffix
}
return name
}
}
func main() {
f := makeSuffix(".jpg")
fmt.Println(f("hello"))
fmt.Println(f("world.jpg"))
}
如果使用普通函数来实现这个功能,需要每次都把 .jpg
后缀都给传进去,使用闭包后我们只需要传一次就可以了。
延时机制。在函数执行完毕后,会执行 defer 标识的行。常用于关闭数据库连接、文件句柄、释放锁等。
原理是当程序顺序执行到标识了 defer 的代码行时,会把那一行代码压入到 defer 栈中,如果有多行,依次压入,当函数执行完毕之后,再从 defer 栈中依次读取执行(先入后出)。
func sum6(n1, n2 int) int {
defer fmt.Println("ok1 n1=", n1)
defer fmt.Println("ok2 n2=", n2)
n1++
n2++
res := n1 + n2
fmt.Println("ok3 res=", res)
return res
}
func main() {
res := sum6(10, 20)
fmt.Println("res=", res)
}
# 输出
ok3 res= 32
ok2 n2= 20
ok1 n1= 10
res= 32
注意:从输出结果中可以看到,当把 defer 标识行压入栈中时,会把引用的变量的值拷贝同时入栈。
两种传递方式:
值类型参数默认就是值传递,而引用类型参数默认就是引用传递。其实,不管是值传递还是引用传递,传递给函数的都是变量的副本,不同的是,值传递的是值的拷贝,引用传递的是地址的拷贝,一般来说,地址拷贝效率高,因为数据量小;而值拷贝决定拷贝的数据大小,数据越大,效率越低。
// 可以定义变量并初始化
var Age int = 20
// 不允许赋值语句在函数体外
Name := "tom" // 等价于 var Name string Name = "tom"
func main() {
...
}