Golang - 函数和包

包解决了同名函数的问题,Go 的每一个文件都有所属的包,通过包来管理项目目录结构的。

如果需要编译为一个可执行程序,需要一个名为 main 的包;如果是写一个库,就没有这个限制

的作用:

  1. 区分相同名字的函数、变量等标识符
  2. 管理项目文件目录
  3. 控制函数、变量等访问范围,即作用域(通过函数名大小写来控制)

函数

函数分为自定义函数和系统函数,函数是为了提取公用代码,提高代码复用性。

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

函数的注意事项:

  1. 函数的形参列表可以有多个,返回值列表也可以有多个
  2. 形参列表和返回值列表的数据类型可以是值类型和引用类型
  3. 函数的命名遵循标识符命名规范,通过首字母大小写来控制访问范围
  4. 基本数据类型和数组默认是值传递的,即进行值拷贝。在函数内修改不会影响到原来的值
  5. 如果希望函数内的变量能修改函数外的变量,可以传入变量的地址&,函数内以指针的方式操作变量
  6. Go 函数不支持重载
  7. 函数(函数也是一种数据类型)可以赋值给一个变量,该变量的类型就是函数类型,通过该变量可以进行函数调用
func main() {
	a := getSum

	res := a(1, 2)
	fmt.Println(res)
}

func getSum(n1 int, n2 int) int {
	return n1 + n2
}
  1. 函数也可以作为形参,并且调用
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
}
  1. Go 可以自定义数据类型
    基本语法: type 自定义数据类型名 数据类型
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
}
  1. Go 支持函数返回值命名
  • 普通函数
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)
}
  1. 可以使用 _ 忽略返回值
sum1, _ := getSumAndSub(10, 5)
fmt.Println(sum1)
  1. 支持可变长度参数,可变参数需要放在形参列表的最后
// 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)
}
  1. 如果形参列表数据类型相同,可以只写一个数据类型
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 函数

每一个源文件都可以包含一个 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 栈中,如果有多行,依次压入,当函数执行完毕之后,再从 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 标识行压入栈中时,会把引用的变量的值拷贝同时入栈。

函数参数传递方式

两种传递方式:

  • 值传递
  • 引用传递

值类型参数默认就是值传递,而引用类型参数默认就是引用传递。其实,不管是值传递还是引用传递,传递给函数的都是变量的副本,不同的是,值传递的是值的拷贝,引用传递的是地址的拷贝,一般来说,地址拷贝效率高,因为数据量小;而值拷贝决定拷贝的数据大小,数据越大,效率越低。

变量作用域

  1. 函数内部定义的变量叫局部变量,作用域仅限于函数内部
  2. 函数外部定义的变量叫全局变量,作用域在整个包都可以引用,如果其首字母为大写,则作用域在整个程序都可以引用
  3. 如果变量实在一个代码块中,比如 for/if 中,那么这个变量的作用域就在该代码块
  4. 函数体外不允许有赋值语句
// 可以定义变量并初始化
var Age int = 20
// 不允许赋值语句在函数体外
Name := "tom" // 等价于 var Name string  Name = "tom"

func main() {
	...
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值