golang 函数和方法
本文内容纲要:
-定义
-函数
-2可变参数列表
-异常panic
-方法
-值接收者和指针接收者
-非结构体类型的方法
-方法的值接收者和函数的值参数
-方法的指针接收者和函数的指针参数
由于自己是搞python开发的,所以在学习go时,当看到函数和方法时,顿时还是挺蒙的,因为在python中并没有明显的区别,但是在go中却是两个完全不同的东西。在官方的解释中,方法是包含了接收者的函数。
定义
函数的格式是固定的
Func+函数名+参数+返回值(可选)+函数体
Funcmain(a,bint)(int){
}
而方法会在方法在func关键字后是接收者而不是函数名,接收者可以是自己定义的一个类型,这个类型可以是struct,interface,甚至我们可以重定义基本数据类型。不过需要注意的是接收者是指针和非指针的区别,我们可以看到当接收者为指针式,我们可以通过方法改变该接收者的属性,但是非指针类型缺做不到。
func (p myint) mysquare() int {
p = p * p
fmt.Println("mysquare p = ", p)
return 0
}
函数
函数的值(闭包)
在Go中,函数被看作第一类值(first-classvalues):函数像其他值一样,拥有类型,可以被赋值给其他变量,传递给函数,从函数返回。函数类型的零值是nil。调用值为nil的函数值会引起panic错误:
varffunc(int)intf(3)//此处f的值为nil,会引起panic错误
函数值不仅仅是一串代码,还记录了状态。Go使用闭包(closures)技术实现函数值,Go程序员也把函数值叫做闭包。我们看个闭包的例子
funcf1(limitint)(func(vint)bool){
//编译器发现limit逃逸了,自动在堆上分配
returnfunc(limitint)bool{returnv>limit}
}
funcmain(){
closure:=f1(5)
fmt.Printf("%v\n",closure(1))//false
fmt.Printf("%v\n",closure(5))//false
fmt.Printf("%v\n",closure(10))//true
}
在这个例子中,f1函数传入limit参数,返回一个闭包,闭包接受一个参数v,判断v是否大于之前设置进去的limit。
2可变参数列表
在go中函数提供可变参数,对那些封装不确定参数个数是一个不错的选择。声明如下
func函数名(变量名...类型)返回值
packagemain
import(
"fmt"
)
funcf1(namestring,vals...int)(sumint){
for_,v:=rangevals{
sum+=v
}
sum+=len(name)
return
}
funcmain(){
fmt.Printf("%d\n",f1("abc",1,2,3,4))//13
}
在函数中提供延迟执行即defer
包含defer语句的函数执行完毕后(例如return、panic),释放堆栈前会调用被声明defer的语句,常用于释放资源、记录函数执行耗时等,有一下几个特点:
当defer被声明时,其参数就会被实时解析
执行顺序和声明顺序相反
defer可以读取有名返回值
运用最典型的场景及关闭资源,如操作文件,数据库操作等。如下例子
funcdo()error{
f,err:=os.Open("book.txt")
iferr!=nil{
returnerr
}
deferfunc(fio.Closer){
iferr:=f.Close();err!=nil{
//logetc
}
}(f)
//..code...
f,err=os.Open("another-book.txt")
iferr!=nil{
returnerr
}
deferfunc(fio.Closer){
iferr:=f.Close();err!=nil{
//logetc
}
}(f)
returnnil
}
异常panic
在开始闭包中提到过返回panic,那什么是panic。Go有别于那些将函数运行失败看作是异常的语言。虽然Go有各种异常机制,但这些机制仅仅用于严重的错误,而不是那些在健壮程序中应该被避免的程序错误。runtime在一些情况下会抛出异常,例如除0,我们也能使用panic关键字自己抛出异常。
出现异常,默认程序退出并打印堆栈。如下函数
packagemain
funcf6(){
func(){
func()int{
x:=0
y:=5/x
returny
}()
}()
}
funcmain(){
f6()
}
如果不想程序退出的话,也有办法,就是使用recover捕捉异常,然后返回error。在没发生panic的情况下,调用recover会返回nil,发生了panic,那么就是panic的值。看个例子:
packagemain
import(
"fmt"
)
typeshouldRecoverstruct{}
typeemptyStructstruct{}
funcf6()(errerror){
deferfunc(){
switchp:=recover();p{
casenil://donoting
caseshouldRecover{}:
err=fmt.Errorf("occurpanicbuthadrecovered")
default:
panic(p)
}
}()
func(){
func()int{
panic(shouldRecover{})
//panic(emptyStruct{})
x:=0
y:=5/x
returny
}()
}()
return
}
funcmain(){
err:=f6()
iferr!=nil{
fmt.Printf("fail%v\n",err)
}else{
fmt.Printf("success\n")
}
}
方法
packagemain
import(
"fmt"
)
typeEmployeestruct{
namestring
salaryint
currencystring
}
/*
displaySalary()methodhasEmployeeasthereceivertype
*/
func(eEmployee)displaySalary(){
fmt.Printf("Salaryof%sis%s%d",e.name,e.currency,e.salary)
}
funcmain(){
emp1:=Employee{
name:"SamAdolf",
salary:5000,
currency:"$",
}
emp1.displaySalary()//CallingdisplaySalary()methodofEmployeetype
}
也许有人会问,方法和函数差不多,为什么还要多此一举使用方法呢?
- Golang不是一个纯粹的面向对象的编程语言,它不支持类。因此通过在一个类型上建立方法来实现与class相似的行为。
- 同名方法可以定义在不同的类型上,但是Golang不允许同名函数。假设有两个结构体Square和Circle。在Square和Circle上定义同名的方法是合法的。
如下一个函数就很明了了
packagemain
import(
"fmt"
"math"
)
typeRectanglestruct{
lengthint
widthint
}
typeCirclestruct{
radiusfloat64
}
func(rRectangle)Area()int{
returnr.length*r.width
}
func(cCircle)Area()float64{
returnmath.Pi*c.radius*c.radius
}
funcmain(){
r:=Rectangle{
length:10,
width:5,
}
fmt.Printf("Areaofrectangle%d\n",r.Area())
c:=Circle{
radius:12,
}
fmt.Printf("Areaofcircle%f",c.Area())
}
值接收者和指针接收者
两者区别在于,以指针作为接收者时,方法内部进行的修改对于调用者是可见的,但是以值作为接收者却不是。
packagemain
import(
"fmt"
)
typeEmployeestruct{
namestring
ageint
}
/*
Methodwithvaluereceiver
*/
func(eEmployee)changeName(newNamestring){
e.name=newName
}
/*
Methodwithpointerreceiver
*/
func(e*Employee)changeAge(newAgeint){
e.age=newAge
}
funcmain(){
e:=Employee{
name:"MarkAndrew",
age:50,
}
fmt.Printf("Employeenamebeforechange:%s",e.name)
e.changeName("MichaelAndrew")
fmt.Printf("\nEmployeenameafterchange:%s",e.name)
fmt.Printf("\n\nEmployeeagebeforechange:%d",e.age)
(&e).changeAge(51)
fmt.Printf("\nEmployeeageafterchange:%d",e.age)
}
上面的程序中,changeName方法有一个值接收者(eEmployee),而changeAge方法有一个指针接收者(e*Employee)。在changeName中改变Employee的name的值对调用者而言是不可见的,因此程序在调用e.changeName("MichaelAndrew")方法之前和之后,打印的name是一样的。而changeAge的接受者是一个指针(e*Employee),因此通过调用方法(&e).changeAge(51)来修改age对于调用者是可见的。
使用(&e).changeAge(51)来调用changeAge方法不是必须的,Golang允许我们省略&符号,因此可以写为e.changeAge(51)。Golang将e.changeAge(51)解析为(&e).changeAge(51)。
非结构体类型的方法
现在我们定义的都是结构体类型的方法,同样可以定义非结构体类型的方法,不过需要注意一点。为了定义某个类型的方法,接收者类型的定义与方法的定义必须在同一个包中。
packagemain
import"fmt"
typemyIntint
func(amyInt)add(bmyInt)myInt{
returna+b
}
funcmain(){
num1:=myInt(5)
num2:=myInt(10)
sum:=num1.add(num2)
fmt.Println("Sumis",sum)
}
在函数和方法中都会接收值参数和指针参数,那么两者又有什么却别?
方法的值接收者和函数的值参数
当一个函数有一个值参数时,它只接受一个值参数。
当一个方法有一个值接收者时,它可以接受值和指针接收者。
如下一个例子
packagemain
import(
"fmt"
)
typerectanglestruct{
lengthint
widthint
}
funcarea(rrectangle){
fmt.Printf("AreaFunctionresult:%d\n",(r.length*r.width))
}
func(rrectangle)area(){
fmt.Printf("AreaMethodresult:%d\n",(r.length*r.width))
}
funcmain(){
r:=rectangle{
length:10,
width:5,
}
area(r)
r.area()
p:=&r
/*
compilationerror,cannotusep(type*rectangle)astyperectangle
inargumenttoarea
*/
//area(p)//会报错
p.area()//callingvaluereceiverwithapointer
}
我们创建了一个指向r的指针p。如果我们试图将这个指针传递给只接受值的area函数那么编译器将报错。
p.area()使用指针接收者p调用一个值接收者方法area。这是完全合法的。原因是对于p.area(),由于area方法必须接受一个值接收者,所以Golang将其解析为(*p).area()。
方法的指针接收者和函数的指针参数
具有指针参数的函数将仅接受指针,而具有指针接收者的方法将接受值和指针接收者
packagemain
import(
"fmt"
)
typerectanglestruct{
lengthint
widthint
}
funcperimeter(r*rectangle){
fmt.Println("perimeterfunctionoutput:",2*(r.length+r.width))
}
func(r*rectangle)perimeter(){
fmt.Println("perimetermethodoutput:",2*(r.length+r.width))
}
funcmain(){
r:=rectangle{
length:10,
width:5,
}
p:=&r//pointertor
perimeter(p)
p.perimeter()
/*
cannotuser(typerectangle)astype*rectangleinargumenttoperimeter
*/
//perimeter(r)
r.perimeter()//callingpointerreceiverwithavalue
}
试图以一个值参数r调用perimeter函数,这是非法的。因为一个接受指针为参数的函数不能接受一个值作为参数。如果去掉注释,则编译报错。
通过一个值接收者r调用一个指针接收者perimeter方法,这是合法的。r.perimeter()将被Golang解析为(&r).perimeter()。
本文内容总结:定义,函数,2可变参数列表,异常panic,方法,值接收者和指针接收者,非结构体类型的方法,方法的值接收者和函数的值参数,方法的指针接收者和函数的指针参数,
原文链接:https://www.cnblogs.com/flash55/p/10546501.html