方法和对象

112 阅读6分钟

GO方法

方法的定义与其他的Go函数一样。当一个Go函数被定义在有限的范围内或与特定的类型相连时,它就被称为方法。方法提供了一种为用户定义的类型添加行为的方法。方法实际上是包含一个额外参数的函数,该参数在关键字func和函数名之间声明。

在Go中,方法是一种特殊的函数,它作用于某种类型的变量,称为接收器,它是放在方法名称前的一个额外参数,用于指定方法所连接的调节器类型。接收者类型可以是任何东西,不仅仅是结构类型:任何类型都可以有方法,甚至是函数类型或int、bool、string或array的别名类型。

方法的一般格式是:
func (recv receiver_type) methodName(parameter_list) (return_value_list) { ... }
在func关键字之后的方法名称之前,用()指定接收器。

下面是一个关于非结构类型的方法的例子。

例子

package main
import "fmt"

type multiply int	

func (m multiply) tentimes() int {
	return int(m * 10)
}

func main() {
	var num int
	fmt.Print("Enter any positive integer: ")
    fmt.Scanln(&num)
	mul:= multiply(num)
	fmt.Println("Ten times of a given number is: ",mul.tentimes())
}

输出

Enter any positive integer: 5
Ten times of a given number is:  50

关键字func和函数名之间的参数被称为接收器,它将函数与指定的类型绑定。当一个函数有一个接收器时,该函数被称为方法。

Go中的接收器有两种类型:值接收器和指针接收器。在上面的程序中,tentimes方法是用一个值接收器声明的。tentimes的接收器被声明为一个int类型的值。当你使用值接收器声明一个方法时,该方法将总是针对用于调用方法的值的副本进行操作。

mul变量被初始化为乘法类型。因此,当我们调用tentimes方法时,可以使用mul.tentimes()来访问tentimes方法,mul的值是调用的接收值,tentimes方法是对这个值的一个拷贝进行操作。

下面是一个程序,它显示了通过接收器参数实现附加在一个类型上的方法的数量,这被称为该类型的方法集。

例子

package main 
import "fmt" 
 
type salary float64 
func (s salary) total() total {    
   return total(s) 
} 
 
type total float64 
func (t total) hra() hra {
   t += t * 0.3   // 30% HRA Addition
   return hra(t)
} 
func (t total) salary() salary {   
   t -=t * 0.10    // 10% Tax Deduction  
   return salary(t) 
} 
 
type hra float64
func (h hra) basic() basic {
   h += h * 0.3   // 30% HRA Addition
  return basic(h)
}
func (h hra) total() total {   
  return total(h)
}

type basic float64 
func (b basic) total() total { 
   return total(b) 
} 
 
func main() { 
    fmt.Println("Salary calculation for First Employee:")
    sal1 := basic(9000.00)
    fmt.Println(sal1.total())
    fmt.Println(sal1.total().hra().total())
    fmt.Println(sal1.total().hra().total().salary())

    fmt.Println("\nSalary calculation for Second Employee:")
    sal2 := basic(5000.00)
    fmt.Println(sal2.total())
    fmt.Println(sal2.total().salary())
}

输出

Salary calculation for First Employee:
9000
11700
10530

Salary calculation for Second Employee:
5000
4500

方法重载

方法重载是基于接收者类型的,一个具有相同名称的方法可以存在于2个或更多不同的接收者类型中,例如,这在同一个包中是允许的。
func (s *inclTax) Salary(e Employee) Employee
func (s *exclTax) Salary(e Employee) Employee

接收方参数可以作为基类型的值或指针传递。指针式接收参数在Go中被广泛使用。

你也可以声明带有指针接收器的方法。

例子

package main
import "fmt" 

type multiply int 
type addition int 

func (m *multiply) twice() { 
  *m = multiply(*m * 2) 
}

func (a *addition) twice() { 
  *a = addition(*a + *a)
} 

func main() { 
  var mul multiply = 15 
  mul.twice()
  fmt.Println(mul)

	
  var add addition = 15 
  add.twice()
  fmt.Println(add)
}

输出

30
30

让我们再举一个例子,在这个例子中,我们调用了一个方法,这个方法接收了一个指向Struct的指针。我们通过值和指针来调用该方法,得到的结果是一样的。

用指针接收者声明方法的例子。

例子

package main
import "fmt" 

type multiply struct {
	num int 
}

func (m *multiply) twice(n int) { 
  	m.num = n*2
}

func (m multiply) display() int{ 
  	return m.num
} 

func main() { 
  fmt.Println("Call by value")	
  var mul1 multiply	// mul1 is a value
  mul1.twice(10)
  fmt.Println(mul1.display())

  fmt.Println("Call by pointer")	
  mul2 := new(multiply) // mul2 is a pointer
  mul2.twice(10)
  fmt.Println(mul2.display())
}

输出

Call by value
20
Call by pointer
20

在main()中,我们不需要考虑是否在指针上调用方法,Go为我们做了这个工作。mul1是一个值,mul2是一个指针,但方法的调用却很正常。


Go中的对象

在 Go 中没有作为对象基础的类类型的概念。Go中的任何数据类型都可以作为对象使用。Go中的结构类型可以接受一组方法来定义其行为。Go语言中没有称为类或对象的特殊类型。Go语言中的strct类型与其他编程语言中通常所说的对象相近。Go支持大多数通常归属于面向对象编程的概念。

在包和可扩展类型系统等概念的帮助下,Go在其核心部分支持物理和逻辑模块化;因此我们能够在Go中实现模块化和封装。

一个新声明的名称类型并不继承其底层类型的所有属性,而是被类型系统以不同方式处理。因此,GO不支持通过继承来实现多态性。但可以通过使用结构体或接口等类型的组合来创建对象并表达其多态性关系。

让我们从下面这个简单的例子开始,看看结构类型是如何被用作实现多态性组合的对象的。

例子

package main
import "fmt"

type gadgets uint8 
const (    
    Camera gadgets = iota
    Bluetooth
    Media
    Storage
    VideoCalling
    Multitasking
    Messaging
) 
type mobile struct { 
    make string 
    model string 
} 
 
type smartphone struct { 
   gadgets gadgets    
}

func (s *smartphone) launch() {
   fmt.Println ("New Smartphone Launched:") 
} 
 
type android struct { 
   mobile 
   smartphone
   waterproof string
} 
func (a *android) samsung() { 
   fmt.Printf("%s %s\n", 
          a.make, a.model)           
} 
 
type iphone struct { 
   mobile 
   smartphone 
   sensor int
} 
func (i *iphone) apple() { 
   fmt.Printf("%s %s\n",
          i.make, i.model) 
} 


func main() { 
   t := &android {}
   t.make ="Samsung"
   t.model ="Galaxy J7 Prime"
   t.gadgets = Camera+Bluetooth+Media+Storage+VideoCalling+Multitasking+Messaging
   t.launch()
   t.samsung() 
}

输出

New Smartphone Launched:
Samsung Galaxy J7 Prime

在上面的程序中,利用结构类型支持的类型嵌入机制,利用继承的组合原则来实现多态性。在这里,每个类型都是独立的,被认为是与其他类型不同的。上面的程序显示,iphone和android这两个类型通过子类型关系是一个移动的。

然而,正在调用的方法t.launch(),无论是iphone还是android,都不是一个名为launch()的方法的接收者。launch()方法是为智能手机类型定义的。由于智能手机类型被嵌入到iphone或android类型中,所以launch()方法在范围上被提升到这些包围类型中,因此可以被访问。