Golang基础之函数
0、前言
函数存在的意义?
函数是一段代码的封装
把一段逻辑抽象出来封装到一个函数中,给它起个名字,每次用到它的时候直接用函数名调用即可
使用函数能够让代码结构更清晰,更简洁。
1、函数基本概念
函数是Go语言里的核心设计,它通过关键字func
来声明,它的格式如下:
func funcName(input1 type1,input2 type2) (output1 type1,output2 type2) {
// 这里是处理逻辑代码
// 返回多个值
return value1,value2
}
- 关键字func用来声明一个函数funcName。
- 函数可以有一个或者多个参数,每个参数后面带有类型,通 过","分隔函数可以返回多个值
- 返回值声明了两个变量output1和output2,如果你不想声明也可以,就保留两个类型声明。
- 如果只有一个返回值且不声明返回值变量,那么你可以省略“包括返回值”的括号
- 如果没有返回值,就直接省略最后的返回信息
- 如果有返回值,那么必须在函数的外层添加return语句。
实际应用函数的例子:
package main
import "fmt"
// 返回a、b中最大值
func max(a,b int) int {
if a > b {
return a
}
return b
}
func main() {
x := 3
y := 4
z := 5
max_xy := max(x,y) // 调用函数 max(x,y)
max_xz := max(x,z) // 调用函数 max(x,z)
fmt.Printf("max(%d,%d) =%d\n",x,y,max_xy)
fmt.Printf("max(%d,%d) =%d\n",x,z,max_xz)
}
我们从中看到,max函数有两个参数,它们的类型都是int,那么第 一个变量的类型可以省略(即a,b int,而非a int,b int),默认为离它 最近的类型,同理多于2个同类型的变量或者返回值。同时我们注意到 它的返回值就是一个类型,这个就是省略写法。
没有返回值
func f1(x int,y int){
fmt.Println(x + y) // 直接处理
}
没有参数没有返回值
func f2(){
fmt.Println("f2")
}
没有参数但有返回值
func f3() int{
return 3 // 需要变量接收返回值
}
可变长参数
可变长参数必须放在函数参数的最后
func f7(x string,y ...int){
fmt.Println(x)
fmt.Println(y) // y的类型是切片 []int。能接受多个值
}
func main(){
f7("下雨了",1,2,3,4,5,6)
// 下雨了
// [1 2 3 4 5 6]
}
2、函数命名返回值
命名返回值就相当于在函数中已经声明了一个变量,并且在return的时候不需要再指定这个变量了
func f4(x int,y int)(ret int){
ret = x + y
return
}
3、函数多返回值
package main
import "fmt"
// 返回a、b中最大值
func SumAndProduct(A,B int)(int,int){
return A+B,A*B
}
func main() {
x := 3
y := 4
xPLUSy,xTIMESy := SumAndProduct(x,y)
fmt.Println(xPLUSy) // 7
fmt.Println(xTIMESy) // 12
}
上面的例子中,我们可以看到函数直接返回了两个参数,当然我们 也可以命名返回参数的变量,这个例子里面只是用了两个类型,我们改 成如下定义,这样返回的时候不用带上变量名,因为直接在函数里面初 始化了。但如果你的函数是导出的(首字母大写),官方建议,最好命 名返回值,因为不命名返回值,虽然使代码更加简洁,但是会造成生成 的文档可读性差。
func SumAndProduct(A,B int)(add int,Multiplied int){
add = A+B
Multiplied = A*B
return
}
4、函数变参
Go语言函数支持变参。接受变参的函数有不定数量的参数
。为了 做到这点,首先需要定义函数使其接受变参。
arg ...int告诉我们Go语言这个函数接受不定数量的参数。注意,这些参数的类型全部是int。在函数体中,变量arg是一个int的slice。
func myfunc(arg ...int){
for _,n := range arg{
fmt.Println(n) // 1,2,3,4,5
}
}
func main() {
myfunc(1,2,3,4,5)
}
5、defer
Go语言中有种不错的设计,即延迟(defer)语句,你可以在函数 中添加多个defer语句。当函数执行到最后时,这些defer语句会按照逆序 执行,最后该函数返回。特别是当你在进行一些打开资源的操作时,遇 到错误需要提前返回,在返回前你需要关闭相应的资源,不然很容易造 成资源泄露等问题。我们打开一个资源操作如下所示。
func ReadWrite() bool{
file.Open("file")
// 做一些工作
if failureX {
file.Close()
return false
}
if failureY {
file.Close()
return false
}
file.Close()
return true
}
我们看到上面有很多重复的代码,Go语言的defer有效解决了这个 问题。使用它后,不但代码量减少了很多,而且程序变得更优雅。在 defer后指定的函数会在函数退出前调用。
func ReadWrite() bool{
file.Open("file")
defer file.Close()
if failureX {
return false
}
if failureY {
return false
}
return true
}
6、作用域
package main
import "fmt"
// 变量作用域
var x = 100 // 定义一个全局变量
func f1(){
// x := 10
name := "jack"
// 函数中查找变量的顺序
// 1.先在函数内部查找
// 2.找不到就往函数的外面查找
fmt.Println(x,name)
}
func main(){
f1()
// fmt.Println(name) // 函数内部定义的变量只能在函数内部使用
}
7、匿名函数
没有名字的函数称为匿名函数,而它通常用于函数内部。
全局匿名函数
var f1 = func(x,y int){
fmt.Println(x+y)
}
func main(){
f1(10,20)
}
局部匿名函数
我们可以将匿名函数定义到函数内部,我们都知道函数内部没有办法声明带名字的函数
func main(){
var f1 = func(x,y int){
fmt.Println(x+y)
}
f1(10,20)
}
如果只是调用一次的函数,还可以简写成立即执行函数
func main(){
// 如果只是调用一次的函数,还可以简写成立即执行函数
func(x,y int){
fmt.Println(x+y)
fmt.Println("Hello world!")
}(100,200)
}
8、内置函数介绍
内置函数 | 描述 | |
---|---|---|
close | 主要用来关闭channel | |
len | 用来求长度,比如string、array、slice、map、channel | |
new | 用来分配内存,主要用来分配值的类型,比如int、string,返回对应类型的指针 | |
make | 用来分配内存,主要用来分配引用类型,比如chan、map、slice,返回具体的类型 | |
append | 用来追加元素到数组、slice中 | |
panic和recover | 用来做错误处理 |
panic/recover
在Go语言中目前是没有异常机制,但是使用panic/revocer
模式来处理。panic
可以在任何地方引发,但recover
只有在defer
调用的函数中有效,首先来看一个例子:
package main
import "fmt"
// panic 和 recover
func funcA(){
fmt.Println("a")
}
func funcB(){
panic("出现了严重的错误!!!") // 程序奔溃退出
fmt.Println("b")
}
func funcC(){
fmt.Println("c")
}
func main(){
funcA()
funcB()
funcC()
}
输出结果
a
panic: 出现了严重的错误!!!
goroutine 1 [running]:
main.funcB(...)
/Users/xiongminghao/Documents/golang/GoProjects/src/go_dev/day3/panic_recover/main.go:15
main.main()
/Users/xiongminghao/Documents/golang/GoProjects/src/go_dev/day3/panic_recover/main.go:26 +0x98
exit status 2
程序运行期间funcB
中引发了panic
导致程序奔溃,异常退出了。这个时候我们就可以通过recover
将程序恢复回来,继续往后执行
package main
import "fmt"
// panic 和 recover
func funcA(){
fmt.Println("a")
}
func funcB(){
// 刚刚打开数据库连接
defer func(){
err := recover()
fmt.Println(err)
fmt.Println("释放数据库连接...")
}()
panic("出现了严重的错误!!!") // 程序奔溃退出
fmt.Println("b")
}
func funcC(){
fmt.Println("c")
}
func main(){
funcA()
funcB()
funcC()
}
输出结果:
a
出现了严重的错误!!!
释放数据库连接...
c
注意:
1.recover() 必须搭配defer使用
2.defer一定要在可能引发panic的语句之前定义
9、指针类型接受者
指正类型的接受者由一个结构体的指针组成,由于指针的特性,调用方法时修改接受者的任意成员变量,在方法结束后,修改都是有效的,这种方式就十分接近于其它语言中面向对象中的this
或者self
,例如我们为Person
添加指针类型
package main
import "fmt"
type person struct{
name,gender string
age int
}
func newPerson(name,gender string,age int) *person {
return &person{
name: name,
gender: gender,
age: age,
}
}
// 使用值接受者:传拷贝进去
func (p person) guonian(){
p.age++
}
// 指针接受者:传内存地址进去
func (p *person) zhenguonian() {
p.age++
}
func (p *person) dream() {
fmt.Println("不上班也能挣钱!")
}
func main(){
p1 := newPerson("xander","男",20)
fmt.Println("过年之前",p1.age) // 20
p1.zhenguonian()
fmt.Println("过年之后",p1.age) // 18
p1.dream()
}
什么时候应该使用指针类型接受者
1.需要修改接受者的值
2.接受者是拷贝代价比较大的大对象
3.保证一致性,如果有某个方法使用了指针接受者,那么其他的方法也应该使用指针接受者
10、任意类型添加方法
在Go语言中,接受者的类型可以是任何类型,不仅仅是结构体,任何类型都可以拥有方法。举个例子:我们基于内置的int
类型使用type关键字可以定义新的自定义类型,然后为我们的自定义类型添加新的方法
package main
import "fmt"
// 给自定义类型加方法
// 不能给别的包里面的类型添加方法,只能给自己的包里添加方法
type MyInt int
func (m MyInt)hello(){
fmt.Println("我是一个int")
}
func main(){
m := MyInt(100)
m.hello() // 我是一个int
}