Go语言面向对象编程

系统类型

为类型添加方法

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}

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

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值