面向过程
所谓的面向过程就是:强调的是步骤、过程、每一步都是自己亲自去实现的。
面向对象
所谓的面向对象其实就是找一个专门做这个事的人来做,不用关心具体怎么实现的。
所以说,面向过程强调的是过程,步骤。而面向对象强调的是对象,也就是干事的人。
在程序中,可以通过属性和方法(函数)来描述类。属性就是特征,方法(函数)就是行为。
面向对象编程好处
封装
继承
多态
继承
继承是一种类间关系,描述一个类从另一个类获取成员信息的类间关系。
继承必定发生在两个类之间,参与继承关系的双方称为父类和子类。
父类提供成员信息,子类获取成员信息。
通过匿名字段来实现继承
package main
import "fmt"
type Student struct {
//属性---成员
//方法---函数
Person //匿名字段,只有类型,没有成员的名字
score float64
}
type Teacher struct {
Person //匿名字段,只有类型,没有成员的名字
salary float64
}
type Person struct {
id int
name string
age int
}
func main() {
var stu Student = Student{Person{1, "张三", 18}, 98}
fmt.Println(stu) //{{1 张三 18} 98}
//部分初始化
var stu1 Student = Student{score: 90}
fmt.Println(stu1) //{{0 0} 90}
var stu2 Student = Student{Person: Person{id: 101}} //{{101 0} 0}
fmt.Println(stu2)
}
成员操作
package main
import "fmt"
type Student struct {
//属性---成员
//方法---函数
Person //匿名字段,只有类型,没有成员的名字
score float64
}
type Person struct {
id int
name string
age int
}
func main() {
var stu Student = Student{Person{101, "张三", 18}, 98}
var stu1 Student = Student{Person{102, "李四", 18}, 80}
//获取成员的值
fmt.Println(stu.score) //98
fmt.Println(stu1.score) //80
fmt.Println(stu1.Person.id) //102 //比较麻烦
fmt.Println(stu1.id) //102
//修改成员的值
stu.score = 100
fmt.Println(stu.score) //100
}
指针类型匿名字段
package main
import "fmt"
type Student struct {
//属性---成员
//方法---函数
*Person //指针类型匿名字段
score float64
}
type Person struct {
id int
name string
age int
}
func main() {
var stu Student = Student{&Person{101, "张三", 18}, 98}
fmt.Println(stu) //{0xc000054460 98} 父类输出的是结构体内存地址 只能使用成员操作了
fmt.Println(stu.name) //张三
}
多重继承—尽量不要写多重继承
package main
import "fmt"
// Student 学生继承person
type Student struct {
//属性---成员
//方法---函数
Person //指针类型匿名字段
score float64
}
// Person 父类 person继承object
type Person struct {
Object1
name string
age int
}
type Object1 struct {
id int
}
func main() {
var stu Student
stu.age = 18
fmt.Println(stu.Person.age) //18
stu.id = 101
fmt.Println(stu.Person.Object1.id)
}
为结构体添加方法—封装
func (对象 结构体类型) 方法名 (参数列表)(返回值列表) {
代码体
}
方法调用
对象名.方法
//不支持重载,只要接收者类型不一样,这个方法就算同名,也是不同方法,不会出现重复定义函数的错误
package main
import "fmt"
// Student
type Student struct {
id int
name string
age int
}
// PrintShow 方法 s为接收者
func (s Student) PrintShow() {
fmt.Println(s) //{101 ziye 18}
}
func (s Student) EditInfo() {
s.age = 20
}
func (s *Student) EditInfo1() {
s.age = 20
}
func main() {
stu := Student{101, "ziye", 18}
//对象名.方法名 把stu中的值传给了s
stu.PrintShow() //完成对方法的调用
stu.EditInfo() //不是引用传递,是值传递
stu.PrintShow()
//内部会进行转换
stu.EditInfo1() //引用传递
stu.PrintShow() //{101 ziye 20}
}
注意事项
只要接收者类型不一样,这个方法就算同名,也是不同方法
接收者为指针类型
package main
import "fmt"
// Student
type Student struct {
id int
name string
age int
}
type Teacher struct {
id int
name string
}
func (s *Student) show() {
fmt.Println(s)
}
func (t *Teacher) show() { //把teacher内存地址给到这里
fmt.Println(t)
}
func main() {
//如果接收者类型不同,即使方法的名字是相同的也是不同的方法
stu := Student{101, "ziye", 18}
stu.show() //等价于(&stu).show()
teacher := Teacher{102, "ziyeye"}
teacher.show()
}
面向对象方法练习
定义一个学生类,有六个属性,分别为姓名、性别、年龄、语文、数学、英语成绩
定义两个方法:
第一方法:打招呼的方法:介绍自己叫XX,今年几岁了。是男同学还是女同学。
第二个方法:计算总分与平均分的方法
package main
import "fmt"
// Student
type StudentInfo struct {
name string //姓名
sex string //性别
age int //年龄
chinese float64 //语文
math float64 //数学
english float64 //英语
}
// SayHello 打招呼
func (studentInfo *StudentInfo) SayHello(username string, userAge int, userSex string) {
//初始化
studentInfo.name = username
studentInfo.age = userAge
studentInfo.sex = userSex
//初始化后的值进行判断
if studentInfo.sex != "男" && studentInfo.sex != "女" {
studentInfo.sex = "男"
}
if studentInfo.age < 1 || studentInfo.age > 100 {
studentInfo.age = 18
}
//打印输出结果
fmt.Printf("我叫%s,年龄是%d,性别是%s\n", studentInfo.name, studentInfo.age, studentInfo.sex)
}
// GetScore 计算平均分
func (studentInfo *StudentInfo) GetScore(chinese float64, math float64, english float64) {
//初始化
studentInfo.chinese = chinese
studentInfo.math = math
studentInfo.english = english
//进行计算
sum := studentInfo.chinese + studentInfo.math + studentInfo.english
//打印输出结果
fmt.Printf("我叫%s,总分%f,平均分%.2f\n", studentInfo.name, sum, sum/3)
}
func main() {
var stu StudentInfo
stu.SayHello("ziye", 18, "女")
stu.GetScore(98, 97, 95)
}
方法继承
package main
import "fmt"
// Student
type Student struct {
Person
score float64
}
type Person struct {
id int
name string //姓名
age int //年龄
}
func (p *Person) PrintInfo() {
fmt.Println(*p) //{101 张三 18}
}
func main() {
stu := Student{Person{101, "张三", 18}, 90}
//子类可以调用父类的方法
stu.PrintInfo()
}
方法继承的练习
根据以下信息,实现对应的继承关系
记者:我叫张三 ,我的爱好是偷拍,我的年龄是34,我是一个男狗仔。
程序员:我叫孙全,我的年龄是23,我是男生,我的工作年限是 3年。
package main
import "fmt"
// Person 定义父类
type Person struct {
name string
age int
sex string
}
// SetValue 给父类添加方法
func (p *Person) SetValue(userName string, userAge int, userSex string) {
p.name = userName
p.age = userAge
p.sex = userSex
}
// Rep 定义相应的子类 记者类
type Rep struct {
Person
Hobby string //爱好
}
// Pro 程序员类
type Pro struct {
Person
WorkYear int
}
// RepSayHello 给子类添加相应的信息
func (r *Rep) RepSayHello(Hobby string) {
r.Hobby = Hobby
fmt.Printf("我叫%s ,我的爱好是%s,我的年龄是%d,我是一个%s狗仔\n", r.name, r.Hobby, r.age, r.sex)
}
// ProSayHello 给子类添加相应的信息
func (p *Pro) ProSayHello(workYear int) {
p.WorkYear = workYear
fmt.Printf("我叫%s,我的年龄是%d,我是%s,我的工作年限是 %d年\n", p.name, p.age, p.sex, p.WorkYear)
}
func main() {
var rep Rep
rep.SetValue("ziye", 34, "男")
rep.RepSayHello("偷拍")
var pro Pro
pro.SetValue("李四", 26, "男")
pro.ProSayHello(3)
}
方法重写
就是子类(结构体)中的方法,将父类中的相同名称的方法的功能重新给改写了
注意:在调用时,默认调用的是子类中的方法
package main
import "fmt"
// Person 定义父类
type Person struct {
name string
age int
}
func (p *Person) PrintInfo() {
fmt.Println("这是父类中的方法")
}
type Student struct {
Person
score float64
}
func (s *Student) PrintInfo() {
fmt.Println("这是子类中的方法")
}
func main() {
var stu Student
stu.PrintInfo() //这是子类中的方法 如果父类中的方法名称与子类中的方法一致,那么通过子类的对象调用的是子类中的方法,方法重写
stu.Person.PrintInfo() //这是父类中的方法 调用父类中的方法
}
方法值与方法表达式
package main
import "fmt"
// Person 定义父类
type Person struct {
name string
age int
}
func (p *Person) PrintInfo() {
fmt.Println(*p) //{ziye 18}
}
func main() {
per := Person{"ziye", 18}
per.PrintInfo()
//方法值
f := per.PrintInfo
fmt.Printf("%T\n", f) //func() 方法类型
f() //{ziye 18}
//方法表达式
f1 := (*Person).PrintInfo //并没有指定一个对象 类名要和方法接收者类型保存一致
f1(&per) //{ziye 18} 方法表达式
}
接口简介
接口就是一种规范与标准,只是规定了要做哪些事情。具体怎么做,接口是不管的。
接口把所有的具有共性的方法定义在一起,任何其他类型只要实现了这些方法就是实
现了这个接口。
接口定义
type 接口名字 interface {
方法声明
}
接口的声明
可以为结构体添加接口中的方法,完成接口中方法实现
package main
import "fmt"
// Personer 接口的声明
type Personer interface {
SayHello() //方法声明
}
type Student struct {
}
// SayHello 使用student完成SayHello
func (s *Student) SayHello() {
fmt.Println("老师好")
}
type Teacher struct {
}
func (t *Teacher) SayHello() {
fmt.Println("学生好")
}
func main() {
//对象名.方法名
var stu Student
stu.SayHello() //老师好
var teacher Teacher
teacher.SayHello() //学生好
//接口变量来调用,必须都实现接口中所声明的方法
var person Personer
person = &stu
person.SayHello() //调用的是Student、实现的SayHello方法 老师好
person = &teacher
person.SayHello() //学生好
}
多态的定义与实现
什么是多态
所谓多态:指的是多种表现形式。
多态就是同一个接口,使用不同的实例而执行不同操作
package main
import "fmt"
type Personer interface {
SayHello()
}
type Student struct {
}
func (s *Student) SayHello() {
fmt.Println("老师好")
}
type Teacher struct {
}
func (t *Teacher) SayHello() {
fmt.Println("学生好")
}
//实现多态
func WhoSayHi(personer Personer) {
personer.SayHello()
}
func main() {
var stu Student
var teacher Teacher
WhoSayHi(&stu)
WhoSayHi(&teacher)
}
案例
用多态来模拟实现 将移动硬盘或者U盘插到电脑上进行读写数据
package main
import "fmt"
type Stroager interface {
Read()
Writer()
}
// MDisk 移动硬盘
type MDisk struct {
}
func (m *MDisk) Read() {
fmt.Println("移动硬盘读取数据")
}
func (m *MDisk) Writer() {
fmt.Println("移动硬盘写入数据")
}
// UDisk U盘
type UDisk struct {
}
func (u *UDisk) Read() {
fmt.Println("U盘读取数据")
}
func (u *UDisk) Writer() {
fmt.Println("U盘写入数据")
}
// Computer 定义一个函数
func Computer(s Stroager) {
s.Writer()
s.Read()
}
func main() {
var uds UDisk
var mds MDisk
Computer(&uds)
Computer(&mds)
}
案例
使用面向对象方式,实现一个计算器程序
案例实现一
package main
import "fmt"
type Object1 struct {
}
func (o *Object1) GetResult(num1, num2 int, op string) int {
//添加参数
var result int
switch op {
case "+":
result = num1 + num2
case "-":
result = num1 - num2
}
return result
}
func main() {
var obj Object1
result := obj.GetResult(8, 6, "+")
fmt.Println(result)
}
案例实现二
package main
import "fmt"
// 加法类
type Add struct {
Object1
}
func (add *Add) GetResult() int { //方法的实现要和接口中方法的声明保持一致
return add.numA + add.numB
}
type Sub struct {
Object1
}
func (sub *Sub) GetResult() int {
return sub.numA - sub.numB
}
type Object1 struct {
numA int
numB int
}
type Resulter interface {
GetResult() int //返回类型int
}
func main() {
add := Add{Object1{10, 8}}
result := add.GetResult()
fmt.Println(result)
sub := Sub{Object1{10, 8}}
result = sub.GetResult()
fmt.Println(result)
}
案例实现三
package main
import "fmt"
// 加法类
type Add struct {
Object1
}
func (add *Add) GetResult() int { //方法的实现要和接口中方法的声明保持一致
return add.numA + add.numB
}
type Sub struct {
Object1
}
func (sub *Sub) GetResult() int {
return sub.numA - sub.numB
}
type Object1 struct {
numA int
numB int
}
type Resulter interface {
GetResult() int //返回类型int
}
// OperatorFactory 对象创建问题
// 1:定义一个新的类
type OperatorFactory struct {
}
// CreaeteOperator 创建一个方法,在该方法中完成对象的创建----封装
func (o *OperatorFactory) CreaeteOperator(op string, numA, numB int) int {
switch op {
case "+":
add := Add{Object1{numA, numB}}
return OperatorWho(&add)
case "-":
sub := Sub{Object1{numA, numB}}
return OperatorWho(&sub)
default:
return 0
}
}
// 多态
func OperatorWho(h Resulter) int {
result := h.GetResult()
return result
}
func main() {
var operator OperatorFactory
creaeteOperator := operator.CreaeteOperator("+", 20, 10)
fmt.Println(creaeteOperator)
}
接口的继承与转换
package main
import "fmt"
type Humaner interface {
SayHello()
}
// Personer 接口继承
type Personer interface {
Humaner
Say()
}
type Student struct {
}
func (s *Student) SayHello() {
fmt.Println("大家好")
}
func (s *Student) Say() {
fmt.Println("你好")
}
func main() {
var Stu Student
var per Personer
per = &Stu
per.Say()
per.SayHello() //可以调用所继承的接口中的方法
//接口转换
var h Humaner
h = per
h.SayHello()
//超集可以转换为子集,反过来不可以
//per = h
}
空接口定义与使用
var i interface{} //空接口
i = 123
fmt.Println(i)
空接口可以赋任意的类型,切片s可以赋值各种类型的
var s []interface{} //空接口切片s
s = append(s, 123, "abc", 23.12)
for i := 0; i < len(s); i++ {
fmt.Println(s[i])
}
空接口(interface{})不包含任何的方法,正因为如此,所有的类型都实现了空接口,因此空接口可以存储任意类型的数值
类型断言
通过类型断言,可以判断空接口中存储的数据类型。
语法:value, ok := m.(T)
m:表空接口类型变量
T:是断言的类型
value: 变量m中的值。
ok: 布尔类型变量,如果断言成功为true,否则为false
package main
import "fmt"
func main() {
var i interface{}
i = 123
value, ok := i.(int)
if ok {
fmt.Println(value)
} else {
fmt.Println("类型推断错误")
}
}
案例–空接口与类型断言综合应用
计算器,完成数据校验
package main
import "fmt"
// 加法类
type Add struct {
Object1
}
func (add *Add) GetResult() int { //方法的实现要和接口中方法的声明保持一致
return add.numA + add.numB
}
func (add *Add) SetData(data ...interface{}) bool {
//对数据的个数进行校验
var b bool = true
if len(data) > 2 || len(data) <= 1 {
fmt.Println("参数个数错误")
b = false
}
value, ok := data[0].(int)
if !ok {
fmt.Println("第一个数类型错误")
b = false
}
value1, ok1 := data[1].(int)
if !ok1 {
fmt.Println("第二个数类型错误")
b = false
}
add.numA = value
add.numB = value1
//对传递过来的类型进行校验
return b
}
type Sub struct {
Object1
}
func (sub *Sub) GetResult() int {
return sub.numA - sub.numB
}
func (sub *Sub) SetData(data ...interface{}) bool {
//对数据的个数进行校验
var b bool = true
if len(data) > 2 || len(data) <= 1 {
fmt.Println("参数个数错误")
b = false
}
value, ok := data[0].(int)
if !ok {
fmt.Println("第一个数类型错误")
b = false
}
value1, ok1 := data[1].(int)
if !ok1 {
fmt.Println("第二个数类型错误")
b = false
}
sub.numA = value
sub.numB = value1
//对传递过来的类型进行校验
return b
}
type Object1 struct {
numA int
numB int
}
type Resulter interface {
GetResult() int //返回类型int
SetData(data ...interface{}) bool //完成参数运算的数据的类型校验
}
// OperatorFactory 对象创建问题
// 1:定义一个新的类
type OperatorFactory struct {
}
// CreaeteOperator 创建一个方法,在该方法中完成对象的创建----封装
func (o *OperatorFactory) CreaeteOperator(op string) Resulter {
switch op {
case "+":
add := new(Add) //返回*add
return add
case "-":
sub := new(Sub) //创建Sub 开辟相应的存储空间
return sub
default:
return nil
}
}
// 多态
func OperatorWho(h Resulter) int {
result := h.GetResult()
return result
}
func main() {
var operator OperatorFactory
creaeteOperator := operator.CreaeteOperator("-") //拿到对象
setData := creaeteOperator.SetData(30, 10)
if setData {
who := OperatorWho(creaeteOperator)
fmt.Println(who)
}
}