Go语言面向对象编程

版权声明:博客仅作为博主的个人笔记,未经博主允许不得转载。 https://blog.csdn.net/qq_35976351/article/details/81838817

系统类型

为类型添加方法

Go语言的类型,除了指针之外,都可以组合出自己的方法。

type Integer int

func (a Integer) Less(b Integer) bool {
    return a < b
}

func Ingeter_Less(a Integer, b Integer) bool {
    return a < b
}

func main() {
    var a Integer = 1
    if a.Less(2) {
        fmt.Println(a, "less 2")
    }
    if Ingeter_Less(a, 2) {
        fmt.Println(a, "less 2")
    }
}

上述的两种写法是等价的,只是暴露了C++的隐藏this指针的行为。因此,这是go语言面向对象的最基本的操作。Go语言面向对象操作时,一切都是传值操作,若要改变原来对象的值,则需要传递指针,这与C语言类似,例如:

type Integer int

func (a *Integer) Add(b Integer) {  // 注意这里的指针符号
    *a += b
}

func main() {
    var a Integer = 1
    a.Add(3)
    fmt.Println(a)
}

值语义和引用语义

值语义可以理解为C语言的赋值操作,等效成复制了原来数据的一个副本。Go语言的基本类型都是值语义,比如intboolfloat32等;复合类型也是值语义,比如数组、指针、结构体等。引用语义就是相当于指针的操作,或者C++的语义,在Go语言中一般借助指针实现。

Go语言的数组是赋值的,这与C语言不同。

var a = [3]int{1, 2, 3}
b := a

那么ab的数据相等,但是b复制了a的值,改变b不会影响a!!

但是可以使用指针的方式:

func main() {
    var a = [3]int{1, 2, 3}
    b := &a         // 注意取地址
    b[0] = 10
    fmt.Println(a)  // 10 2 3
}

Go语言的4种引用类型:

  • 数组切片:指向数组的区间
  • map字典:键值查询
  • channel:执行体间的通信设施
  • 接口(Interface)

结构体

Go语言的结构体和其他语言的类是同等地位的,Go的结构体组合出不同的方法,完成面向对象的过程。Go语言的面向对象的过程中,只保留了组合的特性,抛弃了继承等的其余的特性。

初始化

常规的方式:

type Rect struct {
    x, y          float64
    width, height float64
}

func (r *Rect) Area() float64 {
    return r.width * r.height
}

func main() {
    // 4种初始化方式,之一后3种的取地址符号
    rect1 := new(Rect)
    rect2 := &Rect{}
    rect3 := &Rect{0, 0, 100, 200}
    rect4 := &Rect{width: 100, height: 200}
}

自定义初始化函数。因为Go语言没有构造函数,我们可以创建一个全局函数完成构造的功能。完整版代码:

package main
import "fmt"

type Rect struct {
    x, y          float64
    width, height float64
}

func (r *Rect) Area() float64 {
    return r.width * r.height
}

func NewRect(x, y, width, height float64) (rect *Rect) {
    return &Rect{x, y, width, height}
}

func main() {
    rect4 := NewRect(0, 0, 100, 200)
    fmt.Println(rect4.Area())
}

匿名组合

Go语言的组合文法可以理解成继承,但是这个“继承”的过程是透明的,我们可以直观的看出整个“继承”的结构。匿名组合有值类型和指针类型。因为组合的内部是没有名字的,只有一个结构名,所以匿名。。GoLang的这种方式称为类型嵌入。

值类型

import "fmt"

type Base struct {  // 基类
    Name string
}

// 给Base组合两个方法
func (base *Base) Foo() {  
    fmt.Println("Base Foo")
}
func (base *Base) Bar() {
    fmt.Println("Base Bar")
}

type Derived struct {  // Foo组合一个Base类
    Base     
}

func (foo *Derived) Bar() {  // 扩展自己的Bar方法
    foo.Base.Bar()       // 调用组合的Base的Bar方法
    fmt.Println("Derived Bar")
}

func (foo *Derived) Foo() {  // 扩展自己的Foo方法
    fmt.Println("Derived Foo")
}

func main() {
    derived := &Derived{}
    derived.Base.Bar()
    derived.Base.Foo()
    derived.Bar()
    derived.Foo()
}
/*
输出结果
Base Bar
Base Foo
Base Bar
derived Bar
derived Foo
*/

“派生类”的方法没有改写“基类”的方法,因此“基类”的特性就被保留了下来。

注意防止出现死循环的方法,以上述的Base为例:

func (base *Base) Foo() {
    base.Foo()                // 出现了死循环,该函数永远无法跳出!
}

指针类型

package main

import "fmt"

type Base struct {
    Name string
}

func (base *Base) Foo() {
    fmt.Println("base Foo")
}

type Derived struct {
    *Base
}

func (derived *Derived) Foo() {
    fmt.Println("Derived Foo")
}

func main() {
    base := Base{"base"}
    derived := Derived{&base}
    base.Foo()
    derived.Foo()
    derived.Base.Foo()
}
/*
输出结果:
base Foo
Derived Foo
base Foo
*/

二者的区别

区别在于直接嵌入相当于复制一遍,而指针的就是传入地址,节省空间,对于原来对象的直接使用。

这个例子参考了这个问答

package main

import "fmt"

type Animal struct {
    Name string
}

type Person struct {
    Animal
}

type pPerson struct {
    *Animal
}

func main() {
    animal := Animal{Name: "cat"}
    person := Person{animal}
    pperson := pPerson{&animal}
    fmt.Println("Animal:" + animal.Name)
    fmt.Println("Person:" + person.Name)
    fmt.Println("pPerson:" + pperson.Name)
    fmt.Println("------------我是卖萌分割线------------")
    animal.Name = "Dog"
    fmt.Println("Animal:" + animal.Name)
    fmt.Println("Person:" + person.Name)
    fmt.Println("pPerson:" + pperson.Name)
    // 两者的地址不同,相当于复制了一份
    fmt.Println("animal == person? ", animal == person.Animal)
    // 两者地址相同
    fmt.Println("&animal == pperson.Animal? ", &animal == pperson.Animal)
}
/*
输出结果:
Animal:cat
Person:cat
pPerson:cat
------------我是卖萌分割线------------
Animal:Dog
Person:cat
pPerson:Dog
animal == person?  false
&animal == pperson.Animal?  true
*/

可见性

接口

本文主要参考了这篇文章

interface的定义

interface看成一组方法的集合,interface本身只是包含了一组方法,但是并不实现这一组方法。Go语言中没有显式地实现接口的关键字(比如Java中的implements),只要某个对象实现了某个接口中所有的方法,那么这个对象就实现了这个接口。

代码实例:

package main

import "fmt"

type Duck interface {
    Sing()
    Eat()
}

type YellowDuck struct {
    Sex string
}

func (yd YellowDuck) Sing() {
    fmt.Println("Yellow duck is singing")
}

func (yd YellowDuck) Eat() {
    fmt.Println("Yellow duck eats rice")
}

func (yd YellowDuck) LayEgg() {
    fmt.Println("Yellow duck lays an egg")
}

type DonaldDuck struct {
    Name string
}

func (dd DonaldDuck) Sing() {
    fmt.Println("Donald Duck is singing")
}

func (dd DonaldDuck) Eat() {
    fmt.Println("Donald Duck eats sugars")
}

func (dd DonaldDuck) Speak() {
    fmt.Println("Donald Duck speaks English")
}

func main() {
    yellowDuck := YellowDuck{Sex: "female"}
    donaldDuck := DonaldDuck{Name: "Donald DUck"}
    yellowDuck.Sing()
    donaldDuck.Sing()
}

上述的代码中,我们定义了一个Duck的接口,有Sing()Eat()两个方法,接口的特性要求不能实现这两个方法。

定义了两个对象,YellowDuckDonaldDuck,这两个对象都各自实现了接口中的方法,因此我们说这两个对象都实现了Duck接口 。(他们也有自己特有的方法)。

interface的值

某个interface类型的变量可以存储实现了该类型接口的任意对象。还是以上述的代码为例子,YellowDuckDonaldDuck都实现了Duck这个接口,那么Duck接口类型的变量就可以存储这两个对象。需要注意的是,duck只能调用Duck接口声明的方法,不能调用对象特有的方法!

代码实例,只修改主函数部分:

func main() {
    yellowDuck := YellowDuck{Sex: "female"}
    donaldDuck := DonaldDuck{Name: "Donald Duck"}
    var duck Duck

    duck = yellowDuck
    duck.Sing()
    duck.Eat()
    // duck.LayEgg() 是错误的,只能调用接口中声明的方法

    duck = &donaldDuck
    duck.Sing()
    duck.Eat()
}
/*
输出结果:
Yellow duck is singing
Yellow duck eats rice
Donald Duck is singing
Donald Duck eats sugars
*/

注意上述代码中,duck = &DonaldDuck多了一个&符号。在本例中,这两种方法是等价的,但是如果涉及到的方法需要修改原来对象的值,那么就要用&的赋值方式了。

代码实例:

package main

type Integer int

func (i *Integer) Add(integer Integer) {
    *i += integer
}

func (i Integer) Less(integer Integer) bool {
    return i < integer
}

type LessAdder interface {
    Add(b Integer)
    Less(b Integer) bool
}

func main() {
    var a Integer = 1
    var lessAdder LessAdder = &a  // 在这里必须添加&符号
    lessAdder.Add(3)
}

因为这里涉及到了修改原来的数据,因此需要用&,在一般的 赋值语句中,最好是带着&符号,以免出错。

接口之间的赋值

如果接口A的方法是接口B方法的子集,那么B可以 直接赋值给A

代码实例:

package main

import "fmt"

type F1 interface {
    Foo1()
}

type F2 interface {
    Foo1()
    Foo2()
}

type One struct {
    Name string
}

func (one One) Foo1() {
    fmt.Println("One Foo1")
}

type Two struct {
    Name string
}

func (two Two) Foo1() {
    fmt.Println("Two Foo1")
}

func (two Two) Foo2() {
    fmt.Println("Two Foo2")
}

func main() {
    var f1 F1 = new(One)
    var f2 F2 = new(Two)
    var f3 F1 = f1
    f3 = f2
    f3.Foo1()
}
/*
输出结果:
Two Foo1
*/

接口f2包含了f1,所以可以直接对f1的类型赋值f2

接口查询

查询某个对象是否实现了某个接口,该对象必须实现这个接口的所有方法才行!

代码实例,引自这篇博客

package main

import "fmt"

type IFile interface {
    Read()
    Write()
}

type IReader interface {
    Read()
}

type File struct {
}

func (f *File) Read() {

}

func (f *File) Write() {

}

func main() {
    f := new(File)

    var f1 IFile = f    // ok 因为FIle实现了IFile中的所有方法
    var f2 IReader = f1 // ok 因为IFile中包含IReader中所有方法
    // var f3 IFile = f2            // error 因为IReader并不能满足IFile(少一个方法)
    //
    var f3 IReader = new(File) // ok 因为File实现了IReader中所有方法
    // var f4 IFile = f3            // error 因为IReader并不能满足IFile 同上..如何解决呢? 要用接口查询

    // 接口查询
    // 这个if语句检查file1接口指向的对象实例是否实现了IFile接口
    // 如果实现了
    // 则执行特定的代码。
    // 注意:这里强调的是对象实例,也就是new(File)
    // File包含IFile里所有的方法
    // 所以ok = true
    if f5, ok := f3.(IFile); ok {
        fmt.Println(f5)
        fmt.Println(".............")
    }

    // 询问接口它指向的对象是否是某个类型
    // 这个if语句判断file1接口指向的对象实例是否是*File类型
    // 依然ok
    if f6, ok := f3.(*File); ok {
        fmt.Println(f6)
    }

    fmt.Println(f1, f2, f3)

    if b := 1; true {
        fmt.Println(b, "判断1==1;true")
    }

    if c := 1; false {
        fmt.Println(c, "判断1==1;false")
    }

}

类型查询

在Go语言中,空的interface可以存储所有类型的值,所以我们在前面提到,可以使用空的interface表示形参,以传递任何参数。这种方式也称为类型查询:

package main

import (
    "fmt"
)

type IDuck interface {
    Speak()
    Eat()
}

type DonaldDuck struct {    // DonaldDuck类型,实现了IDuck接口
    Name string
}

func (dd DonaldDuck) Speak() {
    fmt.Println("I'm " + dd.Name)
}
func (dd DonaldDuck) Eat() {
    fmt.Println(dd.Name + " is eating")
}

type IBird interface {
    Fly()
    Eat()
}

type RedBird struct {   // RedBird类型,实现了IBird接口
    Name string
}

func (rb RedBird) Fly() {
    fmt.Println(rb.Name + " can fly")
}
func (rb RedBird) Eat() {
    fmt.Println(rb.Name + " is eating")
}

type Dog struct {   // Dog类型,但是不会输出
    Name string
}

func Foo(args ...interface{}) {
    for _, arg := range args {
        switch v := arg.(type) {
        case int:
            fmt.Println("int value: ", v)
        case string:
            fmt.Println("string value", v)
        case float64:
            fmt.Println("float64  value:", v)
        default:  // 非标准类型,我们需要自己判断
            if v, ok := arg.(IDuck); ok {
                v.Speak()
                v.Eat()
            } else if v, ok := arg.(IBird); ok {
                v.Fly()
                v.Eat()
            } else {
                fmt.Println("No right type !")
            }
        }
    }
}

func main() {
    v1 := 1
    v2 := "hello world!"
    var v3 float64 = 1.1
    v4 := DonaldDuck{Name: "Donald Duck"}
    v5 := RedBird{Name: "Red Bird"}
    v6 := Dog{Name: "Didi"}
    Foo(v1, v2, v3, v4, v5, v6)
}
/*
输出结果:
int value:  1
string value hello world!
float64  value: 1.1
I'm Donald Duck
Donald Duck is eating
Red Bird can fly
Red Bird is eating
No right type !
*/

接口组合

不同接口可以组合成一个新的接口,代码实例:

package main

import "fmt"

type IRead interface {
    Read()
}

type IWrite interface {
    Write()
}

type IReadWrite interface {   // 组合接口
    IRead
    IWrite
}

type IReadWrite1 interface {  // 覆盖了两个接口的方法
    Read()
    Write()
}

type ReadWriter struct{}

func (rw ReadWriter) Read() {
    fmt.Println("Reading")
}

func (rw ReadWriter) Write() {
    fmt.Println("Writing")
}

func main() {
    var rw IReadWrite = ReadWriter{}   // 注意是使用了接口的类型
    if val, ok := rw.(IReadWrite); ok {
        val.Read()
        val.Write()
    }
    if val, ok := rw.(IReadWrite1); ok {
        val.Read()
        val.Write()
    }
}
/*
输出结果:
Reading
Writing
Reading
Writing
*/

有上述代码可以看出,IReadWriterIReadWriter1是等效的接口,两种定义方法都可以。一般我们使用接口组合的方式。

Any类型

Any类型就是我们前面提到的空接口interface{},它可以是指向任何对象的Any类型。

var v1 interface{} = 1
var v2 interface{} = 1.1
var v3 interface{} = "Hello world !"
var v4 interface{} = struct{x int}{1}

同时,作为函数的形参表示传递任意类型的参数,前面提到过了,在这里不在赘述。。。

猜你喜欢

转载自blog.csdn.net/qq_35976351/article/details/81838817