目录
1.学习内容
Go不是一个纯粹的纯面向对象语言,所以在学习中,难免会出现一些曲折,这些难以理解的问题,让我们的学习之路有点艰难,所以在学习之后,能够回过头来,在进行巩固和复习,会让我们的即使更加牢固,所以通过学习还有一些视频讲解,对Go的面向对象再次进行学习。
1.Go中的面向对象
1.概念
面向对象的四大特点:封装,继承,多态,(抽象)
封装:将业务相近的变量,函数封装为结构体(类)
化繁为简,减少直接管理的成员数,以便于做大规模的开发
继承:将公共的部分提取到父类,减少重复代码
多态:一个父类有多种不同的具体子类形态
共性:通过调用父类方法去调度子类实例
个性:不同子类对父类方法的具体实现各不相同
————
抽象:父类接口只定义方法,不做具体实现——战士接口——方法1.进攻 方法2.防守
——显卡接口——显示图形
2.封装+值引用和引用传递
package main
import "fmt"
type Person struct {
//封装结构体的属性
name string
age int
sex bool
hobby []string
}
/*封装结构体的方法
-无论方法的主语定义为值类型还是指针类型,对象值和对象指针能够正常访问
-通常会将主语定义为指针类型——毕竟西门的副本吃了饭,肉不会长到本人身上去
*/
func (p *Person) Eat() {
fmt.Println(p.name, "爱吃米饭")
}
func (p *Person) Drink() {
fmt.Println(p.name, "爱吃喝酒")
}
func (p *Person) Love() {
fmt.Println(p.name, "爱小美")
}
func (p *Person) SelfIntroduce() {
fmt.Println("I am ", p.name, "Today is ", p.age)
}
func MakeHimlove(p Person) {
p.Love()
//玩一次年轻一年
p.age -= 1
}
func Makehislove(p *Person) {
p.Love()
//玩一次年轻一年
p.age -= 1
}
// 这个方法传递值或指针,效果是一样的
func Add(p *Person, a, b int) (sum int) {
fmt.Println("做加法")
return a + b
}
func main01() {
//创建对象,通过对象的值去访问对象的属性和方法
//设置空白的对象(object)/实例(instance)
per := Person{}
//设置其属性
per.name = "西门"
//访问其方法
per.Eat()
per.Drink()
per.Love()
}
func main02() {
//创建对象,通过对象的值去访问对象的属性和方法
//创建对象时,给进行赋值
//hegePtr := &Person{
// name: "西门狗",
// age: 50,
// sex: true,
//}
//按照顺序给所有属性赋值
hegePtr := Person{"西门", 5, true, []string{"打游戏"}}
//访问其方法
hegePtr.SelfIntroduce()
hegePtr.Eat()
hegePtr.Drink()
hegePtr.Love()
//设置空白的对象(object)/实例(instance)
per := Person{}
//设置其属性
per.name = "西门"
}
func main() {
hegePtr := Person{"西门", 10, true, []string{"打游戏"}}
//指针是真身
//要求传递指针就必须传递指针
Makehislove(&hegePtr)
//要求传递值就必须传递值
MakeHimlove(hegePtr)
//值传递传递的是副本,引用传递传递的才是真身
for i := 0; i < 7; i++ {
MakeHimlove(hegePtr)
}
fmt.Println(hegePtr.age)
for i := 0; i < 7; i++ {
Makehislove(&hegePtr)
}
fmt.Println(hegePtr.age)
//通过上述实例我们可以看到,只有当指针传递的时候,才能改变当初的值
//所以我们要改变值的时候,就是需要值传递
}
3.继承
继承的目的是为了发展,增加新的属性和方法,修改父类的属性和方法,覆写(overwrite)父类方法
package main
import "fmt"
type Person struct {
//封装结构体的属性
name string
age int
sex bool
hobby []string
}
/*封装结构体的方法
-无论方法的主语定义为值类型还是指针类型,对象值和对象指针能够正常访问
-通常会将主语定义为指针类型——毕竟西门的副本吃了饭,肉不会长到本人身上去
*/
func (p *Person) Eat() {
fmt.Println(p.name, "爱吃米饭")
}
func (p *Person) Drink() {
fmt.Println(p.name, "爱吃喝酒")
}
func (p *Person) Love() {
fmt.Println(p.name, "爱小美")
}
func (p *Person) SelfIntroduce() {
fmt.Println("I am ", p.name, "Today is ", p.age)
}
// coder特有的方法
// 写代码的
type coder struct {
//持有一个父类声明
Person
//会的语言
langs []string
}
func (c *coder) Code() {
fmt.Println(c.name, "正在写代码")
}
type driver struct {
//持有一个父类声明
Person
//司机id
dId int
//是不是正在开车
idDriving bool
}
func (p *driver) Drink() {
if !p.idDriving {
fmt.Println(p.name, "爱喝酒")
} else {
fmt.Println("正在开车,不能喝酒")
}
}
func main() {
fPtr := new(driver)
//访问父类的属性和方法
fPtr.name = "嫖哥"
fPtr.idDriving = true
fPtr.Drink()
}
func main04() {
c := new(coder)
c.name = "阿门西"
c.Code()
c.langs = []string{"中文", "go"}
c.Drink()
}
4.练习01
package main
import "fmt"
// 学生结构体
type Student struct {
Reader
//学生特有属性
Name string
Major string
}
func (s *Student) Study() {
fmt.Println(s.Name, "正在学习", s.Major)
}
// 老师结构体
type Teacher struct {
Reader
//老师特有属性
Course string
Name string
}
func (t *Teacher) Teach() {
fmt.Println(t.Name, "正在教学", t.Course)
}
// 覆写交罚金,老师不用交
func (t *Teacher) PayPenalty(money float64) {
//r.Balance -= money
fmt.Println("老师不用交罚金")
}
type Book struct {
//书籍id
BookId string
//书的名字
Name string
//书的价格
Price float64
//书的作者
Author string
}
type Reader struct {
//读者id
ReaderId string
//读者余额
Balance float64
}
func (r *Reader) BorrowBook(b *Book) {
fmt.Println(r.ReaderId, "借阅了", b.BookId)
}
func (r *Reader) ReturnBook(b *Book) {
fmt.Println(r.ReaderId, "归还了", b.BookId)
}
func (r *Reader) PayPenalty(money float64) {
r.Balance -= money
fmt.Println(r.ReaderId, "缴纳罚金", money, "剩余余额", r.Balance)
}
func main() {
b1 := Book{Name: "水浒传",
Author: "小生",
Price: 20.1,
BookId: "123",
}
b2 := Book{"三国", "小理", 57.1, "1234"}
b3 := new(Book)
b3.Author = "红楼"
b3.Name = "珊莎"
b3.Price = 100
b3.BookId = "0122"
fmt.Println(b1, b2, b3)
//创建学生和老师
Stu1 := Student{Reader{"001", 100}, "狗剩", "英语"}
r1 := Reader{"002", 200}
s2 := Student{r1, "丽丽", "go"}
fmt.Println(Stu1, r1, s2)
Ter01 := Teacher{r1, "老王", "chinese"}
fmt.Println(Ter01)
//实现方法
//在这里注意new的和直接赋值的区别
s2.BorrowBook(&b1)
Ter01.Teach()
Stu1.BorrowBook(b3)
}
5.多态和接口
package main
//接口只有方法的定义,没有实现全部是抽象方法
//实现接口:结构体实现接口的全部抽象方法,就成为结构体实现方法
//多态:一个父类/接口有不同的子类实现,这个例子中有程序员等
//共性:都会劳动休息
//个性:劳动和休息方式各不相同
import (
"fmt"
"math/rand"
"time"
)
type Worker interface {
//每天工作几小时
Work(hour int) (product string)
//休息
Rest()
}
// 定义结构体
type Coder struct {
skill string
}
func (c *Coder) Work(hour int) (product string) {
fmt.Println(hour)
fmt.Println(c.skill)
return "BUG"
}
func (c *Coder) Rest() {
fmt.Println("你他妈休息啥")
}
// 码农特有的方法
func (c *Coder) Workhome() {
fmt.Println("在家工作")
}
type Product struct {
skill string
}
func (p *Product) Work(hour int) (product string) {
fmt.Println(hour)
fmt.Println(p.skill)
return "无逻辑需求"
}
func (p *Product) Rest() {
fmt.Println("你他妈休息啥")
}
type Boss struct {
skill string
}
func (b *Boss) Work(hour int) (product string) {
fmt.Println(hour)
fmt.Println(b.skill)
return "无逻辑需求"
}
func (b *Boss) Rest() {
fmt.Println("你他妈休息啥")
}
func main() {
workers := make([]Worker, 0)
workers = append(workers, &Coder{"写代码"})
workers = append(workers, &Product{"拍脑袋"})
workers = append(workers, &Boss{"吹牛比"})
r := rand.New(rand.NewSource(time.Now().UnixNano()))
weeker := r.Intn(7)
fmt.Println(weeker)
if weeker > 0 && weeker < 6 {
//全体工作
for _, worker := range workers {
//调度共性,无视具体类型
worker.Work(8)
}
} else {
//全体休息
for _, worker := range workers {
//对接口实例做类型断言(类型判断)
//判断当前worker是否是Coder指针
if coder, ok := worker.(*Coder); ok {
coder.Rest()
coder.Work(8)
} else {
fmt.Println(coder.skill)
}
}
}
}
6.类型断言
package main
import "fmt"
// 类型断言
// 判断一个接口的实例
func TyoeJudge(items ...interface{}) {
for i, x := range items {
switch x.(type) { //这里进行判断这个是哪一个类型,判断之后我们进行输出
case bool:
fmt.Printf("第%d个参数是bool类型,值是%v\n", i, x)
case float32:
fmt.Printf("第%d个参数是float32类型,值是%v\n", i, x)
case float64:
fmt.Printf("第%d个参数是float64类型,值是%v\n", i, x)
case int, int32, int64:
fmt.Printf("第%d个参数是int类型,值是%v\n", i, x)
case string:
fmt.Printf("第%d个参数是string类型,值是%v\n", i, x)
case Student01:
fmt.Printf("第%d个参数是Student01类型,值是%v\n", i, x)
case *Student01:
fmt.Printf("第%d个参数是*Student01类型,值是%v\n", i, x)
default:
fmt.Println("类型不确定")
}
}
}
type Student01 struct {
}
// // 在类型断言的时候,我们还可以进行特定的类型断言
//
// if coder,ok := xxx.(*Coder):ok{
// //确定是程序猿
// //此时是指针类型
// } else{
//
// //判断不是
// }
func main() {
var n1 float64 = 1.2
var n2 float32 = 3.1
var n3 int = 34
var n4 string = "123"
stu1 := Student01{}
stu2 := &Student01{}
TyoeJudge(n1, n2, n3, n4, stu2, stu1)
}
7.接口和继承
这里就主要写了两者关系,后面没有实现接口的具体方法,主要讲解的是有显示和隐式两种继承方法,当一个结构体的指针实现了一个接口,就可以赋值给一个接口,就是实现了这个的接口,如果没有实现其中的一个方法的话,那就是没有实现
// 的定义两个父类接口
// 定义人接口
type People interface {
Eat(foot string) (name string)
Die()
}
// 定义战士
type Fighter interface {
Attack() (booldloss int)
Defind()
}
// 显示的继承父类接口
type Beast interface {
People
Fighter
Run()
}
// 隐式继承父类接口
type Beast01 interface {
People
//隐式继承接口,没有明确的说继承,但是事实上定义了其全部抽象的方法
Attack() (booldloss int)
Defind()
//特有方法
Run()
}
8.回顾
9.重难点
-
在调用方法的时候,使用值和值都可以调用——建议是,尽量使用指针,能最大程度的避免对本来的修改影响不到真身
-
作主语时,使用 值或指针效果一致,但做参数时,一个是拷贝式值类型,一个是地址引用类型
-
无论使用值还是指针做主语,去访问属性和方法,其功能是一致的:
p := &Person{age: 10} p.Love()
-
定义方法是,方法的主语尽量使用指针,一方面是模仿SDK,一方面是最大程度减少值传递的拷贝效应的负面影响
-
对象做参数,值传递是拷贝的:指针传递(引用传递/地址传递)才是传递真身
-
在OOP的世界里,值能够做的指针都能做,反过来就是不成立的
-
创建对象的方式:
-
创建空白对象:p := Person{}
-
创建对象时,有选择的给指定属性赋值:p :=Person(Age:20,Name:"张三“)
-
创建对象时,完整有序的给所有属性赋值:p :=Person(20,"张三“)
-
通过内建函数创建对象指针: pPter := new(Person):new的参数
-
-
new(Type)*Type用于创建结构体的实例指针,参数是结构体的名字,返回的是{所有属性都为默认值的对象}的指针
-
名字的命名可以深度的望文生义,
-
类型名称大写开头,外部包可以创建它的对象,反之亦然,
-
属性名和方法名大写开头,外部包就可以通过实例(或指针)进行访问,反之亦然
-
对接口的实现,语法上要严格区分到底是实例实现了接口还是指针实现了接口,推荐指针
-
对接口进行类型断言,只能断言为实例或指针中的一种,具体取决于【到底是实例还是指针】
-
Woeker,workers := make([]Worker, 0),是接口能把实例a丢入一个切片,意味着a必须是实现了这个接口的子类实例或指针,
-
只要a是该接口的实例,就可以进行append,也可以var worker = a,进行赋值
-
接口实例只认指针!不管实现接口方法的主语是指针还是值!断言也只能断言为指针!
2.总结
面向对象是重要的一部分,在日后的学习之中,肯定是有重要帮助的,所以我们需要进行学习,并且是重点学习,这可以让我们的基础更加牢靠,所以,当一段时间之后,就要复习,与便于让啊我们对这个理解加深,使得我们学习有更好的成效。