Go语言之Go 语言方法
Go 语言方法
go 语言方法定义
方法介绍
在 Go 语言中有一个概念和函数极其相似,叫做方法 。Go 语言的方法其实是作用在接收者(receiver)上的一个函数,接收者是某种非内置类型的变量。因此方法是一种特殊类型的函数。
接收者类型可以是(几乎)任何类型,不仅仅是结构体类型:任何类型都可以有方法,甚至可以是函数类型,可以是 int、bool、string 或数组的别名类型。但是接收者不能是接口类型。
方法的声明和普通函数的声明类似,只是在函数名称前面多了一个参数,这个参数把这个方法绑定到这个参数对应的类型上。
方法定义
首先声明一个自定义类型Test
type Test struct{}
方法参数 receiver 类型可以是 Test 或 *Test。类型 Test不能是接口或指针。
第一种,定义一个无参数、无返回值的方法
func (t Test) method() { } func (t *Test) method() { }
第二种,定义一个单参数、无返回值的方法
func (t Test) method(i int) { } func (t *Test) method(i int) { }
第三种,定义一个多参数、无返回值的方法
func (t Test) method(x, y int) { } func (t *Test) method(x, y int) { }
第四种,定义一个无参数、单返回值的方法
func (t Test) method() (i int) { return } func (t *Test) method() (i int) { return }
第五种,定义一个多参数、多返回值的方法
func (t Test) method(x, y int) (z int, err error) { return } func (t *Test) method(x, y int) (z int, err error) { return }
方法和函数的关系
方法是特殊的函数,定义在某一特定的类型上,通过类型的实例来进行调用,这个实例被叫接收者。
接收者必须有一个显式的名字,这个名字必须在方法中被使用。 接收者类型必须在和方法同样的包中被声明。
注意: Go语言不允许为简单的内置类型添加方法,下面定义的方法是非法的。
package main import ( "fmt" ) //方法不能是内置数据类型 func (a int) Add(b int) { fmt.Println(a + b) }
编译错误:
cannot define new methods on non-local type int
我们可以用Go语言的type,来定义一个和int具有同样功能的类型。这个类型不能看成是int类型的别名,它们属于不同的类型,不能直接相互赋值。
合法的方法定义如下:
package main import ( "fmt" ) type myInt int func (a myInt) Add(b myInt) { fmt.Println(a + b) } func main() { var x, y myInt = 3, 6 x.Add(y) }
函数与方法的区别
1、对于普通函数,接收者为值类型时,不能将指针类型的数据直接传递,反之亦然。
2、对于方法(如struct的方法),接收者为值类型时,可以直接用指针类型的变量调用方法,反过来同样也可以。
Go 语言方法规则
根据调用者不同,方法分为两种表现形式:方法(method value)、方法表达式(method expression)。
两者都可像普通函数那样赋值和传参,区别在于 方法 (method value)绑定了实例,而方法表达式(method expression)必须显式传参。
直接调用
直接调用,类型 T 和 *T 上的方法集是互相继承的。
package main import ( "fmt" ) type T struct { int } func (t T) testT() { fmt.Println("接受者为 T ") } func (t *T) testP() { fmt.Println("接受者为 *T ") } func main() { t1 := T{1} fmt.Printf("t1 is : %v\n", t1) t1.testT() t1.testP() t2 := &t1 fmt.Printf("t2 is : %v\n", t2) t2.testT() t2.testP() }
直接调用,类型 S 包含匿名字段 *T 或 T ,则 S 和 *S 方法集包含 T 和 *T 上的方法集是互相继承的。
package main import ( "fmt" ) type ST struct { T } type SP struct { *T } type T struct { int } func (t T) testT() { fmt.Println("类型 S 包含匿名字段 *T 或 T ,则 S 和 *S 方法集包含 T 方法") } func (t *T) testP() { fmt.Println("类型 S 包含匿名字段 *T 或 T ,则 S 和 *S 方法集包含 *T 方法") } func main() { st1 := ST{T{1}} st2 := &st1 fmt.Printf("st1 is : %v\n", st1) st1.testT() st1.testP() fmt.Printf("st2 is : %v\n", st2) st2.testT() st2.testP() sp1 := SP{&T{1}} sp2 := &sp1 fmt.Printf("sp1 is : %v\n", sp1) sp1.testT() sp1.testP() fmt.Printf("sp2 is : %v\n", sp2) sp2.testT() sp2.testP() }
隐式传递调用
接受者隐式传递,类型 T 和 *T 上的方法集是互相继承的。
package main import ( "fmt" ) type T struct { string } func (t T) testT() { fmt.Println("接受者为 T ") } func (t *T) testP() { fmt.Println("接受者为 *T ") } func main() { t := T{"oldboy"} methodValue1 := t.testT methodValue1() methodValue2 := (&t).testT methodValue2() methodValue3 := t.testP methodValue3() methodValue4 := (&t).testP methodValue4() }
接受者隐式传递,类型 S 包含匿名字段 *T 或 T ,则 S 和 *S 方法集包含 T 和 *T 上的方法集是互相继承的。
package main import ( "fmt" ) type ST struct { T } type SP struct { *T } type T struct { string } func (t T) testT() { fmt.Println("类型 S 包含匿名字段 *T 或 T ,则 S 和 *S 方法集包含 T 方法") } func (t *T) testP() { fmt.Println("类型 S 包含匿名字段 *T 或 T ,则 S 和 *S 方法集包含 *T 方法") } func main() { st1 := ST{T{"oldboy"}} methodValue1 := st1.testT methodValue1() methodValue2 := (&st1).testT methodValue2() methodValue3 := st1.testP methodValue3() methodValue4 := (&st1).testP methodValue4() sp1 := SP{&T{"oldboy"}} methodValue5 := sp1.testT methodValue5() methodValue6 := (&sp1).testT methodValue6() methodValue7 := sp1.testP methodValue7() methodValue8 := (&sp1).testP methodValue8() }
显式传递调用
接受者显示传值,类型 T 的可调用方法集包含接受者为 T 的所有方法,不包含接受者为 *T 的方法。类型 *T 的可调用方法集包含接受者为 *T 或 T 的所有方法集。
package main import ( "fmt" ) type T struct { string } func (t T) testT() { fmt.Println("接受者为 T ") } func (t *T) testP() { fmt.Println("接受者为 *T ") } func main() { t := T{"oldboy"} expression1 := T.testT expression1(t) expression2 := (*T).testT expression2(&t) // expression3 := T.testP // expression3(t) expression4 := (*T).testP expression4(&t) }
接受者显示传值,类型 S 包含匿名字段 *T ,则 S 和 *S 方法集包含 T 和 *T 上的方法集是互相继承的。
类型 S 包含匿名字段 T ,类型 S 的可调用方法集包含接受者为 T 的所有方法,不包含接受者为 *T 的方法。类型 *S 的可调用方法集包含接受者为 *T 或 T 的所有方法集。
package main import ( "fmt" ) type ST struct { T } type SP struct { *T } type T struct { string } func (t T) testT() { fmt.Println("类型 S 包含匿名字段 *T 或 T ,则 S 和 *S 方法集包含 T 方法") } func (t *T) testP() { fmt.Println("类型 S 包含匿名字段 *T 或 T ,则 S 和 *S 方法集包含 *T 方法") } func main() { st1 := ST{T{"oldboy"}} expression1 := ST.testT expression1(st1) expression2 := (*ST).testT expression2(&st1) // expression3 := ST.testP // expression3(st1) expression4 := (*ST).testP expression4(&st1) sp1 := SP{&T{"oldboy"}} expression5 := SP.testT expression5(sp1) expression6 := (*SP).testT expression6(&sp1) expression7 := SP.testP expression7(sp1) expression8 := (*SP).testP expression8(&sp1) }
Go 语言方法应用
匿名字段
Go语言支持只提供类型,而不写字段名的方式,也就是匿名字段,也称为嵌入字段。
当匿名字段是一个struct的时候,那么这个struct所拥有的全部字段都被隐式地引入了当前定义的这个struct。
Go语言匿名字段可以像字段成员那样访问匿名字段方法,编译器负责查找。
package main import "fmt" type Student struct { id int name string } type Course struct { Student } func (self *Student) ToString() string { return fmt.Sprintf("Student: %p, %v", self, self) } func main() { c := Course{Student{1, "oldboy"}} fmt.Printf("Course: %p\n", &c) fmt.Println(c.ToString()) }
运行结果:
Class: 0xc0420023e0 User: 0xc0420023e0, &{1 oldboy}
Go 语言不像其它面相对象语言一样可以写个类,然后在类里面写一堆方法,但其实Go语言的方法很巧妙的实现了这种效果:我们只需要在普通函数前面加个接受者(receiver,写在函数名前面的括号里面),这样编译器就知道这个函数(方法)属于哪个struct了。
继承复用
Go语言中没有继承,但是可以依靠组合来模拟继承和多态。
通过匿名字段,可获得和继承类似的复用能力。依据编译器查找次序,只需在外层定义同名方法,就可以实现。
package main import "fmt" type Student struct { id int name string } type Course struct { Student title string } func (self *Student) ToString() string { return fmt.Sprintf("Student: %p, %v", self, self) } func (self *Course) ToString() string { return fmt.Sprintf("Course: %p, %v", self, self) } func main() { c := Course{Student{1, "oldboy"}, "Golang"} fmt.Println(c.ToString()) fmt.Println(c.Student.ToString()) }
运行结果:
Course: 0xc04207e060, &{{1 oldboy} Golang} Student: 0xc04207e060, &{1 oldboy}
自定义ERROR
错误是可以用字符串描述自己的任何东西。 可以由预定义的内建接口类型 error,和其返回字符串的方法 Error 构成。
type error interface { Error() string }
当用 fmt 包的多种不同的打印函数输出一个 error 时,会自动的调用该方法。
package main import ( "fmt" "os" "time" ) type PathError struct { path string op string createTime string message string } func (p *PathError) Error() string { return fmt.Sprintf("path=%s \nop=%s \ncreateTime=%s \nmessage=%s", p.path, p.op, p.createTime, p.message) } func Open(filename string) error { file, err := os.Open(filename) if err != nil { return &PathError{ path: filename, op: "read", message: err.Error(), createTime: fmt.Sprintf("%v", time.Now()), } } defer file.Close() return nil } func main() { err := Open("/oldboy/golang.go") switch v := err.(type) { case *PathError: fmt.Println("get path error,", v) default: } }