一、结构体指针
* 由于结构体是值类型,在方法传递时希望传递结构体地址,可以使用时结构体指针完成
* 可以结合new(T)函数创建结构体指针
peo := new(People)
//因为结构体本质是值类型,所以创建结构体指针时已经开辟了内存空间
fmt.Println(peo == nil) //输出:false
//由于结构体中属性并不是指针类型,所以可以直接调用
peo.Name = "smallming"
fmt.Println(peo)//输出:&{smallming 0}
peo1:=peo
peo1.Name="佳明哥"
fmt.Println(peo1,peo)//输出:&{佳明哥 0} &{佳明哥 0}
* 如果不想使用new(T)函数,可以直接声明结构体指针并赋值
//声明结构体指针
var peo *People
//给结构体指针赋值
peo = &People{"smallming", 17}
/*
上面代码使用短变量方式如下
peo:= &People{"smallming", 17}
*/
fmt.Println(peo)
* 结构体指针比较的是地址
* (*结构体指针)取出地址中对应的值
二、方法
* 方法和函数语法比较像,区别是函数属于包,通过包调用函数,而方法属于结构体,通过结构体变量调用
* 默认是函数,隶属于包,所以需要添加标识。告诉编译器这个方法属于哪个结构体
* 调用方法时就把调用者赋值给接收者(下面的变量名就是接受者)
func (变量名 结构体类型) 方法名(参数列表) 返回值列表{
//方法体
}
* Go语言中已经有函数了,又添加了对方法的支持主要是保证Go语言是面向对象的。
*Go语言官方对面向对象的解释如下:虽然面向对象没有统一的定义,但是对于我们来说对象仅仅是一个有着方法的值或变量,而方法就是一个属于特定类型的函数
* 从上面的解释可以看出,官方给出明确说明,方法类似于函数.方法归属于特定类型
* 定义一个People类型结构体,再对People结构体定义个run()方法
type People struct {
Name string//姓名
Weight float64//体重.单位斤
}
func (p People) run(){
fmt.Println(p.Name,"正在跑步")
}
func main() {
peo:=People{"张三",17}
peo.run()
}
* 如果设定需求,在每次跑步后体重都减少0.1斤.上面代码就需要修改了.因为结构体是值类型,修改方法中结构体变量p的值,主函数中peo的值不会改变,因为传递的是值副本.所以修改方法中结构体类型为结构体指针类型就可以完成设定需求
type People struct {
Name string//姓名
Weight float64//体重.单位斤
}
func (p *People) run(){
fmt.Println(p.Name,"正在跑步,体重为:",p.Weight) //输出:张三 正在跑步,体重为: 17
p.Weight-=0.1
}
func main() {
peo:=&People{"张三",17}
peo.run()
fmt.Println(peo.Name,"跑完步后的体重是",peo.Weight)//输出:张三 跑完步后的体重是 16.9
}
三、面向对象和面向过程
* 面向过程编程代码只适用于当前情况,而面向对象编程更注重重用,同一套代码可以使用多样的情况
* 面向过程编程(OPP)就是详细的按照顺序的把整个过程实现
* 例如:学生从家里去上学需要出门-->找到OFO-->扫描开锁-->上车-->直行-->左拐-->找到”粥饼面”-->停车-->上锁-->开门--> 找到座位-->点山东煎饼和四川火锅-->上菜-->吃饭-->现金付钱-->出门-->找到OFO .....
* 面向对象编程(OOP)一切皆对象,对象有自己的行为和特征,程序编写过程中把数据和业务进行封装,具有相同类型内容进行继承,同样代码实现多样效果等,实现高可用,高重用的程序.传统面向对象三大基本特征:封装,继承,多态
* 例如:出门后步行或公交或开车或任意品牌共享单车只要能到吃饭的地方就行,吃饭的店任意能填饱肚子就可以,吃饭时什么菜都可以选不是只能山东煎饼和四川火锅,吃完饭付钱时除了现金支付还可以微信支付,支付宝,刷卡等.吃完饭只要能回家就行.
四、Go语言中的面向对象
* 面向对象是一种思想,到目前为止还没有一个非常明确的定义,老程序员在不同时期对面向对象的理解是不同的.Go语言中对面向对象有着自己的理解
* Go语言开发者认为:面向对象就是特定类型(结构体)有着自己的方法,利用这个方法完成面向对象编程,并没有提封装、继承、多态。所有Go语言进行面向对象编程时,重点在于灵活使用方法,Go语言通过这样的设计降低了语言学习的压力.
* Go语言有着自己对面向对象的理解,他也有着自己的封装、继承、多态
五、封装
* 封装主要体现在两个方面:封装数据、封装业务
* Go语言中通过首字母大小控制访问权限。属性首字母小写对外提供访问方法是封装数据最常见的实现方式
* 可以通过方法封装业务
* 提出方法是封装
* 控制结构体属性访问,对外提供访问方法也是封装
* 在面向对象中封装的好处:
* 安全性.结构体属性访问受到限制,必须按照特定访问渠道
* 可复用性,封装的方法实现可复用性
* 可读写,多段增加代码可读性
六、继承
* 按照传统面向对象思想,继承就是把同一类事物提出共同点为父类,让子类可以复用父类的可访问性内容.
* 继承有多种实现方式
* 通过关键字继承,强耦合实现方式
* 组合式继承,松耦合继承方式
* 使用过Java或C#的应该知道尽量少用继承而是使用组合代替继承,可以使用高内聚,低耦合.Java之父之前在一次采访的时候也说过,如果给他一次机会重新做Java,他最希望修改的地方就是继承
* Go语言中的继承是通过组合实现
七、匿名属性
* 在Go语言中支持匿名属性(结构体中属性名字),但是每个结构体中最多只能存在一种匿名属性。编译器认为类型就是属性名,我们在使用时就把类型当作属性名进行使用
type People struct {
string
int
}
func main() {
p:=People{"smallming",17}
fmt.Println(p.string,p.int)
}
八、结构体之间的关系
* 传统面向对象中类与类之间的关系
* 继承:is-a,强耦合性,一般认为类与类之间具有强关系
* 实现:like-a,接口和实现类之间的关系
* 依赖:use-a,具有偶然性的、临时性的、非常弱的,但是B类的变化会影响到A,一般作为方法参数
* 关联:has-a一种强依赖关系,比如我和我的朋友;这种关系比依赖更强、不存在依赖关系的偶然性、关系也不是临时性的,一般是长期性的,而且双方的关系一般是平等的、关联可以是单向、双向的
* 聚合:has-a,整体与部分、拥有的关系
* 组合:contains-a,他体现的是一种contains-a的关系,这种关系比聚合更强,也称为强聚合;他同样体现整体与部分间的关系,但此时整体与部分是不可分的,整体的生命周期结束也就意味着部分的生命周期结束
* 组合>聚合>关联>依赖
九、使用匿名属性完成Go语言中的继承
* Go语言中的继承很好实现,把另一个结构体类型当作另一个结构体的属性,可以直接调用另一个结构体中的内容
* 因为Go语言中结构体不能相互转换,所以不能把子结构体变量赋值给父结构体变量
type People struct {
name string
age int
}
type Teacher struct {
People
classroom string //班级
}
func main() {
teacher := Teacher{People{"smallming", 17}, "302教室"}
fmt.Println(teacher.classroom, teacher.age, teacher.name)
}
十. 接口
* 接口解释:接口是一组行为规范的定义.
* 接口中只能有方法声明,方法只能有名称、参数、返回值,不能有方法体
* 每个接口中可以有多个方法声明,结构体把接口中所有方法都重写后,结构体就属于接口类型
* Go语言中接口和结构体之间的关系是传统面向对象中is-like-a的关系
* 定义接口类型关键字是interface
type 接口名 interface{
方法名(参数列表) 返回值列表
}
* 接口可以继承接口,且Go语言推荐把接口中方法拆分成多个接口
* 接口中声明完方法,结构体重写接口中方法后,编译器认为结构体实现了接口
* 重写的方法要求必须和接口中方法名称、方法参数(参数名称可以不同)、返回值列表完全相同
* 如果接口中有多个方法声明,接口体必须重写接口中全部方法才在任务结构体实现了接口
十一、多态
* 多态:同一件事情由于条件不同产生的结果不同
* 由于Go语言中结构体不能相互转换,所以没有结构体(父子结构体)的多态,只有基于接口的多态.这也符合Go语言对面向对象的诠释
* 多态在代码层面最常见的一种方式是接口当作方法参数
* 结构体实现了接口的全部方法,就认为结构体属于接口类型,这时可以把结构体变量赋值给接口变量
* 重写接口时接收者为Type和*Type的区别
* *Type可以调用*Type和Type作为接收者的方法.所以只要接口中多个方法中至少出现一个使用*Type作为接收者进行重写的方法,就必须把结构体指针赋值给接口变量,否则编译报错
* Type只能调用Type作为接收者的方法
type Live interface {
run()
eat()
}
type People struct {
name string
}
func (p *People) run() {
fmt.Println(p.name, "正在跑步")
}
func (p People) eat() {
fmt.Println(p.name, "在吃饭")
}
func main() {
//重写接口时
var run Live = &People{"张三"}
run.run()
run.eat()
}
* 既然接口可以接收实现接口所有方法的结构体变量,接口也就可以作为方法(函数)参数
type Live interface {
run()
}
type People struct{}
type Animate struct{}
func (p *People) run() {
fmt.Println("人在跑")
}
func (a *Animate) run() {
fmt.Println("动物在跑")
}
func sport(live Live) {
fmt.Println(live.run)
}
func main() {
peo := &People{}
peo.run() //输出:人在跑
ani := &Animate{}
ani.run() //输出:动物在跑
}