目前接触到的语言层面上提供面向对象的实现,主要有三个基础:
type、method、interface
type:
go中没有java或c#的类(class),但有一个同等地位的type。用户可通过type关键字自定义需要的数据结构类型。
//自定义基本类型
type MyFloat float64
//自定义复合类型
type Vertex struct{
X,Y int
M,N string
}
...
var cInt = MyInt(64)
fmt.Println(cInt)
-->64
var cVertex = Vertex{1,2,"x","y"}
fmt.Println(cVertex)
-->{1 2 x y}
method:
当然,仅仅提供组织数据结构的能力,谈不上面向对象。go中所有通过type关键字定义的类型,都可以定义方法。
//自定义复合类型
type Vertex struct{
X,Y int
M,N string
}
//定义getX方法,返回实例的X,此时形参接收的是一个值
func (v Vertex) getX() int{
return v.X
}
//定义getX方法,返回实例的X,此时形参接收的是一个指针
func (v *Vertex) getX() int{
return v.X
}
...
fmt.Println(cVertex.getX())
-->1
其语法和一般的函数定义很类似,只是在func和方法名之间多了(v Vertex),即表示type主体的形参名及其类型,文档称之为receiver。如上,第一个getX方法的receiver称为value receiver,第二个称为getX方法的receiver称为pointer receiver。
这里要注意的是,调用一般函数时,传入的参数必须和形参一致:
//函数需要传入值
func getX_v(v Vertex){
return v.X
}
//函数需要传入指针
func getX_p(v *Vertex){
return v.X
}
...
var myVertex = Vertex{1,2,"x","y"}
fmt.Println(getX_v(myVertex)) //ok
fmt.Println(getX_p(myVertex)) //报错,因为传入的参数必须是指针
但是在类型的methods中,go会自动做一些处理,
//receiver 是个value
func (v Vertex) getX_v() int{
return v.X
}
//receiver 是个pointer
func (v *Vertex) getX_p() int{
return v.X
}
...
fmt.Println(myVertex.getX_v()) //ok
fmt.Println(myVertex.getX_p()) //ok
fmt.Println((&myVertex).getX_v()) //ok
fmt.Println((&myVertex).getX_p()) //ok
1.在调用方法时,以Vertex直接调用method(第一二种情况),以Vertex的指针调用method(第三四种情况),是等效的。
2.当方法被调用时,如果不涉及修改Vertex自身的值,无论receiver是value还是pointer,执行结果也是等效的。
上面代码中,执行myVertex.getX_p时,实际执行的是return (&v).X
上面代码中,执行(&myVertex).getX_v时,实际执行的是return (*v).X
3.在更多时候,给type添加方法时,我们往往使用的是pointer receiver,有两个原因:
一是pointer receiver是个指针,我们可以在方法中借之修改结构体自身的值。
二是value receiver得到的是结构体的一份拷贝,不能修改结构体自身的值,而且当结构体比较大而我们又不需要修改自身值时时,拷贝操作就有点低效了。
组合:
面向对象的一个有力特性是可以通过继承来复用一些函数和属性。go中的继承是通过类型的组合来实现的:
package main
import "fmt"
//定义了Base类型
type Base struct{
X,Y int
}
//Base类型添加方法
func (v Base) showMessage(){
fmt.Println("hello world")
}
//定义Super类,内部组合有Base类型
type Super struct{
Base
}
func main() {
var mySuper = Super{}
mySuper.showMessage() //ok
-->hello world
}
interface:
go中有一种特殊类型interface,是一系列方法的签名。interface变量自身并不实现这些方法,当一个类型提供了interface中所有方法的实现时,我们就说它满足这个interface。
//定义接口
type People interface{
call()
}
//定义某个类型
type Vertex struct{
X,Y int
}
//给Vertex添加call方法
func (v Vertex) call(){
fmt.Println("my name is xxx")
}
func main(){
//定义一个People变量,被赋予一个Vertex实例
var a People = Vertex{1,2}
a.call()
-->my name is xxx
}
和java、c#中的接口不太一样。java、c#中的接口,需要被类继承,并在类中实现这些方法,然后通过调用类的方法来使用。而go中的接口,虽然也是只提供方法签名,也是在类型中实现这些方法,但是需要把满足接口的类型的实例赋予interface变量,然后通过interface.method来调用。
而接口的底层,实际上可看作一个值及其类型(value , type)。value是赋予接口的具体实例,type是这个示例的类型。上边代码中接口a就是一个 ({1,2} , main.People)
一个值的注意的地方是,调用interface变量的方法时,是区分value receiver和pointer receiver的。
//定义接口
type Abser interface {
Abs() float64
}
//定义Vertex类型
type Vertex struct {
X, Y float64
}
//实现Abs方法,此时receiver是pointer receiver
func (v *Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
func main() {
var a Abser
var v = Vertex{3, 4}
a = &v
fmt.Println(a.Abs()) //不会报错,因为v *Vertex并实现了Abs()
a = v
fmt.Println(a.Abs()) //会报错,因为v Vertex并没有实现Abs()
}
go中interface的好处是:
1.方法的签名和实现解耦,interface甚至不关心方法的receiver是value还是pointer。如何实现由不同的类型自己决定,。
2.interface变量可以被复用,只要是实现了接口的类型,都可以被赋予该interface变量,这就很容易实现多态。
接口赋值:
接口赋值由两种情况,
第一种是将满足interface的类型的实例直接赋予interface变量。
第二种是将接口赋予另一个接口。
第一种情况有个需要注意的地方是,如果接口中的某个方法在实现时用的是pointer receiver,赋值的时候要用指针赋值:
package main
import "fmt"
//自定义类型和接口
type Integer int
type LessAdd interface{
Less(b Integer) bool
Add(b Integer)
}
//给Integer类型添加实现
func (i Integer) Less(b Integer) bool{
if i<b {
return true
}else{
return false
}
}
func (i *Integer) Add(b Integer){
*i += b
}
func main() {
var i Integer = 10
var b Integer = 11
var inf LessAdd
fmt.Println(i.Less(b))
i.Add(b)
fmt.Println(i)
inf = i
}
上面代码中,将类型Integer的实例赋予interface变量inf时,go编译报错:
./compile387.go:29:6: cannot use i (type Integer) as type LessAdd in assignment:
Integer does not implement LessAdd (Add method has pointer receiver)
这是因为Integer在实现接口方法时,Add方法用了pointer receiver,而此时interface变量接收的是一个值i而不是指针&i。结果就是interface中的两个方法,Less满足了,但Add不满足。
那为什么改为:
inf = &i
就不报错了呢?这是因为传入&i这个类型指针作为接口方法的receiver时,go可以根据函数
func (i Integer) Less(b Integer) bool{
生成函数
func (i *Integer) Less(b Integer) bool{
此时,Less函数和Add函数都有了,而且这两个函数的receiver都是指针。
但是反过来,go没办法根据
func (i *Integer) Add(b Integer){...}
生成
func (i Integer) Add(b Integer){...}
更何况,如果Add方法的内部实现需要修改指针所指内存的值,receiver由pointer改为value的话,就无法实现值的修改,这也不符合逻辑。
接口赋值的第二种情况,是接口给另一个接口赋值。比如:
interfaceA = interfaceB
此时要求,interfaceA的接口和interfaceB的接口有相同的方法列表。
或者interfaceB的方法列表是interfaceA的超集。