一、面向对象

go面向对象

  • go中没有类的概念,面向对象是通过结构体来实现的
  • 结构体可以定义属性,也可以定义成员方法

成员方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
// 定义一个结构体
type Point struct{ X, Y float64 }

// 普通函数
func Distance(p, q Point) float64 {
    return math.Hypot(q.X-p.X, q.Y-p.Y)
}

// Point 类型的方法
func (p Point) Distance(q Point) float64 {
    return math.Hypot(q.X-p.X, q.Y-p.Y)
}
p := Point{1, 2}
q := Point{4, 6}
//普通方法调用
fmt.Println(Distance(p, q)) // "5", function call

// 结构体方法调用
fmt.Println(p.Distance(q))  // "5", method 

两个函数的名字都叫Distance,但是没有发生冲突,第一个属于包级别的,第二个属于Point类型的
上边类方法的代码里的参数p,叫做方法的receiver (其它语言中的this or self)
不同的receiver 也可以有相同的方法名 (和类其实一样的概念)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
//定义了一个线段,多个Point组成,即Point的Slice
type Path []Point

func (path Path) Distance() float64 {
    sum := 0.0
    for i := range path {
        if i > 0 {
            //调用Point的Distance
            sum += path[i-1].Distance(path[i])
        }
    }
    return sum
}

基于指针对象的方法

函数前receiver和函数的参数一样,是一个值拷贝,我们也可以用传递指针的方式

1
2
3
4
5
// 放大
func (p *Point) ScaleBy(factor float64) {
    p.X *= factor
    p.Y *= factor
}

类型名本身是一个指针的话,不能做为receiver

1
2
type P *int
func (P) f() {/*....*/}         //编译错误

指针receiver方法的调用

1
2
3
4
5
6
7
r := &Point{1, 2}
r.ScaleBy(2)
fmt.Println(*r)     // "{2, 4}"

p := Point{1, 2}
(&p).ScaleBy(2)

当然我们也可以简单方式调用: 如果我们调用的时候,给出的实际类型是一个Point类型,但是方法需要一个Point指针类型的receiver,编译器会隐式的帮助我们用&p去调用。这种简写方法只能用于可以取到地址的变量

1
2
3
4
//还可以直接写
p := Point{1, 2}
p.ScaleBy(2)
Point{1, 2}.ScaleBy(2)      //编译错误

实际类型是一个Point指针类型,但是方法需要一个Point的receiver,编译器也会隐式转换,自动*p解引用,通过地址找到这个变量

实际类型 接收器类型 操作
T T ✔️
*T *T ✔️
T *T 隐式转换,&取地址
*T T 隐式转换,*操作

方法值和方法表达式

可以把方法复制给一个变量,让其使用默认的对象

1
2
3
4
5
6
7
8
p := Point{1, 2}
q := Point{4, 6}

distanceFromP := p.Distance
distanceFromP(q)    //等价于 p.Distance(q)

scaleP := p.ScaleBy
scaleP(2)    //等价于 p.ScaleBy(2)

将对象方法转成普通的函数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
p := Point{1, 2}
q := Point{4, 6}
//变成普通方法
distance := Point.Distance
fmt.Println(distance(p, q))
fmt.Printf("%T", distance)   //func (Point, Point) float64

scale := (*Point).ScaleBy
fmt.Println(scale(&p, 2))
fmt.Printf("%T", scale)   //func (*Point, float64)

二、接口

如果goroutine和channel是支撑起Go语言的并发模型的基石,那么接口是Go语言整个类型系统的基石。

接口的约定

其它语言中,要实现某个接口,需要显式的进行指出
非入侵式接口:只要一个类实现了接口要求的所有函数,我们就说这个类实现了该接口

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package main

import "fmt"
import "math"

type geometry interface {
    area() float64      //面积
    perim() float64     //周长
}

// rect类型实现了geometry接口中所有的方法,即实现了这个接口
type rect struct {
    width, height float64
}
func (r rect) area() float64 {
    return r.width * r.height
}
func (r rect) perim() float64 {
    return 2*r.width + 2*r.height
}

// circle类型实现了geometry接口中所有的方法,即实现了这个接口
type circle struct {
    radius float64
}

func (c circle) area() float64 {
    return math.Pi * c.radius * c.radius
}
func (c circle) perim() float64 {
    return 2 * math.Pi * c.radius
}

// geometry接口类型
func measure(g geometry) {
    fmt.Println(g)
    fmt.Println(g.area())
    fmt.Println(g.perim())
}

func main() {
    r := rect{width: 3, height: 4}
    c := circle{radius: 5}
    // rect、circle都实现了geometry接口,因此可以作为measur(测量)函数的参数
    measure(r)
    measure(c)
}

系统包中的接口: Printf和Sprintf都是通过调用Fprintf实现的,Fprintf第一个参数是一个io.Writer接口

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
package io

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

package fmt

//实现了io.Writer接口
func Fprintf(w io.Writer, format string, args ...interface{}) (int, error)

func Printf(format string, args ...interface{}) (int, error) {
    return Fprintf(os.Stdout, format, args...)      //输出到标准输出当中
}

func Sprintf() {
    var buf bytes.Buffer
    Fprintf(&buf, format, args...)      //输出到buffer中
    return buf.String()
}

自己实现一个io.Writer接口

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
type ByteCounter int
func (c *ByteCounter) Write(p []byte) (int, error) {
    *c += ByteCounter(len(p))
    return len(p), nil
}

var c ByteCounter
//传递的指针,因此c的值变成了字符串的长度
c.Write([]byte("hello"))
fmt.Println(c)      // 5, =len("hello")

c = 0   //reset
var name = "Dolly"
fmt.Fprintf(&c, "hello, %s", name)
fmt.Println(c)      //12, = len("hello, Dolly")

实现接口的条件

接口指定的规则非常简单:表达一个类型属于某个接口只要这个类型实现了这个接口

1
2
3
4
var w io.Writer
w = os.Stdout           //ok
w = new(bytes.Buffer)   //ok
w = time.Second         //编译失败

空接口类型,因为空接口类型对实现它的类型没有要求,所以可以将任意一个值赋值给空接口类型

1
2
3
4
5
6
var any interface{}
any = true
any = 12.34
any = "hello"
any = map[string]int{"one": 1}
any = new(bytes.Buffer)

接口值

一个接口的值由两部分组成,一个具体的类型和对应这个类型的值,他们被称为接口的动态类型和动态值

根据实例看一下接口值的变化

1
2
3
4
var w io.Writer
w = os.Stdout
w = new(bytes.Buffer)
w = nil
1
var w io.Writer

初始化,赋予接口零值,它的类型和值的部分都是nil

type value
nil nil

调用空接口值上的任意方法都会产生panic:

1
w.Write([]byte("hello"))        //panic
1
w = os.Stdout

第二个语句,将一个*os.File类型复制给变量w

type value
*os.File fd int=1(stdout), 即标准输出类型描述符
1
w = new(bytes.Buffer)

第三个语句,将一个*bytes.Buffer类型复制给变量w

type value
*bytes.Buffer data []byte, 即内存缓冲区

第四个语句,将nil赋值给接口值,重置成初始化的状态

使用%T可以查看接口的真实类型

1
2
3
4
5
6
7
8
9
var w io.Writer
fmt.Printf("%T\n", w)  // nil

w = os.Stdout
fmt.Printf("%T\n", w)  // *os.File

w = new(bytes.Buffer)
fmt.Printf("%T\n", w)  // *bytes.Buffer

一个小坑:包含nil指针的接口不是nil接口

1
2
3
4
5
6
7
8
func f(out io.Writer) {
    if out != nil {
        out.Write([]byte("done!\n"))
    }
}

var buf *bytes.Buffer   //未初始化
f(buf)      //panic

当buf作为实参传递给out时,out的状态是这样的:

type value
*bytes.Buffer nil

也就是说它的值是nil,但是类型不是nil, 所以 out != nil 的结果依然是true,引发错误

1
2
var buf io.Writer
f(buf)      //ok

类型判断

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
// 语法x.(T)

var w io.Writer
w = os.Stdout

f := w.(*os.File)   //success
c := w.(*bytes.Buffer)  //pannic


//可以使用这种语法
if c, ok := w.(*os.File); ok {
    /..../
}

if c, ok := w.(*bytes.Buffer); ok {
    /..../
}

举个简单的例子

net/http

1
2
3
4
5
6
package http
type Handler interface {
    ServeHTTP(w ResponseWriter, r *Request)
}

func ListenAndServe(address string, h Handler) error
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
type dollars float32
func (d dollars) String() string {
    return fmt.Sprintf("%.2", d)
}

type database map[string]dollars
func (db database) ServeHTTP(w http.ResponseWriter, req *http.Request) {
    for item, price := range db {
        fmt.Fprintf(w, "%s: %s\n", item, price)
    }
}

func main() {
    db := database{"shoes": 50, "sock": 5}
    log.Fatal(http.ListenAndServe("localhost:8000", db))
}