# Go 快速入门(二)
# 本章学习目标
- 通过学习
struct
、method
掌握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
}