go-面向对象编程-上
面向对象的特点
- golang支持面向对象编程(OOP),但是和传统的面向对象编程有区别,并不是纯粹的面向对象语言。
- golang没有类(class),golang的结构图(struct)和其他编程语言的类(class)有同等的地位,可以理解为golang是基于struct来实现OOP特性
- golang面向对象编程非常简介,去掉了传统oop语言的继承、方法重载、构造函数和析构函数、隐藏的this指针等等
- golang仍然有面向对象编程的继承、封装和多态的特性,只是实现的方式和其他OOP语言不一样
- golang面向对象(OOP)很优雅,OOP本身是语言类型系统的一部分,通过接口(interface)关联,耦合性低,非常灵活。
struct结构体
type Cat struct {
Name string
Age int
Color string
}
func main() {
var cat1 Cat = Cat{"tom", 3, "withe"}
fmt.Printf("cat1: %v\n", cat1) // cat1: {tom 3 withe}
cat1.Name = "xiaobai"
fmt.Printf("cat1.Name: %v\n", cat1.Name) // cat1.Name
fmt.Printf("cat1.Age: %v\n", cat1.Age) // cat1.Age: 3
fmt.Printf("cat1.Color: %v\n", cat1.Color) // cat1.Color: withe
}
内存布局
- 结构图属于值类型
- 声明时就给定了内存空间,且有默认值
结构体的使用
- 字段 = 属性 = field
- 字段是结构体的一个组成部分,一般是基本数据类型、数组、也可以是引用类型
- 字段声明语法同遍历一样,字段名 字段类型
- 字段的类型可以为:基本类型、数组、引用类型
- 在创建一个结构体变量后,若没有给字段赋值,都对应一个默认值
- 不同结构体遍历的字段是独立的,互不影响,一个结构体遍历字段的更改,不影响另一个,结构体是值类型
- 当类型是指针、slice、map时,默认是nil,未分配空间。若要使用的话,需要先make,才能使用
func main() {
var tem Temp
tem.Name = "tem"
tem.Array[1] = 1
tem.Slice = make([]int, 3)
tem.Slice[1] = 3
age := 12
tem.Ptr = &age
tem.Map = make(map[string]int, 0)
tem.Map["haha"] = 5
tem.Map["xixi"] = 9
fmt.Printf("tem: %v\n", tem)
// tem: {tem [0 1 0] [0 3 0] 0xc00010c008 map[haha:5 xixi:9]}
}
type Temp struct {
Name string
Array [3]float64 // 数组
Slice []int // 切片
Ptr *int // 指针
Map map[string]int // map
}
var people1 People = People{"tom", 999}
people2 := people1 // 值拷贝,值传递
people2.Name = "jack"
fmt.Printf("people1: %v\n", people1) // people1: {tom 999}
fmt.Printf("people2: %v\n", people2) // people2: {jack 999}
type People struct {
Name string
Age int
}
结构体声明
type 结构体名称 struct{
field1 type
Field2 type // public
}
实例化-四种方式
- 第三种方法和第四种方法返回的是 结构体指针
- 结构体指针访问字段的标准方式是
(*结构体指针).字段名
,(*people).Name = "tom"
- golang做了简化,支持
结构体指针.字段名
,people.Name = "tom"
func main() {
// 1.
var people1 People
people1.Name = "jack"
people1.Age = 11
fmt.Printf("people1: %v\n", people1) // people1: {jack 11}
// 2.
var people2 People = People{"tome", 22}
fmt.Printf("people2: %v\n", people2) // people2: {tome 22}
// 3.
var people3 *People = new(People)
(*people3).Name = "mark" // 标准写法 <==> people3.Name
people3.Name = "mark"
people3.Age = 33
fmt.Printf("people3: %v\n", people3) // people3: &{mark 33}
people5 := people3
people5.Age = 55
people3.Name = "xxx"
fmt.Printf("people3: %v\n", people3) // people3: &{xxx 55}
fmt.Printf("people5: %v\n", people5) // people5: &{xxx 55}
// 4.
var people4 *People = &People{"nacy", 44}
people6 := people4
fmt.Printf("people4: %v\n", people4) // people4: &{nacy 44}
people6.Age = 66
fmt.Printf("people4: %v\n", people4) // people4: &{nacy 66}
fmt.Printf("people6: %v\n", people6) // people6: &{nacy 66}
}
type People struct {
Name string
Age int
}
结构体内存分配机制
type People struct {
Name string
Age int
}
func main() {
var p1 *People = new(People)
p1.Name = "tom"
p1.Age = 22
p2 := p1
p2.Name = "joun"
fmt.Printf("p1: %v\n", p1) // p1: &{joun 22}
fmt.Printf("p2: %v\n", p2) // p2: &{joun 22}
fmt.Printf("p1的地址: %v\n", &p1) // p1的地址: 0xc0000ac018
fmt.Printf("p2的地址: %v\n", &p2) // p2的地址: 0xc0000ac020
fmt.Printf("p2: %p\n", p2) // p2: 0xc0000a4018
}
结构体使用细节
- 结构体中的所有字段在内存中是连续的
type Point struct {
x int
y int
}
type Rect struct {
leftUp, rightDown Point
}
type Rect2 struct {
leftUp, rightDown *Point
}
func main() {
r1 := Rect{Point{1, 2}, Point{3, 4}}
fmt.Printf("r1: %v\n", r1) // r1: {{1 2} {3 4}}
// 1. 内存分布连续
fmt.Printf("r1.leftUP.x地址 = %p \n", &r1.leftUp.x) // r1.leftUP.x地址 = 0xc00012e000 +8
fmt.Printf("r1.leftUP.y地址 = %p \n", &r1.leftUp.y) // r1.leftUP.y地址 = 0xc00012e008 +8
fmt.Printf("r1.rightDown.x地址 = %p \n", &r1.rightDown.x) // r1.rightDown.x地址 = 0xc00012e010 +8
fmt.Printf("r1.rightDown.y地址 = %p \n", &r1.rightDown.y) // r1.rightDown.y地址 = 0xc00012e018
// 2. r2有两个 *Point类型,这两个*point类型的本身地址也是连续的
// 但是他们所指向的地址不一定连续,取决于系统的执行
r2 := Rect2{&Point{11, 22}, &Point{33, 44}}
fmt.Printf("r2: %v\n", r2) // r2: {0xc0000160c0 0xc0000160d0}
fmt.Printf("r2.leftUP地址 = %p \n", &r2.leftUp) // r2.leftUP地址 = 0xc000010250
fmt.Printf("r2.rightDown地址 = %p \n", &r2.rightDown) // r2.rightDown地址 = 0xc000010258
fmt.Printf("r2.leftUP指向的地址 = %p \n", r2.leftUp) // r2.leftUP指向的地址 = 0xc0000160c0 + 16
fmt.Printf("r2.rightDown指向的地址 = %p \n", r2.rightDown) // r2.rightDown指向的地址 = 0xc0000160d0
fmt.Printf("r2.leftUP.x地址 = %p \n", &r2.leftUp.x) // r2.leftUP.x地址 = 0xc0000160c0 +8
fmt.Printf("r2.leftUP.y地址 = %p \n", &r2.leftUp.y) // r2.leftUP.y地址 = 0xc0000160c8 +8
fmt.Printf("r2.rightDown.x地址 = %p \n", &r2.rightDown.x) // r2.rightDown.x地址 = 0xc0000160d0 +8
fmt.Printf("r2.rightDown.y地址 = %p \n", &r2.rightDown.y) // r2.rightDown.y地址 = 0xc0000160d8
}
- 结构体是用户单独定义的类型,和其他类型进行转换时,需要有完全相同的字段(名字、个数、类型)
func main() {
var a A = A{11}
var b B
fmt.Printf("a: %v\n", a) // a: {11}
fmt.Printf("b: %v\n", b) // b: {0}
b = B(a)
fmt.Printf("b: %v\n", b) // b: {11}
}
type A struct {
num int
}
type B struct {
num int
}
- 结构体进行type重新定义(相当于取别名),Golang认为是新的数据类型,但是相互间可以进行强转
func main() {
var stu1 Student = Student{"tom"}
var stu2 Stu
stu2 = Stu(stu1) // struct 类型需要强转
fmt.Printf("stu2: %v\n", stu2) // stu2: {tom}
var a int = 1
var b Integer
b = Integer(a) // 基本数据类型也需要强转
fmt.Printf("b: %v\n", b) //b: 1
}
type Student struct {
Name string
}
type Stu Student
type Integer int
- struct的每个字段上,可以写一个tag,该tag可以通过反射机制获取,常见的使用场景就是序列化和反序列化
type Monster struct {
Name string `json:"name"`
Age int `json:"age"`
Skill string `json:"skill"`
}
func main() {
// 1. 创建一个Monster变量
monster := Monster{"niumowang", 505, "bajiaoshan"}
fmt.Printf("monster: %v\n", monster)
// 2. 将monster序列化为json字符串
// json.Marshal 函数中使用了反射机制
data, err := json.Marshal(monster)
if err == nil {
fmt.Printf("data: %c\n", data)
// data: [{ " n a m e " : " n i u m o w a n g " , " a g e " : 5 0 5 , " s k i l l " : " b a j i a o s h a n " }]
fmt.Printf("string(data): %v\n", string(data)) // []byte --> string
// string(data): {"name":"niumowang","age":505,"skill":"bajiaoshan"}
} else {
fmt.Printf("err: %v\n", err)
}
}
方法
- golang中的方法是作用在指定的数据类型上,因此自定义类型,都可以有方法,而不仅仅是struct
func ([结构体变量] 结构体) 方法名(参数) 返回值 { }
方法的使用
func main() {
var people People = People{"tom", 22}
fmt.Printf("people: %v\n", people) // people: {tom 22}
people.say() // 调用say hahaha \n p.Name: tom
res1, res2 := people.run(10, 20) // 调用run
fmt.Printf("res1: %v res2: %v\n", res1, res2) // res1: 30 res2: -10
people.UpdateName("jack")
fmt.Printf("people.Name: %v\n", people.Name) // people.Name: tom
(*&people).UpdateAge(33)
fmt.Printf("people.Age: %v\n", people.Age) // people.Age: 33
people.UpdateAge(55)
fmt.Printf("people.Age: %v\n", people.Age) // people.Age: 55
}
type People struct {
Name string
Age int
}
func (p People) say() {
fmt.Println("hahaha")
fmt.Printf("p.Name: %v\n", p.Name) // 使用结构体中的字段
}
func (p People) run(a, b int) (int, int) {
return a + b, a - b
}
func (p People) UpdateName(name string) {
p.Name = name
fmt.Printf("p.Name: %v\n", p.Name) // p.Name: jack
}
func (p *People) UpdateAge(age int) {
p.Age = age
}
方法调用和传参机制
- 在通过一个变量去调用方法时,其调用机制和函数一样
- 变量调用方法时,该变量本身也会作为一个参数传递到方法(若变量是值类型,则是值拷贝;若是引用类型,则是地址拷贝)
func main() {
var circle Circle
circle.radius = 1
area := circle.area()
fmt.Printf("area: %v\n", area) // area: 3.141592653589793
}
type Circle struct {
radius float64
}
func (circle Circle) area() float64 {
res := math.Pow(circle.radius, 2.0) * math.Pi
return res
}
方法的声明
func (recevier type) methodName(参数列表) (返回值列表) {
方法体
return 返回值
}
- 参数列表:表示方法的输入
- recevier type: 便是这个方法和type这个类型进行绑定,或者说该方法作用于type类型
- recevier tye: type可以是结构体,也可以是其他的自定义类型
- recevier:就是type类型的一个变量
- 返回值列表:便是返回的值,可以是多个
- 方法主体:表示为了实现某一功能代码块
- return语句不是必须的
注意细节
-
结构体类型是值类型,在方法调用中,遵守值类型的传递机制,是值拷贝传递方式
-
若希望在方法中,修改结构体变量的值,可以通过结构体指针的方式来处理
-
golang中的方法作业再指定的数据类型上的,因此自定义类型,都可以都方法,而不仅仅是struct,比如int,float32
func main() { var a Interge a.hello() } type Interge int func (integer Interge) hello() { fmt.Println("hello int") }
-
方法的访问范围控制的规则和函数一样。方法名首字母小写,只能在本包访问,方法首字母大写,可以在本包和其他包中访问
-
若一个变量实现了String() 方法,那么fmt.Ptintln默认会调用这个变量的String()进行输出
func main() { stu := Student{ Name: "tom", Age: 22, } fmt.Println(&stu) // Name = [tom] Age = [22] } type Student struct { Name string Age int } func (stu *Student) String() string { str := fmt.Sprintf("Name = [%v] Age = [%v]", stu.Name, stu.Age) return str }
方法和函数的区别
-
调用方式不一样
函数的调用方式:函数名(实参列表)
方法的调用方式:变量.方法名(实参列表)
-
对于普通函数,接受者为值类型,不能将指针类型的数据直接传递,,反之亦然
-
对于方法,接受者为值类型是,可以只用用指针类型的变量调用方法,反过来同样也可以
-
真正决定是值拷贝还是地址拷贝的是,方法本身的类型决定
若方法本身是值类型,则是值拷贝。若方法本身是引用类型,则是地址拷贝
func main() {
var p People = People{"tom", 22}
test1(p)
fmt.Printf("p: %v\n", p) // p: {tom 22}
test2(&p)
fmt.Printf("p: %v\n", p) // p: {tom 22}
p.test3()
(&p).test3() // 这个也不会改变name,仍是值传递
fmt.Printf("p: %v\n", p) // p: {tom 22}
p.test4() // 这个看似是值传递,实则是引用传递
(&p).test4()
fmt.Printf("p: %v\n", p) // p: {mary 22}
}
type People struct {
name string
age int
}
func test1(p People) {
fmt.Printf("p.name: %v\n", p.name) // p.name: tom
}
func test2(p *People) {
fmt.Printf("p.name: %v\n", p.name) // p.name: tom
}
func (p People) test3() {
p.name = "jack"
fmt.Printf("p.name: %v\n", p.name) // p.name: jack
}
func (p *People) test4() {
p.name = "mary"
fmt.Printf("p.name: %v\n", p.name) // p.name: mary
}
工厂模式
model
student.go
type Student struct {
Name string
Age int
}
func (student *Student) GetSum(a, b int) int {
return a + b
}
// people 首字母未大写, 因此需要使用工厂模式
type people struct {
Name string
age int
}
// 工厂模式
func NewPeople(name string, age int) *people {
people := people{name, age}
return &people
}
func (p *people) SetAge(age int) {
p.age = age
}
model
main.go
import (
"fmt"
"go_code/Study_go/Study_go_nine/demo17/model"
)
func main() {
var student model.Student = model.Student{
Age: 20,
Name: "tom",
}
fmt.Printf("student: %v\n", student) // student: {tom 20}
fmt.Printf("student.GetSum(20, 30): %v\n", student.GetSum(20, 30)) // student.GetSum(20, 30): 50
// 工厂模式 -- 类似于 java中的构造函数
p := model.NewPeople("jack", 22) // p是引用类型 指针
// p.age = 20 不能访问age
fmt.Printf("p: %v\n", p) // p: &{jack 22}
p.Name = "xixi"
fmt.Printf("p: %v\n", p) // p: &{xixi 22}
fmt.Printf("p: %v\n", *p) // p: {xixi 22}
p.SetAge(33)
fmt.Printf("p: %v\n", *p) // p: {xixi 33}
}
go-面向对象编程-下
抽象/封装
认识
type Account struct {
AccountNo string
Pwd string
Balance float64
}
func (account *Account) Deposite(money float64, pwd string) {
if account.Pwd == pwd {
account.Balance += money
fmt.Println("success")
} else {
fmt.Println("password error")
}
}
func (account *Account) WithDraw(money float64, pwd string) {
if pwd == account.Pwd {
if money > account.Balance {
fmt.Println("balance erro")
} else {
account.Balance -= money
fmt.Println("success")
}
} else {
fmt.Println("password error")
}
}
func (account *Account) Query(pwd string) {
if pwd == account.Pwd {
fmt.Printf("account.Balance: %v\n", account.Balance)
} else {
fmt.Println("password error")
}
}
import "go_code/Study_go/Study_go_ten/demo01/main/pojo"
func main() {
var account pojo.Account = pojo.Account{
AccountNo: "123123123",
Pwd: "123123",
Balance: 50.0,
}
account.Query("123123") // account.Balance: 50
account.Deposite(50, "123123") // success
account.Query("123123") // account.Balance: 100
account.WithDraw(80, "123123") // success
account.Query("123123") // account.Balance: 20
}
封装
-
封装就是把抽象出来的字段和对字段的操作封装在一起,数据被保护在内部,程序的其他包只有通过被授权的操作(方法),才能对字段进行操作
-
好处:隐藏实现细节,可以对数据进行验证,保证安全合理
-
封装实现步骤:
-
结构体、字段的首字母小写
-
给结构体所在包提供一个工厂模式的函数,首字母大写,类似一个构造函数
-
提供一个首字母大写的Set方法,用于对属性判断并赋值
func (var 结构体类型名) SetXxx(参数列表)(返回值列表) {
var.字段 = 参数
}
-
提供一个首字母大写Get方法,用于获取属性的值
func (var 结构体类型名) GetXxx() 返回值类型 {
return var.字段
}
-
import (
"fmt"
"go_code/Study_go/Study_go_ten/demo02/pojo"
)
func main() {
person := pojo.NewPerson("tom", 22, 11231.2)
fmt.Printf("person: %v\n", person) // person: &{tom 22 11231.2}
person.SetAge(33)
person.SetSal(23321.2)
fmt.Printf("person.GetAge(): %v\n", person.GetAge()) // person.GetAge(): 33
fmt.Printf("person.GetSal(): %v\n", person.GetSal()) // person.GetSal(): 23321.2
}
type person struct {
name string
age int
sal float64
}
// 工厂模式
func NewPerson(name string, age int, sal float64) *person {
person := person{}
person.name = name
person.age = age
person.sal = sal
return &person
}
func (p *person) SetAge(age int) {
p.age = age
}
func (p *person) GetAge() int {
return p.age
}
func (p *person) SetSal(sal float64) {
p.sal = sal
}
func (p *person) GetSal() float64 {
return p.sal
}
继承
-
在golang中,若一个struct嵌套了另一个匿名结构体,那么这个结构体可以直接访问匿名结构体的字段和方法,从而实现了继承特性
-
基本语法
基本语法: type Goods struct { Name string Price int } type Book struct { Goods // 这里就是嵌套匿名结构体 Writer string } // 举例说明 type Student struct { Name string Age int Score float64 } func (stu *Student) showInfo() { fmt.Printf("stu: %v\n", stu) } func (stu *Student) setScore(score float64) { stu.Score = score } type Pupil struct { Student // 嵌入匿名结构体Student } func (p *Pupil) testing() { fmt.Println("pupil testing ...") } type Graduate struct { Student // 嵌入匿名结构体Student } func (graduate *Graduate) testing() { fmt.Println("graduate testing ...") } func main() { var pupil *Pupil = &Pupil{} pupil.Name = "tom" pupil.Age = 11 pupil.testing() pupil.setScore(90) pupil.showInfo() // stu: &{tom 11 90} }
-
结构体可以使用嵌套匿名结构体所有的字段和方法,即:首字母大写或小写的字段、方法,都可以使用
-
匿名结构体字段访问可简化
a.A.Name = xx ==> a.Name = xx
-
当结构体和匿名结构体有相同的字段或者是方法时,编译器采用就近访问原则访问,如希望访问匿名结构体的字段和方法,可以通过匿名结构体名来区分
b.B.Hello() 访问匿名结构体的方法
而b.Hello() 访问本结构体的方法
package main
import "fmt"
type A struct {
name string
Age int
}
func (a *A) sayhello() {
fmt.Println("A hello")
}
func (a *A) setAge() {
a.Age = 10
fmt.Printf("a.Age: %v\n", a.Age)
}
type B struct {
A
Age int
}
func (b *B) sayhello() {
fmt.Println("B hello")
}
func main() {
var b B
b.A.name = "tom"
b.name = "jack" // 简化调用
b.A.Age = 12 // 就近调用
b.Age = 22 // 就近调用
b.sayhello() // B hello
b.A.sayhello() // A hello
fmt.Printf("b: %v\n", b) // b: {{jack 12} 22}
}
-
多继承:结构体嵌入两个(或多个)匿名结构体,如两个匿名结构体有相同的字段和方法(同时结构体本身没有同名的字段和方法),在访问时,就必须明确指定匿名结构体名字,否则编译报错。
- 若嵌入的匿名结构体有相同的字段名或者方法名,则在访问时,需要通过匿名结构体类型来区分
- 为了保证代码的简洁性,建议不使用多继承
type A struct { name string age int } func (a *A) say() { fmt.Println("AAAAA") } type B struct { name string age int } func (b *B) say() { fmt.Println("BBBBB") } type C struct { A B } func main() { var c C c.A.name = "tom" c.B.name = "jack" c.A.age = 11 c.B.age = 22 fmt.Printf("c: %v\n", c) // c: {{tom 11} {jack 22}} c.A.say() // AAAAA c.B.say() // BBBBB }
-
若一个struct嵌套了一个有名结构体,这种模式就是组合模式,若是组合关系,那么在访问组合的结构体的字段或方法时,必须带上结构体的名字
type A struct { name string age int } func (a *A) say() { fmt.Println("hello aaaaa") } type B struct { a A } func main() { var b B b.a.name = "tom" b.a.age = 11 fmt.Printf("b: %v\n", b) // b: {{tom 11}} b.a.say() // hello aaaaa }
-
嵌套匿名结构体后,也可以在创建结构体变量时,直接指定各个匿名结构体的字段值
type Goods struct { Name string Price float64 } type Brand struct { Name string Address string } type TV struct { *Goods *Brand } func main() { tv := TV{ Goods: &Goods{ Name: "TV", Price: 2999.99, }, Brand: &Brand{ Name: "meidi", Address: "beijing", }, } fmt.Printf("tv: %v\n", tv) // tv: {{TV 2999.99} {meidi beijing}} // tv: {0xc0000a4018 0xc0000b6000} tv1 := &TV{&Goods{"air-d", 3999.99}, &Brand{"haier", "shanghai"}} fmt.Printf("tv1: %v\n", tv1) // tv1: &{0xc00000c048 0xc000060040} fmt.Printf("tv1: %v\n", *tv1) // tv1: {0xc00000c048 0xc000060040} fmt.Printf("tv1.Goods: %v\n", tv1.Goods) // tv1.Goods: &{air-d 3999.99 fmt.Printf("tv1.Brand: %v\n", tv1.Brand) // tv1.Brand: &{haier shanghai} }
-
基本数据类型也可以作为继承的匿名结构体
- 若一个结构体有int类型的匿名字段,就不能有第二个
- 若需要有多个int字段,则必须给int字段指定名字
接口
认识
type USB interface {
Start()
Stop()
}
type Phone struct {
}
type Carmer struct {
}
// 实现接口:只要是实现了结构的所有方法,就是实现了接口
func (p Phone) Start() {
fmt.Println("phone start")
}
func (p Phone) Stop() {
fmt.Println("phone stop")
}
func (c Carmer) Start() {
fmt.Println("carmer start")
}
func (c Carmer) Stop() {
fmt.Println("carmer stop")
}
type Computer struct {
}
// 体现多态:接收参数是接口结构,只要实现此结构的所有结构体都可作为参数
func (computer Computer) Working(usb USB) {
usb.Start()
usb.Stop()
}
func main() {
computer := Computer{}
phone := Phone{}
carmer := Carmer{}
computer.Working(phone) // phone start \n phone stop
computer.Working(carmer) // carmer start \n carmer stop
}
介绍
-
interface类型可以定义一组方法,但是这些不需要实现。并且interface不能包含任何变量
-
基本语法:
type 接口名 interface {
method1(参数列表) 返回值列表
method2(参数列表) 返回值列表
}
-
接口中的所有方法都没有方法体,即接口中的方法都是没有实现的方法。接口体现了程序设计的多态和高内聚低耦合的思想
-
Golang中的接口,不需要显式的实现,只要一个变量,含有接口类型中的所有方法,那么这个变量就实现了这个接口。(不需要implement关键字)
注意事项和细节
-
接口本身不能实例化,但是可以指向一个实现了该接口的自定义类型的变量(实例)
-
接口中的所有方法都没有方法体,即都是没有实现的方法
-
在Golang中,一个自定义类型需要将某个接口的所有方法实现,即实现了这个接口
-
一个自定义类型只有实现了某个接口,才能将该自定义类型的实例赋给接口变量
-
只要是自定义类型,就可以实现接口,不仅仅是结构体类型
type Person interface { Say() } type Student struct { } func (stu Student) Say() { fmt.Println("Student say") } type Integer int func (i Integer) Say() { fmt.Println("Integer say i = ", i) } func main() { var student Student var person1 Person = student person1.Say() // Student say var i Integer = 10 var person2 Person = i person2.Say() // Integer say i = 10 }
-
一个自定义类型可以实现多个接口
-
Golang接口中不能有变量
-
一个接口可以继承多个别的接口,这时若要实例化接口,就必须实现所有接口的方法
type A interface { SayA() // name string 接口不能有变量 } type B interface { SayB() } type C struct { } func (c C) SayA() { fmt.Println("c SayA") } func (c C) SayB() { fmt.Println("c SayB") } func (c C) SayD() { fmt.Println("c SayD") } type D interface { // 继承了接口A B A B SayD() } func main() { var c C var a A = c a.SayA() // c SayA var b B = c b.SayB() // c SayB var d D = c d.SayA() // c SayA d.SayB() // c SayB d.SayD() // c SayD }
-
interface类型默认是一个指针,若没有对interface初始化使用,那么输出nil
-
空接口interface{} 没有任何方法,所以所有类型都实现了这个空接口
-
当接口继承其他多个接口时,这多个接口不能出现相同的方法,若出现了相同相同的方法,则编译出错,减少冗余
-
结构体实现方法时,绑定的结构体不能是
*Student
,因为Student 和 *Student不是一个类型type Person interface { Say() } type Student struct { } func (student *Student) Say() { fmt.Println("student say") } func main() { var a Person fmt.Printf("a: %v\n", a) // a: <nil> fmt.Printf("a: %p\n", &a) // a: 0xc000098210 var student Student a = &student // 注意:指针地址 fmt.Printf("a: %v\n", a) // a: {} fmt.Printf("a: %p\n", &a) // a: 0xc000098210 student.Say() // student say a.Say() // 指针调用:student say var temp interface{} = student fmt.Printf("temp: %v\n", temp) // temp: {} var num int = 22 temp = num fmt.Printf("temp: %v\n", temp) // temp: 22 }
经典案例
import (
"fmt"
"sort"
)
type Hero struct {
Name string
Age int
skill int
}
type HeroSlice []Hero
func (heros HeroSlice) Len() int { return len(heros) }
func (heros HeroSlice) Swap(i, j int) { heros[i], heros[j] = heros[j], heros[i] }
func (heros HeroSlice) Less(i, j int) bool { return heros[i].skill < heros[j].skill }
func main() {
// 切片的基础排序
var sliceNum []int = []int{2, 3, 5, 7, 1, 3}
sliceNum = append(sliceNum, 500, 100)
sort.Ints(sliceNum)
fmt.Printf("sliceNum: %v\n", sliceNum) // sliceNum: [1 2 3 3 5 7 100 500]
var heros HeroSlice
heros = append(heros, Hero{"tom", 11, 1000})
heros = append(heros, Hero{"jack", 15, 6000})
heros = append(heros, Hero{"mary", 22, 3000})
fmt.Printf("hero: %v\n", heros) // hero: [{tom 11 1000} {jack 15 6000} {mary 22 3000}]
sort.Sort(heros)
for _, v := range heros {
fmt.Printf("v: %v\n", v)
// v: {tom 11 1000}
// v: {mary 22 3000}
// v: {jack 15 6000}
}
// 内置切片排序
sort.Slice(heros, func(i, j int) bool {
return heros[i].Age < heros[j].Age
})
fmt.Printf("hero: %v\n", heros) // hero: [{tom 11 1000} {jack 15 6000} {mary 22 3000}]
}
接口与集成的比较
- 继承的价值主要在于:解决代码的复用性和可维护性
- 接口的价值在于:设计的规范性,设计好各种规范(方法),让其他自定义类型去实现这些方法
- 接口比继承更加灵活:继承是满足is-a的关系,而接口只需满足like-a的关系
- 接口一定程度上实现代码解耦
示例1
type Monkey struct {
Name string
}
func (monkey *Monkey) climbing() {
fmt.Println("monkey climbing")
}
type BirdAble interface {
Flying()
}
type FishAble interface {
Swimming()
}
type LitterMonkey struct {
Monkey
}
func (litterMonkey *LitterMonkey) Flying() {
fmt.Println("litterMonkey flying")
}
func (litterMonkey *LitterMonkey) Swimming() {
fmt.Println("litterMonkey swimming")
}
func main() {
monkey := LitterMonkey{
Monkey{
Name: "wukong",
},
}
fmt.Printf("monkey: %v\n", monkey) // monkey: {{wukong}}
monkey.climbing() // monkey climbing
monkey.Flying() // litterMonkey flying
monkey.Swimming() // litterMonkey swimming
}
示例2
type Person struct {
Name string
Age int
}
type Student struct {
Person
Score float64
}
type BigStudent struct {
Student
Professional string
}
type SmallStudent struct {
Student
Enjoy string
}
type Athlet struct {
Person
Address string
}
type BacketballAthlet struct {
Athlet
Team string
}
type SoccesAthlet struct {
Athlet
Guojia string
}
type English interface {
Learn()
}
func (student *BigStudent) Learn() {
fmt.Println("studet learn English")
}
func (athlet *BacketballAthlet) Learn() {
fmt.Println("athlet learn English")
}
func main() {
}
多态
类似接口中的USB
多态参数
在前面的USB接口案例中,USB可以接收手机变量,也可以接收相机变量,就体现了USB接口的多态
多态数组
给USB数组中,存放Phone结构体和Camera结构体变量,Phone还有一个特有的方法call(),请遍历USB数组,若是Phone变量,除了调用USB接口声明的方法外,还需要调用Phone 特有的方法call()
type USB interface {
Start()
Stop()
}
type Phone struct {
Brand string
}
type Carmer struct {
Brand string
}
func (phone Phone) Start() {
fmt.Println("phone start")
}
func (phone Phone) Stop() {
fmt.Println("phone stop")
}
func (phone Phone) Call() {
fmt.Println("phone Call")
}
func (carmer Carmer) Start() {
fmt.Println("carmer start")
}
func (carmer Carmer) Stop() {
fmt.Println("carmer stop")
}
type Computer struct {
}
// 多态参数
func (computer Computer) Connect(usb USB) {
usb.Start()
usb.Stop()
}
func main() {
// 1. 多态参数的使用
var phone Phone
var carmer Carmer
var computer Computer
computer.Connect(phone)
computer.Connect(carmer)
// phone start
// phone stop
// carmer start
// carmer stop
// 2. 多态数组
var usb [3]USB
usb[0] = Phone{"xiaomi"}
usb[1] = Carmer{"suoni"}
usb[2] = Phone{"huawei"}
fmt.Printf("usb: %v\n", usb) // usb: [{xiaomi} {suoni} {huawei}]
for _, v := range usb {
computer.Connect(v)
p, err := v.(Phone)
if err {
p.Call()
}
}
/*
phone start
phone stop
phone Call
carmer start
carmer stop
phone start
phone stop
phone Call
*/
}
类型断言
type Point struct {
x int
y int
}
func main() {
// 案例 1
var a interface{}
var point Point = Point{1, 2}
a = point
var b Point
b = a.(Point) // 类型断言
fmt.Printf("b: %v\n", b) // b: {1 2}
// 案例 2
var c interface{}
var d float32 = 5.6
c = d
e := c.(float32)
fmt.Printf("e: %v\n", e) // e: 5.6
// 案例 3 带判断的类型断言
f, res := c.(int)
if res { // res: false
fmt.Printf("f: %v\n", f)
} else {
fmt.Printf("res: %v\n", res)
}
}
示例
func TypeJudge(parameters ...interface{}) {
for index, val := range parameters {
switch val.(type) {
case bool:
fmt.Printf("index: %v =bool= val: %v\n", index, val)
case float32:
fmt.Printf("index: %v =float32= val: %v\n", index, val)
case float64:
fmt.Printf("index: %v =float64= val: %v\n", index, val)
case int, int32, int64, int8:
fmt.Printf("index: %v =ints= val: %v\n", index, val)
case string:
fmt.Printf("index: %v =string= val: %v\n", index, val)
case Student:
fmt.Printf("index: %v =Student= val: %v\n", index, val)
case *Student:
stu := val.(*Student)
fmt.Printf("index: %v =*Student= val: %v\n", index, *stu)
default:
fmt.Printf("index: %v =no= val: %v\n", index, val)
}
}
}
type Student struct {
Name string
}
func main() {
n1 := 300
var n2 float32 = 12.3
var n3 float64 = 153.69
name := "tom"
address := "beijing"
var nums []int = []int{1, 2, 3}
var student Student = Student{"jack"}
var stuPointer *Student = &Student{"mary"}
TypeJudge(n1, n2, n3, name, address, nums, student, stuPointer)
/*
index: 0 =ints= val: 300
index: 1 =float32= val: 12.3
index: 2 =float64= val: 153.69
index: 3 =string= val: tom
index: 4 =string= val: beijing
index: 5 =no= val: [1 2 3]
index: 6 =Student= val: {jack}
index: 7 =*Student= val: &{mary}
*/
}