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
}
posted @ 2020-02-11 16:01  jasonminghao  阅读(404)  评论(0编辑  收藏  举报