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