Golang 速成 | 面向对象特征
方法
假设有两个方法,一个方法的接收者是指针类型,一个方法的接收者是值类型,那么:
- 对于值类型的变量和指针类型的变量,这两个方法有什么区别?
- 如果这两个方法是为了实现一个接口,那么这两个方法都可以调用吗?
- 如果方法是嵌入到其他结构体中的,那么上面两种情况又是怎样的?
Go |
---|
| package main
import "fmt"
//定义一个结构体
type T struct {
name string
}
func (t T) method1() {
t.name = "new name1"
}
func (t *T) method2() {
t.name = "new name2"
}
func main() {
t := T{"old name"}
fmt.Println("method1 调用前 ", t.name)
t.method1()
fmt.Println("method1 调用后 ", t.name)
fmt.Println("method2 调用前 ", t.name)
t.method2()
fmt.Println("method2 调用后 ", t.name)
}
|
Output |
---|
| method1 调用前 old name
method1 调用后 old name
method2 调用前 old name
method2 调用后 new name2
|
当调用 t.method1()
时相当于 method1(t)
,实参和形参都是类型 T,可以接受。此时在 method1()
中的 t 只是参数 t 的值拷贝,所以 method1()
的修改影响不到 main 中的 t 变量。
当调用 t.method2()
=> method2(t)
,这是将 T
类型传给了 *T
类型,go 可能会取 t 的地址传进去:method2(&t)
。所以 method1()
的修改可以影响 t 。
T 类型的变量这两个方法都是拥有的。
方法值和方法表达式
方法值
我们经常选择一个方法,并且在同一个表达式里执行,比如常见的 p.Distance()
形式,实际上将其分成两步来执行也是可能的。p.Distance
叫作“选择器”,选择器会返回一个方法"值"一个将方法 (Point.Distance) 绑定到特定接收器变量的函数。这个函数可以不通过指定其接收器即可被调用;即调用时不需要指定接收器,只要传入函数的参数即可:
Go |
---|
| package main
import "fmt"
import "math"
type Point struct{ X, Y float64 }
//这是给struct Point类型定义一个方法
func (p Point) Distance(q Point) float64 {
return math.Hypot(q.X-p.X, q.Y-p.Y)
}
func main() {
p := Point{1, 2}
q := Point{4, 6}
distanceFormP := p.Distance // 方法值(相当于C语言的函数地址,函数指针)
fmt.Println(distanceFormP(q)) // "5"
fmt.Println(p.Distance(q)) // "5"
//实际上distanceFormP 就绑定了 p接收器的方法Distance
distanceFormQ := q.Distance //
fmt.Println(distanceFormQ(p)) // "5"
fmt.Println(q.Distance(p)) // "5"
//实际上distanceFormQ 就绑定了 q接收器的方法Distance
}
|
在一个包的 API 需要一个函数值且调用方希望操作的是某一个绑定了对象的方法的话,方法"值"会非常实用。
举例来说,下面例子中的 time.AfterFunc
这个函数的功能是在指定的延迟时间之后来执行一个 (译注:另外的) 函数。且这个函数操作的是一个 Rocket 对象 r :
Go |
---|
| type Rocket struct { /* ... */ }
func (r *Rocket) Launch() { /* ... */ }
r := new(Rocket)
time.AfterFunc(10 * time.Second, func() { r.Launch() })
|
直接用方法"值"传入 AfterFunc 的话可以更为简短:
Go |
---|
| time.AfterFunc(10 * time.Second, r.Launch)
|
省掉了上面那个例子里的匿名函数。
方法表达式
和方法"值"相关的还有方法表达式。当调用一个方法时,与调用一个普通的函数相比,我们必须要用选择器 (p.Distance) 语法来指定方法的接收器。
当 T 是一个类型时,方法表达式可能会写作 T.f
或者 (*T).f
,会返回一个函数"值",这种函数会将其第一个参数用作接收器,所以可以用通常 (译注:不写选择器) 的方式来对其进行调用:
Go |
---|
| package main
import "fmt"
import "math"
type Point struct{ X, Y float64 }
//这是给struct Point类型定义一个方法
func (p Point) Distance(q Point) float64 {
return math.Hypot(q.X-p.X, q.Y-p.Y)
}
func main() {
p := Point{1, 2}
q := Point{4, 6}
distance1 := Point.Distance //方法表达式, 是一个函数值(相当于C语言的函数指针)
fmt.Println(distance1(p, q))
fmt.Printf("%T\n", distance1) //%T表示打出数据类型 ,这个必须放在Printf使用
distance2 := (*Point).Distance //方法表达式,必须传递指针类型
distance2(&p, q)
fmt.Printf("%T\n", distance2)
}
|
Output |
---|
| 5
func(main.Point, main.Point) float64
func(*main.Point, main.Point) float64
// 这个Distance实际上是指定了Point对象为接收器的一个方法func (p Point) Distance(),
// 但通过Point.Distance得到的函数需要比实际的Distance方法多一个参数,
// 即其需要用第一个额外参数指定接收器,后面排列Distance方法的参数。
// 看起来本书中函数和方法的区别是指有没有接收器,而不像其他语言那样是指有没有返回值。
|
当你根据一个变量来决定调用同一个类型的哪个函数时,方法表达式就显得很有用了。你可以根据选择来调用接收器各不相同的方法。下面的例子,变量 op 代表 Point 类型的 addition 或者 subtraction 方法,Path.TranslateBy 方法会为其 Path 数组中的每一个 Point 来调用对应的方法:
Go |
---|
| package main
import "fmt"
import "math"
type Point struct{ X, Y float64 }
//这是给struct Point类型定义一个方法
func (p Point) Distance(q Point) float64 {
return math.Hypot(q.X-p.X, q.Y-p.Y)
}
func (p Point) Add(another Point) Point {
return Point{p.X + another.X, p.Y + another.Y}
}
func (p Point) Sub(another Point) Point {
return Point{p.X - another.X, p.Y - another.Y}
}
func (p Point) Print() {
fmt.Printf("{%f, %f}\n", p.X, p.Y)
}
//定义一个Point切片类型 Path
type Path []Point
//方法的接收器 是Path类型数据, 方法的选择器是TranslateBy(Point, bool)
func (path Path) TranslateBy(another Point, add bool) {
var op func(p, q Point) Point //定义一个 op变量 类型是方法表达式 能够接收Add,和 Sub方法
if add == true {
op = Point.Add //给op变量赋值为Add方法
} else {
op = Point.Sub //给op变量赋值为Sub方法
}
for i := range path {
//调用 path[i].Add(another) 或者 path[i].Sub(another)
path[i] = op(path[i], another)
path[i].Print()
}
}
func main() {
points := Path{
{10, 10},
{11, 11},
}
anotherPoint := Point{5, 5}
points.TranslateBy(anotherPoint, false)
fmt.Println("------------------")
points.TranslateBy(anotherPoint, true)
}
|
Output |
---|
| {5.000000, 5.000000}
{6.000000, 6.000000}
------------------
{10.000000, 10.000000}
{11.000000, 11.000000}
|
源码
Go 结构体 |
---|
| package main
import "fmt"
//声明一种行的数据类型 myint, 是int的一个别名
type myint int
//定义一个结构体
type Book struct {
title string
auth string
}
func changeBook(book Book) {
//传递一个book的副本
book.auth = "666"
}
func changeBook2(book *Book) {
//指针传递
book.auth = "777"
}
func main() {
/*
var a myint = 10 fmt.Println("a = ", a) fmt.Printf("type of a = %T\n", a) */
var book1 Book
book1.title = "Golang"
book1.auth = "zhang3"
fmt.Printf("%v\n", book1)
changeBook(book1)
fmt.Printf("%v\n", book1)
changeBook2(&book1)
fmt.Printf("%v\n", book1)
}
|
Go 类与对象 |
---|
| package main
import "fmt"
//如果类名首字母大写,表示其他包也能够访问
type Hero struct {
//如果说类的属性首字母大写, 表示该属性是对外能够访问的,否则的话只能够类的内部访问
Name string
Ad int
level int
}
/*
func (this Hero) Show() {
fmt.Println("Name = ", this.Name) fmt.Println("Ad = ", this.Ad) fmt.Println("Level = ", this.Level)}
func (this Hero) GetName() string {
return this.Name}
func (this Hero) SetName(newName string) {
//this 是调用该方法的对象的一个副本(拷贝)
this.Name = newName}
*/
func (this *Hero) Show() {
fmt.Println("Name = ", this.Name)
fmt.Println("Ad = ", this.Ad)
fmt.Println("Level = ", this.level)
}
func (this *Hero) GetName() string {
return this.Name
}
func (this *Hero) SetName(newName string) {
//this 是调用该方法的对象的一个副本(拷贝)
this.Name = newName
}
func main() {
//创建一个对象
hero := Hero{Name: "zhang3", Ad: 100}
hero.Show()
hero.SetName("li4")
hero.Show()
}
|
Go 继承 |
---|
| package main
import "fmt"
type Human struct {
name string
sex string
}
func (this *Human) Eat() {
fmt.Println("Human.Eat()...")
}
func (this *Human) Walk() {
fmt.Println("Human.Walk()...")
}
//=================
type SuperMan struct {
Human //SuperMan类继承了Human类的方法
level int
}
//重定义父类的方法Eat()
func (this *SuperMan) Eat() {
fmt.Println("SuperMan.Eat()...")
}
//子类的新方法
func (this *SuperMan) Fly() {
fmt.Println("SuperMan.Fly()...")
}
func (this *SuperMan) Print() {
fmt.Println("name = ", this.name)
fmt.Println("sex = ", this.sex)
fmt.Println("level = ", this.level)
}
func main() {
h := Human{"zhang3", "female"}
h.Eat()
h.Walk()
//定义一个子类对象
//s := SuperMan{Human{"li4", "female"}, 88}
var s SuperMan
s.name = "li4"
s.sex = "male"
s.level = 88
s.Walk() //父类的方法
s.Eat() //子类的方法
s.Fly() //子类的方法
s.Print()
}
|
Go 多态 |
---|
| package main
import "fmt"
//本质是一个指针
type AnimalIF interface {
Sleep()
GetColor() string //获取动物的颜色
GetType() string //获取动物的种类
}
//具体的类
type Cat struct {
color string //猫的颜色
}
func (this *Cat) Sleep() {
fmt.Println("Cat is Sleep")
}
func (this *Cat) GetColor() string {
return this.color
}
func (this *Cat) GetType() string {
return "Cat"
}
//具体的类
type Dog struct {
color string
}
func (this *Dog) Sleep() {
fmt.Println("Dog is Sleep")
}
func (this *Dog) GetColor() string {
return this.color
}
func (this *Dog) GetType() string {
return "Dog"
}
func showAnimal(animal AnimalIF) {
animal.Sleep() //多态
fmt.Println("color = ", animal.GetColor())
fmt.Println("kind = ", animal.GetType())
}
func main() {
var animal AnimalIF //接口的数据类型, 父类指针
animal = &Cat{"Green"}
animal.Sleep() //调用的就是Cat的Sleep()方法 , 多态的现象
animal = &Dog{"Yellow"}
animal.Sleep() // 调用Dog的Sleep方法,多态的现象
cat := Cat{"Green"}
dog := Dog{"Yellow"}
showAnimal(&cat)
showAnimal(&dog)
}
|
Go interface{} |
---|
| package main
import "fmt"
//interface{}是万能数据类型
func myFunc(arg interface{}) {
fmt.Println("myFunc is called...")
fmt.Println(arg)
//interface{} 改如何区分 此时引用的底层数据类型到底是什么?
//给 interface{} 提供 “类型断言” 的机制
value, ok := arg.(string)
if !ok {
fmt.Println("arg is not string type")
} else {
fmt.Println("arg is string type, value = ", value)
fmt.Printf("value type is %T\n", value)
}
}
type Book struct {
auth string
}
func main() {
book := Book{"Golang"}
myFunc(book)
myFunc(100)
myFunc("abc")
myFunc(3.14)
}
|