# Go 快速入门(二)

# 本章学习目标

  • 通过学习structmethod掌握Go如何实现面向对象的封装、继承、多态。
  • 通过学习interface,掌握interface如何存储各种变量及如何灵活封装方法和包。

# 一、指针

Go 拥有指针。指针保存了值的内存地址。

类型 *T 是指向 T 类型值的指针。其零值为 nil

var p *int

& 操作符会生成一个指向其操作数的指针。

i := 42
p = &i

* 操作符表示指针指向的底层值。

fmt.Println(*p) // 通过指针 p 读取 i
*p = 21         // 通过指针 p 设置 i

使用一个指针引用一个值被称为间接引用

与 C 不同,Go 没有指针运算。

# 二、面向对象

GO中没有class关键字,却引入了struct,二者不是简单的替换那么简单,struct表达的涵义远比class要广。 主流的面向对象语言(C++, Java)不太强调类与类型的区别,本着一切皆对象的原则,这些语言中的类型是以类为基础的,即通过类来定义类型,类是这类语言的根基。与之不同,Go中更强调类型。

# 1、struct

结构体(struct)用于声明新的类型,作为其它类型的属性或字段的容器。

#### 1.1、结构体定义

// 定义方式
type structName struct {
    field1 type1
    field2 type2
    ...
}

// 示例
type person struct {
    name string
    age int
}

# 1.2、结构体赋值方式

  • 通过field:value的方式初始化。

    P := person{age:24, name:"Tom"}
    
  • 使用 new 函数给一个新的结构体变量分配内存,它返回指向已分配内存的指针:var t *T = new(T)

    var t = new(T)
    
    // or
    
    var t *T
    t = new(T)
    

# 1.3、结构体使用

无论变量是一个结构体类型还是一个结构体类型指针,都使用同样的 选择器符. 来引用结构体的字段:

type myStruct struct {
	i int
}
var v myStruct    // v是结构体类型变量
var p *myStruct   // p是指向一个结构体类型变量的指针
v.i
p.i

# 1.4、struct的匿名字段

定义的时候是字段名与其类型一一对应,实际上Go支持只提供类型,而不写字段名的方式,也就是匿名字段,也称为嵌入字段

当匿名字段是一个struct的时候,那么这个struct所拥有的全部字段都被隐式地引入了当前定义的这个struct。

package main
import "fmt"

type Human struct {
    name string
    age int
    weight int
}

type Student struct {
    Human  // 匿名字段,那么默认Student就包含了Human的所有字段
    speciality string
}

func main() {
    
    // 初始化一个学生
    mark := Student{Human{"Mark", 25, 120}, "Computer Science"}

    // 访问相应的字段
    fmt.Println("His name is ", mark.name)
    fmt.Println("His age is ", mark.age)
    fmt.Println("His weight is ", mark.weight)
    fmt.Println("His speciality is ", mark.speciality)
    
    // 修改对应的备注信息
    mark.speciality = "AI"
    fmt.Println("Mark changed his speciality")
    fmt.Println("His speciality is ", mark.speciality)
    
    // 修改年龄信息
    fmt.Println("Mark become old")
    mark.age = 46
    fmt.Println("His age is", mark.age)
}

# 2、method

在 Go 语言中,结构体就像是类的一种简化形式,类的方法就是method。

# 2.1、语法

method的语法如下:

func (r ReceiverType) funcName(parameters) (results)

例:

package main
import (
    "fmt"
    "math"
)

type Rectangle struct {
    width, height float64
}

type Circle struct {
    radius float64
}

func (r Rectangle) area() float64 {
    return r.width*r.height
}

func (c Circle) area() float64 {
    return c.radius * c.radius * math.Pi
}

func main() {
    r1 := Rectangle{12, 2}
    r2 := Rectangle{9, 4}
    c1 := Circle{10}
    c2 := Circle{25}

    fmt.Println("Area of r1 is: ", r1.area())
    fmt.Println("Area of r2 is: ", r2.area())
    fmt.Println("Area of c1 is: ", c1.area())
    fmt.Println("Area of c2 is: ", c2.area())
}

# 2.2、指针作为receiver

定义 一个类(结构体)很多情况需要使用类的方法来修改类的属性值。如果结构体系的receiver不是一个指针,那么method接收的是一个结构体的copy。

package main

import (
	"fmt"
)

type Rectangle struct {
	width, height float64
}

func (r Rectangle) SetWidth(width float64) {
	r.width = width
}

func (r *Rectangle) SetHeight(height float64) {
	r.height = height
}

func main() {
	r := new(Rectangle)
	r.SetWidth(10)
	r.SetHeight(5)

	fmt.Println("Rectangle:width ", r.width)
	fmt.Println("Rectangle:height ", r.height)
}

// 结果:
// Rectangle:width  0   // 值传递
// Rectangle:height  5  // 引用传递

# 2.3、method继承/重写

如果匿名字段实现了一个method,那么包含这个匿名字段的struct也能调用该method。GO不支持重载

package main

import "fmt"

type Human struct {
    name  string
    age   int
    phone string
}

type Student struct {
    Human  //匿名字段
    school string
}

type Employee struct {
    Human   //匿名字段
    company string
}

//在human上面定义了一个method
func (h *Human) SayHi() {
    fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone)
}

//Employee的method重写Human的method
func (e *Employee) SayHi() {
    fmt.Printf("Hi, I am %s, I work at %s. Call me on %s\n", e.name,
        e.company, e.phone)
}

func main() {
    human := Human{"Human", 45, "111-888-XXXX"}
    student := Student{Human{"Student", 25, "222-222-YYYY"}, "MIT"}
    employee := Employee{Human{"Employee", 45, "111-888-XXXX"}, "Golang Inc"}

    human.SayHi()
    student.SayHi()  // Student 继承了匿名字段Human的 SayHi method
    employee.SayHi() // Employee 重写了匿名字段Human的 SayHi method
}

// 结果:
// command-line-arguments
// Hi, I am Human you can call me on 111-888-XXXX
// Hi, I am Student you can call me on 222-222-YYYY
// Hi, I am Employee, I work at Golang Inc. Call me on 111-888-XXXX

Go匿名字段功能和php中的trait类似。与其它语言不同,Go语言的继承通过匿名组合完成:基类以struct的方式定义,子类只需要把基类作为成员放在子类的定义中,支持多继承。Java的继承通过extends关键字完成,不支持多继承。

# 3、结构体工厂

Go 语言不支持面向对象编程语言中那样的构造方法,但是可以很容易的在 Go 中实现 “构造子工厂”方法。为了方便通常会为类型定义一个工厂,按惯例,工厂的名字以 new 或 New 开头。假设定义了如下的 File 结构体类型:

type File struct {
    fd      int     // 文件描述符
    name    string  // 文件名
}

下面是这个结构体类型对应的工厂方法,它返回一个指向结构体实例的指针:

func NewFile(fd int, name string) *File {
    if fd < 0 {
        return nil
    }

    return &File{fd, name}
}

然后这样调用它:

f := NewFile(10, "./test.txt")

通过应用可见性规则可以禁止使用 new 函数,强制用户使用工厂方法,从而使类型变成私有的,就像在面向对象语言中那样。

Go中大部分包中的结构都是通过添加New**函数创建,例如:bufio.NewReader()、http.NewRequest()、json.NewDecoder()。

# 三、interface

# 1、interface类型

interface是一组method的组合,可以通过interface来定义对象的一组行为。如果某个对象实现了某个interface的所有方法,则此对象就实现了此interface

定义格式:

type Namer interface {
    Method1(paramList) returnType
    Method2(paramList) returnType
    ...
}
  • 类型不需要显式声明它实现了某个接口:接口被隐式地实现。多个类型可以实现同一个接口
  • 实现某个接口的类型(除了实现接口方法外)可以有其他的方法。
  • 一个类型可以实现多个接口
  • 接口类型可以包含一个实例的引用, 该实例的类型实现了此接口。
// 定义interface
type Human interface {
    Eat()
}

type Employee struct {
    company string
}

// Employee隐式地实现Human接口
func (e *Employee) Eat() {
    // do something
}

// 实现某个接口的类型(除了实现接口方法外)可以有其他的方法
func (e *Employee) OwnMethod() {
    // do something
}

type Coder interface {
    Code()
}

// Employee实现Coder接口(一个类型可以实现多个接口)
func (e *Employee) Coder() {
    // do something
}

type Student struct {
    school string
}

// Student实现Coder接口(多个struct可以实现同一个接口)
func (e *Student) Coder() {
    // do something
}

func main() {

    employee := new(Employee)
    employee.company = "……"

    // 接口类型可以包含一个实例的引用, 该实例的类型实现了此接口。
    var humanInterfaceType Human
    humanInterfaceType = employee
    humanInterfaceType.Eat()
}

# 2、interface值

  • 如果我们定义了一个interface的变量,那么这个变量里面可以存实现这个interface的任意类型的对象。例如:上面例子中变量humanInterfaceType能够被任意实现了Human方法赋值。

  • 空interface(interface{})不包含任何的method,正因为如此,所有的类型都实现了空interface。空interface对于描述起不到任何的作用(因为它不包含任何的method),但是空interface在我们需要存储任意类型的数值的时候相当有用,因为它可以存储任意类型的数值。

    // 定义a为空接口
    var a interface{}
    var i int = 5
    s := "Hello world"
    // a可以存储任意类型的数值
    a = i
    a = s
    

# 3、interface函数参数

interface的变量可以持有任意实现该interface类型的对象,可以通过定义interface参数,让函数接受各种类型的参数。

举个例子:fmt.Println是我们常用的一个函数,但是你是否注意到它可以接受任意类型的数据。打开fmt的源码文件,你会看到这样一个定义:

type Stringer interface {
     String() string
}

也就是说,任何实现了String方法的类型都能作为参数被fmt.Println调用。

package main
import (
    "fmt"
)

type Human struct {
    name string
    phone string
}

// 通过这个方法 Human 实现了 fmt.Stringer
func (h Human) String() string {
    return "❰name:"+h.name+",✆:" +h.phone+"❱"
}

func main() {
    Bob := Human{"Bob", "138********"}
    fmt.Println("This Human is : ", Bob)
}

// 结果
// This Human is :  ❰name:Bob,✆:138********❱

# 4、嵌入interface

Struct匿名字段一样,相同的逻辑引入到interface里面。如果一个interface1作为interface2的一个嵌入字段,那么interface2隐式的包含了interface1里面的method。

例如io包中:

type Reader interface {
   Read(p []byte) (n int, err error)
}
type Writer interface {
   Write(p []byte) (n int, err error)
}
type ReadWriter interface {
	Reader
	Writer
}

# 本章试练