一. 方法定义
Golang方法总是绑定对象的实例,并隐式将实例作为第一实参。
- 只能为当前包内命名类型定义方法
- 参数receiver可以任意命名。如方法中未曾使用,可省略参数名
- 参数receiver类型可以是T或*T。基类型T不能是接口或指针类型(即多级指针)
- 不支持方法重载,receiver只是参数前面的组成部分
- 可用实例value或pointer调用全部方法,编译器自动转换
一个方法就是一个包含接收者(receiver)的函数,接收者(receiver)可以是命名类型或者结构体类型的一个值或者是一个指针。
所有给定类型的方法属于该类型的方法集。
1.1 方法定义
func (reveicer type) methodName(参数列表)(返回值列表){}//参数和返回值可以省略
package maintype test struct{}// 接收者为值类型,无参,无返回值
func (t test) method0() {}// 接收者为值类型,单参,无返回值
func (t test) method1(x int) {}// 接收者为值类型,无参,单返回值
func (t test) method2() (res int) { return }// 接收者为值类型,多参,无返回值
func (t test) method3(x, y int) {}// 接收者为值类型,无参,多返回值
func (t test) method4() (res1, res2 int) { return }// 接收者为值类型,多参,多返回值
func (t test) method5(x, y int) (res1, res2 int) { return }// 接收者为指针类型,无参,无返回值
func (t *test) method6() {}// 接收者为指针类型,单参,无返回值
func (t *test) method7(x int) {}// 接收者为指针类型,无参,单返回值
func (t *test) method8() (res int) { return }// 接收者为指针类型,多参,无返回值
func (t *test) method9(x, y int) {}// 接收者为指针类型,无参,多返回值
func (t *test) method10() (res1, res2 int) { return }// 接收者为指针类型,多参,多返回值
func (t *test) method11(x, y int) (res1, res2 int) { return }func main(){}
下面定义了一个结构体类型和它的方法。
解释:首先我们定义了一个叫做User的结构体类型,然后定义了一个该类型的方法Notice,该方法的接收者是一个User类型的值。要调用Notice方法,我们需要一个User类型的值或者指针。
在例子中,当我们使用指针时,Go调整成解引用使得调用可以被执行。
注意:当接收者不是一个指针时,该方法操作对应接收者值的副本。意思是即使你使用指针调用方法,但方法的接收者是值类型,方法内部的操作还是对副本进行操作,不会修改原来的值。
我们修改Notice方法,让他的接收者变成指针类型。
注意:当接收者是指针,即使使用值类型调用方法,方法内部也是对指针操作。会修改原来的值。
- 方法不过是特殊的函数,只需要将其还原,就知道receiver T和*T的差别
1.2 普通函数与方法区别
- 对于普通函数,接收者为值类型时,不能将指针类型的数据直接传递,反之亦然
- 对于方法,接收者为值类型,可以直接使用指针类型的变量调用方法,反过来也可以。
package mainimport "fmt"//普通函数
//接收者为值类型
func valueIntTest(a int) int {return a + 10
}//接收者为指针类型
func pointIntTest(a *int) int {return *a + 10
}func testValue() {a := 10fmt.Println(valueIntTest(a))//不能传递指针 如:valueIntTest(&a)b := 20fmt.Println(pointIntTest(&b))//不能传递值 如:pointIntTest(b)
}//结构体
type Data struct {x int
}func (d Data) valueShowData() {fmt.Println(d.x)
}func (d *Data) pointShowData() {fmt.Println(d.x)
}func testStruct() {//值类型调用方法d1 := Data{x: 10}d1.valueShowData()d1.pointShowData()//指针类型调用方法d2 := &Data{x: 100}d2.valueShowData()d2.pointShowData()
}func main() {testValue()testStruct()
}
二. 匿名字段
Golang匿名字段:可以像字段成员那样访问匿名字段方法,编译器负责查找。
Go语言是通过嵌套来实现继承的,依据编译器查找次序,只需要在外层定义同名方法,就可以实现继承的隐藏。
三. 方法集
Golang的方法集:每个类型都有与之关联的方法集,这会影响到接口的实现规则。
- 类型T方法集包含全部receiver T的方法
- 类型*T方法集包含全部receiver T + *T方法
- 如类型S包含匿名字段T,则S和*S方法集包含T的方法
- 如类型S包含匿名字段*T,则S和*S方法集包含T + *T方法
- 不管嵌入T或*T,*S方法集总是包含T + *T方法
用实例value和pointer调用方法(含其它自定义类型)不受方法集的约束,编译器总是查找全部方法,并自动转换reveiver实参。
方法集和调用方法没有关系。方法集是Go语言中实现面向对象编程特性的关键概念之一,它允许开发者为自定义类型定义行为,并通过接口来实现多态。同时,方法集也是Go语言类型系统和接口系统中不可或缺的一部分。
- Go语言中内部类型方法提升的规则
类型T方法集包含全部receiver T的方法。
类型*T方法集包含全部receiver T + *T方法。
给定结构体类型S和一个命名为T的类型,方法提升像下面规定的这样被包含在结构体方法集中:
如类型S包含匿名字段T,则S和*S方法集包含T方法。
这条规则说的是当我们嵌入一个值类型,嵌入类型的接收者为值类型的方法将被提升,可以被外部类型的值和指针调用。
如类型S包含匿名字段*T,则S和*S方法集包含T+*T方法。
这条规则说的是当我们嵌入一个类型的指针,嵌入类型的接收者为值类型或者指针类型的方法将被提升,可以被外部类型的值或指针调用。
四. 表达式
Golang表达式:根据调用者不同,方法分为两种表现形式。
- object.method(arg...)
- type.method(arg...)
前者称为method value,后者method expression。两者都可像普通函数那样赋值和传参,区别在于method value绑定实例,而method expression则需显示传参。
- method value会复制receiver
在汇编层面上,method value和闭包的实现方式相同,实际返回FuncVal类型对象。
FuncVal { method_address, receiver_copy }
- 可依据方法集转换method expression,注意receiver类型的差异
- 将方法还原成函数,就是下面的代码
package maintype User struct{}func (User) TestValue() {}func (*User) TestPoint() {}func main() {var u *User = nilu.TestPoint()(*User)(nil).TestPoint() //method value,对象为nil(*User).TestPoint(nil) //method expression,传的对象为nil
}
五. 自定义error
5.1 抛异常和处理异常
5.1.1 抛异常
package mainimport "fmt"//系统抛异常
func test01() {a := [5]int{1, 2, 3, 4}a[1] = 123fmt.Println(a)index := 10a[index] = 10fmt.Println(a)
}func getCircleArea(radius float32) float32 {if radius < 0 {panic("半径不能为负") //自己抛异常}return 3.14 * radius * radius
}func test02() {getCircleArea(-5)
}//捕获异常
func test03() {//回收异常defer func() {if err := recover(); err != nil {fmt.Println(err)}}()test02()fmt.Println("这里不执行")
}func test04() {test03()fmt.Println("test04")
}func main() {test04()test01()
}
5.1.2 返回异常
5.2 自定义错误