实现了以上特征的语言,才能成为面向对象编程范式语言。
严格意义来说,Go语言就是不想实现面向对象编程范式。但是面向对象又有一些不错的特性,Go语言通过组合的方式实现了类似的功能。Go语言实现了一种非常有自我特征的面向对象。
通过结构体,可以把数据字段封装在内,还可以为结构体提供方法。
访问控制:
Go没有提供类似C++、Java一样的构造函数、析构函数。在Go中,用构造结构体实例的函数,这个函数没有特别的要求,只要返回结构体实例或其指针即可(建议返回指针,不然返回值会拷贝)。习惯上,构造函数命名是New或new开头。如果有多个构造函数,可以使用不同命名函数,因为Go也没有函数重载。
package main
import "fmt"
type Animal struct {
name string
age int
}
func NewDefaultAnimal() *Animal {
return &Animal{"nobody", 1}
}
func NewAnimal(name string, age int) *Animal {
return &Animal{name, age}
}
func main() {
p := NewDefaultAnimal()
fmt.Println(*p, p.name, p.age)
fmt.Println(*NewAnimal("zfl", 21))
}
{nobody 1} nobody 1
{zfl 21}
通过不同的函数名来模拟构造函数重载
Go语言没有提供继承的语法,实际上需要通过匿名结构体嵌入(组合)来实现类似效果。
package main
import "fmt"
// 定义 Animal 结构体,表示动物
type Animal struct {
name string
age int
}
// 定义 Cat 结构体,继承自 Animal 结构体,表示猫
type Cat struct {
Animal
color string
}
// 为 Animal 结构体定义 run 方法
func (*Animal) run() {
fmt.Println("Animal run ~~~")
}
// 为 Cat 结构体定义 run 方法,覆盖了父类 Animal 的 run 方法
func (*Cat) run() {
fmt.Println("cat run~~~")
}
func main() {
cat := new(Cat)
cat.run() //调用 Cat 结构体的 run 方法,输出cat run信息 ,如果没有cat的函数方法,默认输出的是cat.Animal.run()
cat.Animal.run() //用 Cat 结构体内嵌的 Animal 结构体的 run 方法,输出动物在奔跑的信息
}
cat run~~~
Animal run ~~~
覆盖override,也称重写。注意不是重载overload。
func (*Cat) run() {
fmt.Println("cat run~~~")
}
为Cat增加一个run方法,这就是覆盖。特别注意 cat.run() 和 cat.Animal.run() 的区别。
上例增加run方法是完全覆盖,就是不依赖父结构体方法,重写功能
如果是依赖父结构体方法,那就要在子结构体方法中显式调用它。
func (c *Cat) run() {
c.run() //cat.run() 这是无限递归调用,小心!自己调自己
c.Animal.run() // 这是调用父结构体方法
fmt.Println("Cat run+++")
}
Go语言不能像Java语言一样使用多态,但可以通过引入interface接口来解决。
package main
import (
"fmt"
)
type Runner interface {
run()
}
type Animal struct {
name string
age int
}
func (*Animal) run() {
fmt.Println("animal run ~~~")
}
type Cat struct {
Animal
color string
}
func (c *Cat) run() {
c.Animal.run()
fmt.Println("cat run+++")
}
type Dog struct {
Animal
color string
}
func (d *Dog) run() {
d.Animal.run()
fmt.Println("Dog run+++")
}
func test(a Runner) {
a.run()
}
func main() {
d := new(Dog)
d.name = "tom"
test(d)
c := new(Cat)
c.name = "herry"
test(c)
}
animal run ~~~
Dog run+++
animal run ~~~
cat run+++
定义了一个 Runner 接口,其中包含了一个 run 方法。然后,定义了一个 Animal 结构体,以及继承自 Animal 的 Cat 和 Dog 结构体。这两个结构体分别实现了 Runner 接口的 run 方法。
在 main 函数中,创建了一个 Dog 类的实例 d 和一个 Cat 类的实例 c,并分别调用了 test 函数,将这两个实例作为参数传入。由于 Dog 和 Cat 类都实现了 Runner 接口的 run 方法,因此它们都可以作为 Runner 接口的实例进行传递和调用。
要实现某类型的排序
那么自定义类型,要想排序,就要实现sort包中该接口。
假设有N个学生,学生有姓名和年龄,按照年龄排序结构体实例。
学生使用结构体Student,多个学生就使用切片[ ]Student。
func Ints(x []int) { Sort(IntSlice(x)) } 观察这个方法,它依赖下面的定义
// IntSlice attaches the methods of Interface to []int, sorting in increasing
order.
type IntSlice []int
func (x IntSlice) Len() int { return len(x) }
func (x IntSlice) Less(i, j int) bool { return x[i] < x[j] }
func (x IntSlice) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
就是要在[]Student上实现Interface接口的Len、Less、Swap方法。为了方便可以定义一个新类型,好实现方法。
package main
import (
"fmt"
"math/rand"
"sort"
"strconv"
"time"
)
type Student struct {
Name string
Age int
}
type StudentSlice []Student
func (x StudentSlice) Len() int { return len(x) }
func (x StudentSlice) Less(i, j int) bool { return x[i].Age < x[j].Age }
func (x StudentSlice) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
func main() {
// 随机生成学生数据
r := rand.New(rand.NewSource(time.Now().UnixNano()))
students := make([]Student, 0, 5)
for i := 0; i < 5; i++ {
name := "Tom" + strconv.Itoa(i)
age := r.Intn(30) + 20
students = append(students, Student{name, age})
}
fmt.Printf("%+v, %[1]T\n", students)
fmt.Println("~~~~~~~~~~~~~~~~~~~~~~~~~~~")
sort.Sort(StudentSlice(students))
// 强制类型转化为StudentSlice后就可以应用接口方法排序了
fmt.Printf("%+v, %[1]T\n", students)
}
[{Name:Tom0 Age:46} {Name:Tom1 Age:27} {Name:Tom2 Age:48} {Name:Tom3 Age:49} {Name:Tom4 Age:40}], []main.Student
~~~~~~~~~~~~~~~~~~~~~~~~~~~
[{Name:Tom1 Age:27} {Name:Tom4 Age:40} {Name:Tom0 Age:46} {Name:Tom2 Age:48} {Name:Tom3 Age:49}], []main.Student
对于切片来说,Len、Swap实现其实都这么写,切片中元素排序,就是某种类型的元素之间如
何比较大小不知道,能否只提出这一部分的逻辑单独提供?从而简化切片的排序。这就要靠sort.Slice(待排序切片,less函数) 了。
package main
import (
"fmt"
"math/rand"
"sort"
"strconv"
"time"
)
type Student struct {
Name string
Age int
}
func main() {
// 随机生成学生数据
r := rand.New(rand.NewSource(time.Now().UnixNano()))
students := make([]Student, 0, 5)
for i := 0; i < 5; i++ {
name := "Tom" + strconv.Itoa(i)
age := r.Intn(30) + 20
students = append(students, Student{name, age})
}
fmt.Printf("%+v, %[1]T\n", students)
fmt.Println("~~~~~~~~~~~~~~~~~~~~~~~~~~~")
sort.Slice(students, func(i, j int) bool {
return students[i].Age > students[j].Age
})
fmt.Printf("%+v,%[1]T\n", students)
}
[{Name:Tom0 Age:21} {Name:Tom1 Age:41} {Name:Tom2 Age:21} {Name:Tom3 Age:28} {Name:Tom4 Age:27}], []main.Student
~~~~~~~~~~~~~~~~~~~~~~~~~~~
[{Name:Tom1 Age:41} {Name:Tom3 Age:28} {Name:Tom4 Age:27} {Name:Tom0 Age:21} {Name:Tom2 Age:21}],[]main.Studet
map是键值对的集合,是无序的hash表。但是排序输出是序列,也就是排序所需的键或值要存入序列中,然后才能排序。
思路:提取key为序列,排序后,用有序序列中的key映射value输出
package main
import (
"fmt"
"sort"
)
func main() {
m := make(map[int]string)
m[1] = "a"
m[2] = "b"
m[0] = "c"
var keys []int
for k := range m {
keys = append(keys, k)
}
sort.Ints(keys)
fmt.Println(keys)
for _, k := range keys {
fmt.Println("Key:", k, "Value:", m[k])
}
}
[0 1 2]
Key: 0 Value: c
Key: 1 Value: a
Key: 2 Value: b
不能使用key排序思路,想象每一个键值对就是一个{key:xxx, value:yyy}的结构体实例,就转换成了结构体序列排序了。
package main
import (
"fmt"
"sort"
)
type Entry struct {
key int
Value string
}
func main() {
m := make(map[int]string)
m[1] = "a"
m[2] = "b"
m[0] = "c"
entries := make([]Entry, len(m))
i := 0 // 为什么用了i
for k, v := range m {
entries[i] = Entry{k, v}
i++
}
fmt.Println(entries)
sort.Slice(entries, func(i, j int) bool {
return entries[i].Value < entries[j].Value
}) // Value升序
fmt.Println(entries)
}
[{1 a} {2 b} {0 c}]
[{1 a} {2 b} {0 c}]