上一篇 Go圣经-学习笔记之defer和异常处理
方法声明
方法是对象的行为,在OOP中,我们一般称对象是由属性集合和行为集合共同组成,通过其行为修改对象属性。在Go语言中,方法的声明是在普通函数名字前加一个变量类型,就是一个方法。例如:
func (p Point) Distance(q Point) float64 { return math.Hypot(p.X-q.X, p.Y-q.Y) }
上面DEMO,q是表示方法的接收器,在其他语言中就是this或者self,表示对象自身。在Go语言中,我们可以任意地选择接收器的名字,一般官方建议是采购结构体类型的第一个字母
基于指针对象的方法
当调用一个函数时,会对其每一个参数j进行拷贝,如果一个函数需要更新变量,或者函数的某个参数占内存空间比较大,避免大内存拷贝,我们这时候就需要指针了。对应到方法,就是要采用指针接收器。例如:
func (p *Point) ScaleBy(factor float64) { p.X *= factor p.Y *= factor }
如果我们采用非指针对象修改对象的属性,如下:
type Person struct { Name string } func (p Person) ModifyName(name string) { p.Name = name return } func main() { var p = Person{Name: "chendonghai"} p.ModifyName("chenjixiang") fmt.Println(p.Name) } // 输出结果: /* chendonghai */
输出结果不是chenjixiang
的原因,是Person类型的对象接受者不是指针对象,所以调用方法时,发生了拷贝,所以产生的对象修改都是临时对象属性值的修改。
关于对象接收者,这里有两点关于编译器优化的说明:
- 如果对象接收者是指针类型数据,调用时使用的非指针对象,那么编译器会默认改为指针对象调用方法
- 如果对象接收者是非指针类型数据,调用时使用了指针对象,那么编译器会把指针对象改为非指针对象调用。
func (p Point) Distance(q Point) float64 { return 1.0 } var p = &Point{ ... } p.Distance(...) // 这编译器会把这个语句改为:(*p).Distance(...) func (p *Point) Distance(q Point) float64 { return 1.0 } var p = Point{ ... } p.Distance(...) // 则编译器会把指非指针对象改为指针对象: (&p).Distance(...)
说到这里,就要提一个与C++不同的地方:
type Person struct { Name string } func (p *Person) GetName() string { if p ==nil { return "" } return p.Name } func main(){ var p *Person fmt.Println(p.GetName()) }
C++ 这样做,必挂。它是通过vtable[]虚表做到的。在我写的最开始那篇文章,Go语言中不存在没有初始化的变量,每个数据类型数据都有对应的零值,比如:nil,所以调用时是合法的。C++叫做没有初始化的值。我在stackoverflow上提个这个问题,更具体,更权威的回答:nil值调用指针对象的方法
这个我经常遇到,例如:利用protoc生成Go语言版本的protocol buffer的数据模型文件,一定会存在。例如: 
go语言中,零值的类型数据读取时不会panic,引用类型、指针类型的零值写入时会panic。