一、面向对象
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
|
初始化,赋予接口零值,它的类型和值的部分都是nil
调用空接口值上的任意方法都会产生panic:
1
|
w.Write([]byte("hello")) //panic
|
第二个语句,将一个*os.File类型复制给变量w
type |
value |
*os.File |
fd int=1(stdout), 即标准输出类型描述符 |
第三个语句,将一个*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))
}
|