Tian Jiale's Blog

Go 中的面向对象

结构体

Golang 中没有类,但可以定义类型上的方法,同时有接口,可以通过接口定义对象的要求。也可以通过类型定义对象的构造方法及行为。

什么是结构体?在 Go 结构体是字段的集合。

type Vertex struct {
  X int
  Y int
}

访问方法

对于结构体对象可以使用 . 访问对象成员。在使用指针时,虽然可以使用 (*p) 来访问对象成员,但在 Go 中可以直接使用 指针.成员变量 的方式访问成员变量。

结构体对象的定义

结构体对象有多种定义方法,以上文的结构体来举例。

v1 = Vertex{1, 2}
v2 = Vertex{X: 1}
v3 = Vertex{}
p  = &Vertex{1, 2}

方法

方法是一个具有特殊接收器参数的函数。其中方法的调用变量在函数名之前,示例如下。

type Vertex struct {
  X, Y float64
}

func (v Vertex) Abs() float64 {
  return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

func main() {
  v := Vertex{3, 4}
  fmt.Println(v.Abs())
}

这里的方法与 Go 的函数只有接收对象的区别,其他和不同函数相同。

前面说到但可以定义类型上的方法,这里需要指出,方法的定义只能与变量声明在一个包中,不能定义在其他包中定义的变量类型的方法,同样内置变量类型也是不可以的,但可以通过 type MyFloat float64 方式来定义自己的变量类型之后添加方法。

指针接收器

在 Go 中所有的对象复制都是拷贝,所以在声明方法时使用的不是指针变量,那么在方法中对变量的操作都是无法修改原变量的,可以使用指针接收器来修改原变量。

那么值接收器改为了指针接收器,调用时也必须使用指针变量吗?当然不是,在 Go 中为方便,会自动将 p 转换成 (&p) 因为 p 有一个指针接收器。同样的,如果声明的是值接收器,可以直接使用指针调用方法。

需要注意的是,同一个方法只能设置一种接收器,即不可同时声明值接收器和指针接收器。

接口

接口定义了一组方法的集合。

和其他语一样,接口类型的值可以保存实现这些方法的任何值。

需要注意的是,接口类型的值如果需要保存一个值变量或指针变量,那么该值类型必须实现接口定义的值接收器方法或指针接收器方法。如果函数定义在值类型上,那么接口类型的值可以保存指针变量(解除指针)。但是定义了指针接收器方法的值类型不能赋值给接口,因为 Golang 中赋值是拷贝,虽然可以把接口中保存的值的指针给到函数,但这个值已经不是原来的那个值了,如此毫无意义,甚至会让新手写出许多隐性 Bug。

接口的实现是隐式的,不需要 implements 等关键字,隐式接口将接口的定义与其实现分离开来,然后接口可以不需要预先书写就出现在任何包中。

接口类型的值保存了实现接口的值类型的值及其类型。

(value, type)

空指针异常的处理

在 Go 中通过 nil 接收器处理处理空指针异常的情况。当接口保存的对象值为空时,指针接收器中判断接收对象是否为 nil 即可处理空指针异常。

需要注意的是,这种空指针异常的处理方法针对的是接口内部保存的是空值的情况,如果接口本身是空值是会报错的。

类型断言

类型断言提供对接口值的底层具体值的访问。

t := i.(T)

如果接口保存的值与指定的值类型不同,则会产生 panic。

可以通过指定第二个参数来获取断言的正确性,此时不会产生 panic。

s, ok := i.(string)

嵌入

嵌入实现了部分类继承的需求。

用类型声明但没有显式字段名称的字段称为嵌入字段。嵌入字段必须指定为类型名 t 或指向非接口类型名 * t 的指针,而且 t 本身可能不是指针类型。未限定的类型名充当字段名。

struct {
  T1        // field name is T1
  *T2       // field name is T2
  P.T3      // field name is T3
  *P.T4     // field name is T4
  x, y int  // field names are x and y
}

在创建有嵌入的结构体的对象时,必须显式地初始化嵌入类型。

co := container{
    base: base{
        num: 1,
    },
    str: "some name",
}

含有嵌入类型的结构体会继承嵌入类型上所定义的方法,同样地会实现相关接口,对于嵌入类型的成员变量可以通过完整的变量寻址路径来获取,也可以直接通过 . 来获取。