[Golang]面向对象营养餐,一文管够(封装,继承,多态)
Posted @书生
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[Golang]面向对象营养餐,一文管够(封装,继承,多态)相关的知识,希望对你有一定的参考价值。
Go中也存在着面向对象编程,不过与传统的OOP(面向对象编程)语言相比,它舍弃了使用类,采用更加灵活的结构体。
实现继承也采用使用匿名字段的方式,摒弃了传统意义的继承,并且通过接口可以实现多态。
在Go中,也摒弃了虚方法,构造函数,析构函数的概念。
注: Go中面向对象的三大特性,使用结构体实现封装,采用匿名字段实现继承,通过接口实现多态
封装
结构体:
结构体相当于一个存放相同或不同数据类型变量的集合,结构体的变量相当于我们封装时事物的属性,我们称之为字段;例如:我们用结构体来描述一个人的特征:姓名,年龄,性别,身份证号
type Person struct{
name string
age int
sex string
id string
}
注:
Person就是上面结构体的名称,相当于该结构体的类型,首字母大写,可在其他包访问,小写尽在本包访问,注意在本包中,结构体名称需要保证唯一;
结构体内的字段名称(eg:name,age,sex,id)在本结构体内必须是唯一的;
相同数据类型的字段可以写在同一行
eg:
type Person struct{
name,sex,id string
age int
}
结构体的定义
type 结构体名称 struct {
//字段名称 数据类型
}
注:
结构体在定义后,仅仅是完成了对内存布局的一种描述,并不占用内存空间,只有实例化后才会占用内存空间,也就是说只有实例化后,我们才能访问对应的结构体变量
实例化结构体
注:
当我们实例化出结构体变量的时候,即使不给对应的字段进行赋值,各个字段也拥有该数据类型的默认值
- 先声明变量,再给各个字段依次赋值
var p person //先声明结构体person的变量p
fmt.Printf("%+v\\n",p)
//倘若指声明了变量,不进行赋值 {name: sex: id: age:0}
//name sex id的数据类型为string 默认值为"" 打印的时候显示不出来
//给各个字段进行赋值
p.name = "Andy"
p.sex = "man"
p.id = "123456789"
p.age = 18
fmt.Printf("%+v\\n",p)
//{name:Andy sex:man id:123456789 age:18}
- 声明变量的时候,直接赋值
var p1 person = person{"Andy","man","123456789",18}
//初始化字段的值必须和结构体声明的字段顺序一致,否则初始化的结构体可能和你预想的不同
//你也可以使用下面的这种形式,字段:字段的值
var p2 person = person{name:"Andy",sex:"man",id:"123456789",age:18}
总体来说,实例化结构体仅仅有上面的两种形式,下面的两种形式其实和上方有些类似
3. 采用内置函数new得到一个结构体指针,使用结构体指针,初始化结构体的字段
var p3 *person = new(person)
//实际中采用指针的方式访问字段需要先解引用:(*p3).name="Andy"
//但是我们可以省略解引用的步骤,想使用变量一样使用指针,
//这是因为Go的编译器做了优化,会替我们自动转换为正确的形式
p3.name = "Andy"
p3.sex = "man"
p3.id = "1234567"
p3.age = 17
var p4 *person = &person{name: "Andy", sex: "man",
id: "123456789", age: 18}
内存分布
结构体的所有字段是连续的
type person struct {
name string
age int
sex string
}
func main() {
var p person = person{"shusheng", 17, "man"}
fmt.Println(unsafe.Sizeof(p.name))
fmt.Println(unsafe.Sizeof(p.age))
fmt.Println(unsafe.Sizeof(p.sex))
fmt.Printf("%p ",&p)
fmt.Println(&(p.name), &(p.age), &(p.sex))
}
16
8
16
0xc42005c150 0xc42005c150 0xc42005c160 0xc42005c168
匿名结构体与匿名字段
匿名结构体
匿名结构体就是没有名字的结构体,无须通过type关键字定义就可以直接使用。创建匿名结构体时,同时要创建对象。匿名结构体由结构体定义和键值对初始化两部分组成
p := struct {
name, sex string
age int
}{"Andy", "man", 18}
//定义匿名结构体的形式
结构体变量名 := struct{
结构体字段
}{字段对应的初始值}
匿名字段
所谓的匿名字段就是在结构体中没有字段名称,仅有数据类型,并且该数据类型的匿名字段仅有一个,匿名字段其实会默认使用类型作为字段名
type person struct{
name string
string
}
var p1 person = person{"ee","S"}
fmt.Printf("%+v\\n", p1)
var p2 person
p2.name = "shusheng"
p2.string = "golang"
fmt.Printf("%+v\\n", p2)
//{name:ee string:S}
//{name:shusheng string:golang}
方法
方法其本质就是我们所说的函数,只不过在Go中,方法绑定了具体的类型,只有特定类型的对象才能够调用这个方法
通过上面,我们就可以直到方法和函数就有一定的区别的
1.含义,作用:
函数是用于实现特定功能的代码集合
方法是一类事物实现特定行为的代码集合
2.调用
函数调用的时候没有指定的参与者
方法必须通过其绑定类型的对象进行调用
3.函数名一定不能重复,方法名可以重复,需要绑定不同的类型
方法定义的基础语法
func (绑定变量名 绑定变量数据类型) 方法名 (形参列表) (返回值列表){
方法体
}
eg:
type person struct {
name string
string
}
//为结构体person绑定一个方法Run
func (p person) Run() {
fmt.Println(p.name, "正在跑步")
}
//使用
var p2 person
p2.name = "shusheng"
p2.string = "golang"
fmt.Printf("%+v\\n", p2)
p2.Run()
注:
我们不仅可以为结构体绑定方法,也可以为非结构体绑定方法,既可以为任何类型(除了非本地类型(其他包的类型),包括内建类型:eg:int 类型不能有方法)绑定方法,但是可以重新type,绑定方法(eg:type INT int可以为INT类型绑定方法)
绑定变量可以是个指针类型
func (p *person) Run() {
fmt.Println(p.name, "正在跑步")
}
继承
我们仅需要在结构体中包含父类的匿名结构体
type person struct {
name string
sex string
}
type student struct{
person //包含匿名结构体,即继承了person
id int
}
//
var s student = student{person{"ss","man"},7 }
fmt.Printf("%+v\\n", s)
//{person:{name:ss sex:man} id:7}
当我们在结构体中包含了匿名结构体,匿名结构体实现的字段和方法都会被继承下来,我们可以调用继承下来的方法
type person struct {
name string
sex string
}
type student struct{
person
id int
}
func (p person) Run() {
fmt.Println(p.name, "正在跑步")
}
func main() {
var s student = student{person{"ss","man"},7 }
fmt.Printf("%+v\\n", s)
s.Run() //调用方法Run
}
//ss 正在跑步
方法重写:
方法重写是指一个包含了匿名字段的struct也实现了该匿名字段实现的方法,我们再次调用这个方法时,会采用“就近原则”,调用我们这个结构体的方法
type person struct {
name string
sex string
}
type student struct{
person
id int
}
func (p person) Run() {
fmt.Println(p.name, "正在跑步")
}
//结构体student也实现了方法Run
func (s student) Run() {
fmt.Println(s.name, "正在跑步.....")
}
func main() {
var s student = student{person{"ss","man"},7 }
fmt.Printf("%+v\\n", s)
s.Run() //调用方法Run
}
结果:
{person:{name:ss sex:man} id:7}
ss 正在跑步.....
多态:
多态是指代码可以根据类型的具体实现采取不同行为的能力
接口:
面向对象语言中,接口用于定义对象的行为。接口只指定对象应该做什么,实现这种行为的方式(实现细节)由对象来决定。
在Go语言中 ,接口定义了一组方法 ,如果某个对象实现了该接口的所有方法,则此对象就实现了该接口,Go语言的类型都是隐式实现接口的。任何定义了接口中所有方法的类型都被称为隐式地实现了该接口
接口的定义:
type 接口名 interface{
该接口实现的方法
}
接口是用来定义行为的类型。这些被定义的行为不由接口直接实现,而是通过方法由用户定义的类型实现。如果用户定义的类型实现了某个接口类型声明的一组方法,那么这个用户定义的类型的值就可以赋给这个接口类型的值,这个赋值会把用户定义的类型的值存入接口类型的值。
//定义了一个接口phone,该接口定义了一个方法call
type phone interface {
call()
}
//定义了两个结构体类型Andorid和Iphone都实现了方法call
type Andorid struct {
brand string
}
type Iphone struct {
brand string
}
func (a Andorid) call() {
fmt.Println("Andorid", a.brand, "is calling...")
}
func (i Iphone) call() {
fmt.Println("Iphone", i.brand, "is calling...")
}
//定义了一个函数calling 调用时需要传入参数 phone接口类型的变量
func calling(p phone) {
p.call()
}
func main() {
//我们将实现了phone接口中所有方法的结构体类型Andorid和Iphone
//的变量赋值给phone接口类型变量,编译能够通过
var a Andorid = Andorid{"OPPO"}
calling(a)
var i Iphone = Iphone{"iphone4"}
calling(i)
}
/*
结果:
Andorid OPPO is calling...
Iphone iphone4 is calling...
多态是指代码可以根据类型的具体实现采取不同行为的能力
根据上面的定义,我们发现通过接口就能够实现多态,
大家可以好好体会一下上面的代码
*/
注:
接口变量可以接收一个任何实现了该接口方法的变量,那么一种类型就可以同时实现多个接口的方法
空接口
正式由于接口具有 接口变量可以接收一个任何实现了该接口方法的变量 的特性,所以空接口(不包含任何方法的接口)可以接收任何类型的变量。
类型断言
我们在上面讨论了,可以使用接口来实现多态,即使用接口变量来调用接口内定义的方法,但是如果我们想要使用接口变量来访问brand的话,必定编译出现错误(p.brand ,当然在实现多态的时候,不会这么用),这就需要我们使用类型断言。
type phone interface {
call()
}
//定义了两个结构体类型Andorid和Iphone都实现了方法call
type Andorid struct {
brand string
}
type Iphone struct {
brand string
}
func (a Andorid) call() {
fmt.Println("Andorid", a.brand, "is calling...")
}
func (i Iphone) call() {
fmt.Println("Iphone", i.brand, "is calling...")
}
//定义了一个函数calling 调用时需要传入参数 phone接口类型的变量
func calling(p phone) {
p.call()
}
类型断言主要用于知道接口存放的变量的数据类型
- Comma-ok 断言
value,flag := 接口变量.(实际数据类型)
实际数据类型相符的话,flag为true,value是实际类型的变量
实际数据类型不服的话,flag为false
也就是说value和flag都是变量,
value存放实际数据类型的值,flag是一个bool类型变量
func calling(p phone) {
if value, flag := p.(Iphone); flag {
fmt.Println(value.brand)
}
p.call()
}
- switch-type
var x interface{}
switch i:=x.(type){
case int:
//int类型元素
case float32:
//float32类型元素
default:
//未知类型元素
}
原文链接:
https://blog.csdn.net/weixin_43519514/article/details/117092328
进阶:接口的实现
参考《Go语言实战》
接口值是一个两个字长度的数据结构,第一个字包含一个指向内部表的指针。这个内部表叫作 iTable,包含了所存储的值的类型信息。iTable 包含了已存储的值的类型信息以及与这个值相关联的一组方法。第二个字是一个指向所存储值的指针。将类型信息和指针组合在一起,就将这两个值组成了一种特殊的关系。
type phone interface {
call()
}
//定义了一个结构体类型Andorid实现了方法call
type Andorid struct {
brand string
}
//定义了一个函数calling 调用时需要传入参数 phone接口类型的变量
func calling(p phone) {
p.call()
}
func main() {
//我们将实现了phone接口中所有方法的结构体类型Andorid和Iphone
//的变量赋值给phone接口类型变量,编译能够通过
var a Andorid = Andorid{"OPPO"}
calling(a)
}
注:
如果要实现接口,接收者的类型一定是值类型,不能为指针,编译器会报错
./XXX.go:28:11: cannot use p (type person) as
type RUN in argument to running:
person does not implement RUN
(run method has pointer receiver)
注:如果本篇博客有任何错误和建议,欢迎伙伴们留言,你快说句话啊!
以上是关于[Golang]面向对象营养餐,一文管够(封装,继承,多态)的主要内容,如果未能解决你的问题,请参考以下文章