30

Golang 复合数据类型:方法

 3 years ago
source link: https://studygolang.com/articles/29099
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

传统的面向对象编程

在面向对象编程(OOP)中,类与对象是面向对象编程的两个主要方面。一个 类(Class) 能够创建一种新的 类型(Type) ,其中 对象(Object) 就是类的 实例(Instance) 。可以这样来类比:你可以拥有类型 int 的变量,也就是说存储整数的变量是 int 类的实例(对象)。

对象可以使用 属于 它的普通变量来存储数据。这种从属于对象或类的变量叫作 字段(Field) 。对象还可以使用 属于 类的函数来实现某些功能,这种函数叫作类的 方法(Method) 。这两个术语很重要,它有助于我们区分函数与变量,哪些是独立的,哪些又是属于类或对象的。总之,字段与方法通称类的 属性(Attribute)

方法

在 Golang 中,方法是作用在接收者(receiver)上的一个函数,接收者是某种类型的变量;接收者可以是任意类型(指针、接口除外),包括结构体类型、函数类型、可以是 int ,boll,string或数组别名类型。

  • 接收者不能是一个接口类型,因为接口是一个抽象定义,方法却必须要具体实现
  • 接收者不能是一个指针类型,但可以是任何其他类型允许的指针

方法重载

类型的代码和绑定在它上面的方法的代码可以不放置在一起,它们可以存在不同的源文件中,唯一的要求是它们必须是同一个包的。 因为 方法是函数,所以同样的,不允许方法重载,即对于一个类型只能有一个给定名称的方法,但是如果基于接收器类型,是有重载的 。如下面的例子,具有同样名字的方法 Add 可以在 2 个或多个不同的接收器类型 *denseMatrix*sparseMatrix 上存在,比如在同一个包里这么做是允许的:

//a是接收器变量,*denseMatrix和*sparseMatrix是接收器类型,Add是方法,b Matrix是参数列表,Matrix是返回参数
func (a *denseMatrix) Add(b Matrix) Matrix
func (a *sparseMatrix) Add(b Matrix) Matrix

接收器的标准格式

  • recv 是接收器名称, receiver_type 是接收器类型( 接收器中的参数变量名在命名时,官方建议使用接收器类型名的第一个小写字母 ,例如 Socket 类型的接收器变量应该命名为 s )
  • methodName 是方法名, paramater_list 是参数列表, return_value_list 是返回值列表
func (recv receiver_type)methodName(paramater_list)(return_value_list) {...}

接收器的类型

在计算机中,小对象由于值复制时的速度较快,所以适合使用非指针接收器,大对象因为复制性能较低,适合使用指针接收器,在接收器和参数间传递时不进行复制,只是传递指针。

  • 指针类型

    由于指针的特性,调用方法时,修改接收器指针的任意成员变量,在方法结束后,修改都是有效的。指针类型的接收器由一个结构体的指针组成,更接近于面向对象中的 this 或者 self;但 Go 并没有 this 和 self 这两个关键字,因此,也可以使用 this 和 self 作为接收者的实例化名字(this 和 self 跟一般的实例化名字没什么两样)。

    //定义TwoInts结构体
    type TwoInts struct {
        a int
        b int
    }
    func main(){
        two1 := new(TwoInts)//实例化,返回指针*TwoInts
        two1.a = 12
        two1.b = 10
        //调用AddThem方法
        fmt.Println(two1.AddThem())
        //调用AddToParam方法
        fmt.Println(two1.AddToParam(20))
    
        two2 := TwoInts{3,4} //实例化,返回结构体TwoInts{3,4}
        //调用AddThem方法
        fmt.Println(two2.AddThem())
    }
    //指针接收器的两个值相加方法
    func (tn *TwoInts)AddThem()int{
        return tn.a + tn.b
    }
    //指针接收器的三个值相加方法
    func (tn *TwoInts)AddToParam(param int)int{
        return tn.a + tn.b + param
    }
    
    /*
    22
    42
    7
    */

    代码说明:

    • 定义一个 TwoInts 结构,拥有两个整型的成员变量。使用名字 two1 实例化 TwoInts 结构体,返回指针类型 *TwoInts ,分别设置实例化成员变量 two1.atwo1,b 的值为 1210 ;使用名字 two2 实例化 TwoInts 结构体,设置成员变量返回结构体 TwoInts
    • 构造成员变量的方法 AddThem ,设置方法的接收器类型为指针 *TwoInts ,返回 tn.a + tn.b 的整型值;因此可以修改成员值,即便退出方法,也有效;构造另一个成员变量的方法 AddToParam ,设置方法的接收器类型为指针 *TwoInts ,返回 tn.a + tn.b + param 的整型值。
    • main 函数中,使用 fmt.Println 函数分别调用 AddThemAddToParam 方法,获取成员变量的值。
  • 非指针类型

    当方法作用于非指针接收器时,Go语言会在代码运行时将接收器的值复制一份,在非指针接收器的方法中可以获取接收器的成员值,但修改后无效。

    // 定义点结构
    type Point struct {
        X int
        Y int
    }
    // 非指针接收器的加方法
    func (p Point) Add(other Point) Point {
        // 成员值与参数相加后返回新的结构
        return Point{p.X + other.X, p.Y + other.Y}
    }
    func main() {
        // 初始化点
        p1 := Point{3, 4}
        p2 := Point{2, 5}
        // 与另外一个点相加
        result := p1.Add(p2)
        // 输出结果
        fmt.Println(result.X,result.Y,result)
    }
    
    /*
    5 9 {5 9}
    */

    代码说明:

    • 定义一个 Point 点结构,拥有 XY 两个整型的成员变量
    • Point 结构定义一个非指针接收器的 Add 方法,传入和返回都是 Point 的结构,可以方便地实现多个点连续相加的效果,例如 P4 := P1.Add( P2 ).Add( P3 )
    • p1p2 实例化两个点
    • p1p2 两个点相加后返回结果并存储到 result
    • 打印结果 resultXY 相加的值

      由于例子中使用了非指针接收器, Add() 方法变得类似于只读的方法,Add() 方法内部不会对成员进行任何修改

### 函数和方法的区别

函数将变量作为参数:Function(recv);方法在变量上被调用:recv.Method1()

  • 当接收者是指针时,方法可以改变接收者的值和状态。(对于方法来说)
  • 当参数作为指针传递时,即通过引用调用时,函数也可以改变参数的状态。(对于函数来说)

### Golang设计模式之工厂方法

参考链接: Golang设计模式之工厂方法(掘金)

在面向对象编程中,可以通过构造子方法实现工厂模式,但 Golang 并不是面向对象编程语言,因此不能使用构造子方法来实现设计模式,而是相应地提供了其他方案。以结构体为例,通常会为结构体类型定义一个工厂,工厂的名字以 new 或者 New 开头。

//不强制构造函数,首字母大写
type File struct {
    fd int
    name string
}

//构造工厂方法
func NewFile(fd int,name string) *File{
    if fd < 0 {
        return nil
    }
    return &File{fd,name}
}

func main() {
    //调用工厂方法NewFile
    f := NewFile(10,"./test.yxy")
    fmt.Println(f)
    //计算结构体占用多少内存
    size := unsafe.Sizeof(File{})
    fmt.Println(size)
}

/*
&{10 ./test.yxy}
24
*/

代码说明:

  • 以首字母为大写,不强制使用构造函数,创建结构体 File
  • 为这个结构体构造一个工厂方法 NewFile ,并返回指向结构体的指针 *File
  • main 函数调用该工厂方法

强制使用工厂方法,通过应用可见性就可以禁止使用 new 函数,强制用户使用工厂方法,从而使类型转变成私有的。

### 指针或值作为接收者

如果想要方法改变接收者的数据,就在接收者的指针类型上定义该方法。否则,就在普通的值类型上定义方法。

change() 接收一个指向 B 的指针,并改变它的内部成员; write() 通过拷贝接收 B 的值并只输出 B 的内容,

type B struct {
      thing int
  }
  
  func (b *B) change() {
      b.thing = 1
  }
  
  func (b B) write() string { 
      return fmt.Sprint(b) 
  }
  
  func main() {
      var b1 B // b1是值
      b1.change()
      fmt.Println(b1.write())
  
      b2 := new(B) // b2是指针
      b2.change()
      fmt.Println(b2.write())
  }
  
  /* 
  {1}
  {1}
  */

欢迎关注我们的微信公众号,每天学习Go知识

FveQFjN.jpg!web

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK