面向对象

如果我们自己来修电脑,应该有哪些步骤呢?

第一步:判断问题的原因

第二步:找工具

第三步:暴力拆卸

这个修理的步骤就是面向过程,所谓的面向过程就是:强调的是步骤、过程、每一步都是自己亲自去实现的。

如果采用面向对象的思想,那么应该怎样修电脑呢?

找维修店的工作人员来帮我们修电脑,但是到底怎么修,我们是不用考虑的,也就是说我们不关心步骤与过程。

大家可以想一下,在生活中还有哪些事情是面向过程,面向对象的。

比如说,做饭,面向过程就是自己做,自己买菜,自己洗,自己炒,整个过程都有自己来完成,但是如果是面向对象,可以叫外卖,不用关心饭是怎么做的。

所以通过以上案例,大家能够体会出,面向过程就是强调的步骤,过程,而面向对象强调的是对象,找个人来做。

在面向对象中,还有两个概念是比较重要的,一是对象,二是类。

什么是对象呢?

万物皆对象,例如小明同学是一个对象,小亮同学也是一个对象

那么我们在生活中怎样描述一个对象呢?

比如,描述一下小明同学:

姓名:小明

性别:男

身高:180cm

体重:70kg

年龄:22岁

吃喝拉撒睡一切正常健康

吃喝嫖赌抽

通过以上的描述,可以总结出在生活中描述对象,可以通过特征(身高,体重,年龄等)和行为(爱好等)来进行描述。

那么在程序中,可以通过属性和方法(函数)来描述对象。属性就是特征,方法(函数)就是行为。所以说,对象必须具有属性和方法。虽然说,万物皆对象,但是在描述一个对象的时候,一定要具体不能泛指,例如,不能说“电灯”是一个对象,而是说具体的哪一台“电灯”。

大家可以思考一下,如果我们现在描述一下教室中某一台电灯,应该有哪些属性(特征)和方法(行为)呢?

下面我们在思考一下,下面这道题:

小明(一个学生)\杨老师\邻居王叔叔\小亮的爸爸\小亮的妈妈

找出这道题中所有对象的共性(所谓共性,指的是相同的属性和方法)。

所以说,我们可以将这些具有相同属性和相同方法的对象进行进一步的封装,抽象出来类这个概念。

类就是个模子,确定了对象应该具有的属性和方法。

对象是根据类创建出来的

例如:上面的案例中,我们可以抽出一个“人”类(都有年龄,性别,姓名等属性,都有吃饭,走路等行为),“小明”这个对象就是根据“人”类创建出来的,也就是说先有类后有对象。

GO语言中的面向对象

前面我们了解了一下,什么是面向对象,以及类和对象的概念。但是,GO语言中的面向对象在某些概念上和其它的编程语言还是有差别的。

严格意义上说,GO语言中没有类(class)的概念,但是我们可以将结构体比作为类,因为在结构体中可以添加属性(成员),方法(函数)。

面向对象编程的好处比较多,我们先来说一下“继承”,

所谓继承指的是,我们可能会在一些类(结构体)中,写一些重复的成员,我们可以将这些重复的成员,单独的封装到一个类(结构体)中,作为这些类的父类(结构体),我们可以通过如下图来理解:

当然严格意义上,GO语言中是没有继承的,但是我们可以通过”匿名组合”来实现继承的效果。

匿名组合

匿名字段

一般情况下,定义结构体的时候是字段名与其类型一一对应,实际上Go支持只提供类型,而不写字段名的方式,也就是匿名字段,也称为嵌入字段。

当匿名字段也是一个结构体的时候,那么这个结构体所拥有的全部字段都被隐式地引入了当前定义的这个结构体。

//人
type Person struct {name stringsex byteage int
}
//学生
type Student struct {Person    //匿名字段,那么默认Student就包含了Person的所有字段id intaddr string
}

初始化

//人
type Person struct {name stringsex byteage int
}
//学生
type Student struct {Person//匿名字段,那么默认Student就包含了Person的所有字段id intaddr string
}
func main() {//顺序初始化s1 := Student{Person{"mike",'m',18},1,"sz"}//s1 = {Person:{name:mike sex:109 age:18}id:1 addr:sz}fmt.Printf("s1=%+v\n",s1)//s2 := Student{"mike",'m',18,1,"sz"}//err//部分成员初始化1s3 := Student{Person:Person{"lily",'f',19},id:2}//s3 = {Person:{name:lily sex:102 age:19}id:2 addr:}fmt.Printf("s3=%+v\n",s3)//部分成员初始化2s4 := Student{Person:Person{name:"tom"},id:3}//s4 = {Person:{name:tomsex:0age:0}id:3addr:}fmt.Printf("s4=%+v\n",s4)
}

成员的操作

var s1 Student//变量声明
//给成员赋值
s1.name = "mike"//等价于s1.Person.name="mike"
s1.sex = 'm'
s1.age = 18
s1.id = 1
s1.addr = "sz"
fmt.Println(s1)    //{{mike 109 18}1 sz}
var s2 Student//变量声明
s2.Person = Person{"lily",'f',19}
s2.id = 2
s2.addr = "bj"
fmt.Println(s2)    //{{lily 102 19}2 bj}

同名字段

//人
type Person struct{name stringsex byteage int
}
//学生
type Student struct{Person    //匿名字段,那么默认Student就包含了Person的所有字段id intaddr stringname string    //和Person中的name同名
}
func main(){var s Student//变量声明//给Student的name,还是给Person赋值?s.name = "mike"//{Person:{name:sex:0age:0}id:0addr:name:mike}fmt.Printf("%+v\n",s)//默认只会给最外层的成员赋值//给匿名同名成员赋值,需要显示调用s.Person.name = "yoyo"//Person:{name:yoyosex:0age:0}id:0addr:name:mike}fmt.Printf("%+v\n",s)
}

其它匿名字段

非结构体类型

所有的内置类型和自定义类型都是可以作为匿名字段的:

type mystr string//自定义类型
type Person struct {name stringsex byteage int
}
type Student struct {Person    //匿名字段,结构体类型int    //匿名字段,内置类型mystr    //匿名字段,自定义类型
}
func main() {//初始化s1 := Student{Person{"mike",'m',18},1,"bj"}//{Person:{name:mikesex:109age:18}int:1mystr:bj}fmt.Printf("%+v\n",s1)//成员的操作,打印结果:mike,m,18,1,bjfmt.Printf("%s,%c,%d,%d,%s\n",s1.name,s1.sex,s1.age,s1.int,s1.mystr)
}

结构体指针类型

type Person struct {    //人name stringsex byteage int
}
type Student struct {//学生*Person    //匿名字段,结构体指针类型id intaddr string
}
func main() {//初始化s1 := Student{&Person{"mike",'m',18},1,"bj"}//{Person:0xc0420023e0id:1addr:bj}fmt.Printf("%+v\n",s1)//mike,m,18fmt.Printf("%s,%c,%d\n",s1.name,s1.sex,s1.age)//声明变量var s2 Students2.Person = new(Person)//分配空间s2.name = "yoyo"s2.sex = 'f's2.age = 20s2.id = 2s2.addr = "sz"//yoyo10220220fmt.Println(s2.name,s2.sex,s2.age,s2.id,s2.age)
}

方法

概述

在面向对象编程中,一个对象其实也就是一个简单的值或者一个变量,在这个对象中会包含一些函数,这种带有接收者的函数,我们称为方法(method)。本质上,一个方法则是一个和特殊类型关联的函数。

一个面向对象的程序会用方法来表达其属性和对应的操作,这样使用这个对象的用户就不需要直接去操作对象,而是借助方法来做这些事情。

在Go语言中,可以给任意自定义类型(包括内置类型,但不包括指针类型)添加相应的方法。

⽅法总是绑定对象实例,并隐式将实例作为第⼀实参 (receiver),方法的语法如下:

func (receiver ReceiverType) funcName (parameters) (results)
  • 参数 receiver 可任意命名。如⽅法中未曾使⽤,可省略参数名。
  • 参数 receiver 类型可以是 T 或 *T。基类型 T 不能是接⼝或指针。
  • 不支持重载方法,也就是说,不能定义名字相同但是不同参数的方法。

为类型添加方法

基础类型作为接收者

type MyInt int//自定义类型,给int改名为MyInt//在函数定义时,在其名字之前放上一个变量,即是一个方法
func (a MyInt) Add(b MyInt) MyInt {//面向对象return a + b
}//传统方式的定义
func Add(a, b MyInt) MyInt {//面向过程return a + b
}func main() {var a MyInt=1var b MyInt=1//调用func (aMyInt) Add(bMyInt)
fmt.Println("a.Add(b)=",a.Add(b))//a.Add(b)=2//调用func Add(a,bMyInt)
fmt.Println("Add(a,b)=",Add(a,b))//Add(a,b)=2
}

通过上面的例子可以看出,面向对象只是换了一种语法形式来表达。方法是函数的语法糖,因为receiver其实就是方法所接收的第1个参数。

注意:虽然方法的名字一模一样,但是如果接收者不一样,那么方法就不一样。

结构体作为接收者

方法里面可以访问接收者的字段,调用方法通过点(. )访问,就像struct里面访问字段一样:

type Person struct {name stringsex byteage int
}func (p Person) PrintInfo(){//给Person添加方法fmt.Println(p.name,p.sex,p.age)
}func main() {p:=Person{"mike",'m',18}//初始化p.PrintInfo()//调用func(pPerson)PrintInfo()
}

值语义和引用语义

type Person struct {name stringsex byteage int
}//指针作为接收者,引用语义
func (p *Person) SetInfoPointer(){//给成员赋值(*p).name = "yoyo"p.sex = 'f'p.age = 22
}//值作为接收者,值语义
func (p Person) SetInfoValue(){//给成员赋值p.name = "yoyo"p.sex = 'f'p.age = 22
}func main() {//指针作为接收者,引用语义p1 := Person{"mike",'m',18}//初始化fmt.Println("函数调用前=",p1)//函数调用前={mike10918}(&p1).SetInfoPointer()fmt.Println("函数调用后=",p1)//函数调用后={yoyo10222}fmt.Println("==========================")p2 := Person{"mike",'m',18}//初始化//值作为接收者,值语义fmt.Println("函数调用前=",p2)//函数调用前={mike10918}p2.SetInfoValue()fmt.Println("函数调用后=",p2)//函数调用后={mike10918}
}

方法集

类型的方法集是指可以被该类型的值调用的所有方法的集合。

用实例实例 value 和 pointer 调用方法(含匿名字段)不受⽅法集约束,编译器编总是查找全部方法,并自动转换 receiver 实参。

类型 *T 方法集

一个指向自定义类型的值的指针,它的方法集由该类型定义的所有方法组成,无论这些方法接受的是一个值还是一个指针。

如果在指针上调用一个接受值的方法,Go语言会聪明地将该指针解引用,并将指针所指的底层值作为方法的接收者。

类型 *T ⽅法集包含全部 receiver T + *T ⽅法:

type Person struct{name stringsex byteage int
}//指针作为接收者,引用语义
func (p *Person) SetInfoPointer(){(*p).name="yoyo"p.sex='f'p.age=22
}//值作为接收者,值语义
func (p Person) SetInfoValue(){p.name="xxx"p.sex='m'p.age=33
}func main() {//p为指针类型var p*Person = &Person{"mike",'m',18}p.SetInfoPointer()    //func (p)SetInfoPointer()p.SetInfoValue()    //func (*p)SetInfoValue()(*p).SetInfoValue()    //func (*p)SetInfoValue()
}

类型 T 方法集

一个自定义类型值的方法集则由为该类型定义的接收者类型为值类型的方法组成,但是不包含那些接收者类型为指针的方法。

但这种限制通常并不像这里所说的那样,因为如果我们只有一个值,仍然可以调用一个接收者为指针类型的方法,这可以借助于Go语言传值的地址能力实现。

type Person struct{name stringsex byteage int
}//指针作为接收者,引用语义
func (p *Person) SetInfoPointer(){(*p).name="yoyo"p.sex='f'p.age=22
}//值作为接收者,值语义
func (p Person)SetInfoValue(){p.name="xxx"p.sex='m'p.age=33
}func main() {//p为普通值类型var p Person = Person{"mike",'m',18}(&p).SetInfoPointer()    //func(&p)SetInfoPointer()p.SetInfoPointer()    //func(&p)SetInfoPointer()p.SetInfoValue()    //func(p)SetInfoValue()(&p).SetInfoValue()    //func(*&p)SetInfoValue()
}

匿名字段

方法的继承

如果匿名字段实现了一个方法,那么包含这个匿名字段的struct也能调用该方法。

type Person struct {name stringsex byteage int
}//Person定义了方法
func (p *Person) PrintInfo() {fmt.Printf("%s,%c,%d\n",p.name,p.sex,p.age)
}type Student struct {Person//匿名字段,那么Student包含了Person的所有字段id intaddr string
}func main() {p := Person{"mike",'m',18}p.PrintInfo()s := Student{Person{"yoyo",'f',20},2,"sz"}s.PrintInfo()
}

方法的重写

type Person struct {name stringsex byteage int
}
//Person定义了方法
func (p *Person) PrintInfo() {fmt.Printf("Person:%s,%c,%d\n",p.name,p.sex,p.age)
}
type Student struct {Person//匿名字段,那么Student包含了Person的所有字段id intaddr string
}
//Student定义了方法
func (s *Student) PrintInfo() {fmt.Printf("Student:%s,%c,%d\n",s.name,s.sex,s.age)
}
func main() {p:=Person{"mike",'m',18}p.PrintInfo()    //Person:mike,m,18s:=Student{Person{"yoyo",'f',20},2,"sz"}s.PrintInfo()    //Student:yoyo,f,20s.Person.PrintInfo()    //Person:yoyo,f,20
}

方法值和方法表达式

类似于我们可以对函数进行赋值和传递一样,方法也可以进行赋值和传递。

根据调用者不同,方法分为两种表现形式:方法值和方法表达式。两者都可像普通函数那样赋值和传参,区别在于方法值绑定实例,⽽方法表达式则须显式传参。

方法值

type Person struct{name stringsex byteage int
}
func (p *Person) PrintInfoPointer() {fmt.Printf("%p,%v\n",p,p)
}
func (p Person) PrintInfoValue(){fmt.Printf("%p,%v\n",&p,p)
}
func main() {p:=Person{"mike",'m',18}p.PrintInfoPointer()    //0xc0420023e0,&{mike 109 18}pFunc1:=p.PrintInfoPointer    //方法值,隐式传递 receiverpFunc1()    //0xc0420023e0,&{mike 109 18}pFunc2:=p.PrintInfoValuepFunc2()    //0xc042048420,{mike 109 18}
}

方法表达式

type Person struct {name stringsex byteage int
}
func (p *Person) PrintInfoPointer() {fmt.Printf("%p,%v\n",p,p)
}
func (p Person) PrintInfoValue() {fmt.Printf("%p,%v\n",&p,p)
}
func main() {p:=Person{"mike",'m',18}p.PrintInfoPointer()//0xc0420023e0,&{mike 109 18}//方法表达式,须显式传参//func pFunc1 (p *Person))pFunc1:=(*Person).PrintInfoPointerpFunc1(&p)    //0xc0420023e0,&{mike 109 18}pFunc2:=Person.PrintInfoValuepFunc2(p)    //0xc042002460,{mike 109 18}
}

接口

在讲解具体的接口之前,先看如下问题。

使用面向对象的方式,设计一个加减的计算器

代码如下:

package mainimport "fmt"//父类
type Operate struct {num1 intnum2 int
}//加法子类
type Add struct {Operate
}//减法子类
type Sub struct {Operate
}//加法子类的方法
func (a *Add) Result() int {return a.num1 + a.num2
}//减法子类的方法
func (s *Sub) Result() int {return s.num1 - s.num2
}//方法调用
func main0201() {//创建加法对象//var a Add//a.num1 = 10//a.num2 = 20//v := a.Result()//fmt.Println(v)//创建减法对象var s Subs.num1 = 10s.num2 = 20v := s.Result()fmt.Println(v)
}

以上实现非常简单,但是有个问题,在main( )函数中,当我们想使用减法操作时,创建减法类的对象,调用其对应的减法的方法。但是,有一天,系统需求发生了变化,要求使用加法,不再使用减法,那么需要对main( )函数中的代码,做大量的修改。将原有的代码注释掉,创建加法的类对象,调用其对应的加法的方法。有没有一种方法,让main( )函数,只修改很少的代码就可以解决该问题呢?有,要用到接下来给大家讲解的接口的知识点。

什么是接口

接口就是一种规范与标准,在生活中经常见接口,例如:笔记本电脑的USB接口,可以将任何厂商生产的鼠标与键盘,与电脑进行链接。为什么呢?原因就是,USB接口将规范和标准制定好后,各个生产厂商可以按照该标准生产鼠标和键盘就可以了。

在程序开发中,接口只是规定了要做哪些事情,干什么。具体怎么做,接口是不管的。这和生活中接口的案例也很相似,例如:USB接口,只是规定了标准,但是不关心具体鼠标与键盘是怎样按照标准生产的.

在企业开发中,如果一个项目比较庞大,那么就需要一个能理清所有业务的架构师来定义一些主要的接口,这些接口告诉开发人员你需要实现那些功能。

接口定义

接口定义的语法如下:

//先定义接口  一般以er结尾  根据接口实现功能
type Humaner interface {//方法  方法的声明sayhi()
}

怎样具体实现接口中定义的方法呢?

type student11 struct {name  stringage   intscore int
}func (s *student11)sayhi()  {fmt.Printf("大家好,我是%s,今年%d岁,我的成绩%d分\n",s.name,s.age,s.score)
}type teacher11 struct {name    stringage     intsubject string
}func (t *teacher11)sayhi()  {fmt.Printf("大家好,我是%s,今年%d岁,我的学科是%s\n",t.name,t.age,t.subject)
}

具体的调用如下:

func main() {//接口是一种数据类型 可以接收满足对象的信息//接口是虚的  方法是实的//接口定义规则  方法实现规则//接口定义的规则  在方法中必须有定义的实现var h Humanerstu := student11{"小明",18,98}//stu.sayhi()//将对象信息赋值给接口类型变量h = &stuh.sayhi()tea := teacher11{"老王",28,"物理"}//tea.sayhi()//将对象赋值给接口 必须满足接口中的方法的声明格式h = &teah.sayhi()
}

只要类(结构体)实现对应的接口,那么根据该类创建的对象,可以赋值给对应的接口类型。

接口的命名习惯以er结尾。

多态

接口有什么好处呢?实现多态。

多态就是同一个接口,使用不同的实例而执行不同操作

所谓多态指的是多种表现形式,如下图所示:

使用接口实现多态的方式如下:

package mainimport "fmt"//先定义接口  一般以er结尾  根据接口实现功能
type Humaner1 interface {//方法  方法的声明sayhi()}type student12 struct {name  stringage   intscore int
}func (s *student12)sayhi()  {fmt.Printf("大家好,我是%s,今年%d岁,我的成绩%d分\n",s.name,s.age,s.score)
}type teacher12 struct {name    stringage     intsubject string
}func (t *teacher12)sayhi()  {fmt.Printf("大家好,我是%s,今年%d岁,我的学科是%s\n",t.name,t.age,t.subject)
}//多态的实现
//将接口作为函数参数  实现多态
func sayhello(h Humaner1)  {h.sayhi()
}func main() {stu := student12{"小明",18,98}//调用多态函数sayhello(&stu)tea := teacher12{"老王",28,"Go"}sayhello(&tea)
}

关于接口的定义,以及使用接口实现多态,大家都比较熟悉了,但是多态有什么好处呢?现在还是以开始提出的计算器案例给大家讲解一下,在开始我们已经实现了一个加减功能的计算器,但是有同学感觉太麻烦了,因为实现加法,就要定义加法操作的类(结构体),实现减法就要定义减法的类(结构体),所以该同学实现了一个比较简单的加减法的计算器,如下所示:

1.使用面向对象的思想实现一个加减功能的计算器,可能有同学感觉非常简单,代码如下:

我们定义了一个类(结构体),然后为该类创建了一个方法,封装了整个计算器功能,以后要使用直接使用该类(结构体)创建对象就可以了。这就是面向对象总的封装性。

也就是说,当你写完这个计算器后,交给你的同事,你的同事要用,直接创建对象,然后调用GetResult()方法就可以,根本不需要关心该方法是怎样实现的.这不是我们前面在讲解面向对象概念时说到的,找个对象来干活吗?不需要自己去实现该功能。

2.大家仔细观察上面的代码,有什么问题吗?

现在让你在改计算器中,再增加一个功能,例如乘法,应该怎么办呢?你可能会说很简单啊,直接在GetResult( )方法的switch中添加一个case分支就可以了。

问题是:在这个过程中,如果你不小心将加法修改成了减法怎么办?或者说,对加法运算的规则做了修改怎么办?举例子说明:

你可以把该程序方法想象成公司中的薪资管理系统。如果公司决定对薪资的运算规则做修改,由于所有的运算规则都在Operation类中的GetResult()方法中,所以公司只能将该类的代码全部给你,你才能进行修改。这时,你一看自己作为开发人员工资这么低,心想“TMD,老子累死累活才给这么点工资,这下有机会了”。直接在自己工资后面加了3000

numA+numB+3000

所以说,我们应该将加减等运算分开,不应该全部糅合在一起,这样你修改加的时候,不会影响其它的运算规则:

具体实现如下:

现在已经将各个操作分开了,并且这里我们还定义了一个父类(结构体),将公共的成员放在该父类中。如果现在要修改某项运算规则,只需将对应的类和方法发给你,进行修改就可以了。

这里的实现虽然将各个运算分开了,但是与我们第一次实现的还是有点区别。我们第一次实现的加减计算器也是将各个运算分开了,但是没有定义接口。那么该接口的意义是什么呢?继续看下面的问题。

3:现在怎样调用呢?

这就是我们一开始给大家提出的问题,如果调用的时候,直接创建加法操作的对象,调用对应的方法,那么后期要改成减法呢?需要做大量的修改,所以问题解决的方法如下:

创建了一个类OperationFactory,在改类中添加了一个方法CreateOption( )负责创建对象,如果输入的是“+”,创建

OperationAdd的对象,然后调用OperationWho( )方法,将对象的地址传递到该方法中,所以变量i指的就是OperationAdd,接下来在调用GetResult( )方法,实际上调用的是OperationAdd类实现的GetResult( )方法。

同理如果传递过来的是“-”,流程也是一样的。

所以,通过该程序,大家能够体会出多态带来的好处。

4:最后调用

这时会发现调用,非常简单,如果现在想计算加法,只要将”-”,修改成”+”就可以。也就是说,除去了main( )函数与具体运算类的依赖。

当然程序经过这样设计以后:如果现在修改加法的运算规则,只需要修改OperationAdd类中对应的方法,

不需要关心其它的类,如果现在要增加“乘法” 功能,应该怎样进行修改呢?第一:定义乘法的类,完成乘法运算。

第二:在OperationFactory类中CrateOption( )方法中添加相应的分支。但是这样做并不会影响到其它的任何运算。

大家可以自己尝试实现“乘法”与“除法”的运算。

在使用面向对象思想解决问题时,一定要先分析,定义哪些类,哪些接口,哪些方法。把这些分析定义出来,然后在考虑具体实现。

最后完整代码如下:

package mainimport "fmt"//定义接口
type Opter interface {//方法声明Result() int
}//父类
type Operate struct {num1 intnum2 int
}
//加法子类
type Add struct {Operate
}//加法子类的方法
func (a *Add) Result() int {return a.num1 + a.num2
}//减法子类
type Sub struct {Operate
}//减法子类的方法
func (s *Sub) Result() int {return s.num1 - s.num2
}//创建一个类负责对象创建
//工厂类
type Factory struct {}func (f *Factory) Result(num1 int, num2 int, ch string) {switch ch {case "+":var a Adda.num1 = num1a.num2 = num2Result(&a)case "-":var s Subs.num1 = num1s.num2 = num2Result(&s)}
}//通过设计模式调用
func main() {//创建工厂对象var f Factoryf.Result(10, 20, "+")
}

下面我们将接口其它的知识点再给大家说一下:

4.接口继承与转换(了解)
接口也可以实现继承:

package mainimport "fmt"//先定义接口  一般以er结尾  根据接口实现功能
type Humaner2 interface {   //子集//方法  方法的声明sayhi()}type Personer interface {  //超集Humaner2   //继承sayhi()sing(string)
}type student13 struct {name  stringage   intscore int
}func (s *student13)sayhi()  {fmt.Printf("大家好,我是%s,今年%d岁,我的成绩%d分\n",s.name,s.age,s.score)
}func (s *student13)sing(name string)  {fmt.Println("我为大家唱首歌",name)
}func main() {//接口类型变量定义var h Humaner2var stu student13 = student13{"小吴",18,59}h = &stuh.sayhi()//接口类型变量定义var p Personerp = &stup.sayhi()p.sing("大碗面")
}

接口继承后,可以实现“超集”接口转换“子集”接口,代码如下:

package mainimport "fmt"//先定义接口  一般以er结尾  根据接口实现功能
type Humaner2 interface {   //子集//方法  方法的声明sayhi()}type Personer interface {  //超集Humaner2   //继承sayhi()sing(string)
}type student13 struct {name  stringage   intscore int
}func (s *student13)sayhi()  {fmt.Printf("大家好,我是%s,今年%d岁,我的成绩%d分\n",s.name,s.age,s.score)
}func (s *student13)sing(name string)  {fmt.Println("我为大家唱首歌",name)
}func main()  {//接口类型变量定义var h Humaner2  //子集var p Personer    //超集var stu student13 = student13{"小吴",18,59}p = &stu//将一个接口赋值给另一个接口//超集中包含所有子集的方法h = p  //okh.sayhi()//子集不包含超集//不能将子集赋值给超集//p = h  //err//p.sayhi()//p.sing("大碗面")
}

空接口

空接口(interface{})不包含任何的方法,正因为如此,所有的类型都实现了空接口,因此空接口可以存储任意类型的数值。

例如:

var i interface{}
//接口类型可以接收任意类型的数据
//fmt.Println(i)
fmt.Printf("%T\n",i)
i = 10
fmt.Println(i)
fmt.Printf("%T\n",i)

当函数可以接受任意的对象实例时,我们会将其声明为interface{},最典型的例子是标准库fmt中PrintXXX系列的函数,例如:

func Printf(fmt string, args ...interface{})
func Println(args ...interface{})如果自己定义函数,可以如下:func Test(arg ...interface{}) {}

Test( )函数可以接收任意个数,任意类型的参数。

类型查询

我们知道interface的变量里面可以存储任意类型的数值(该类型实现了interface)。那么我们怎么反向知道这个变量里面实际保存了的是哪个类型的对象呢?目前常用的有两种方法:

  • comma-ok断言
  • switch测试

comma-ok断言

Go语言里面有一个语法,可以直接判断是否是该类型的变量: value, ok = element.(T),这里value就是变量的值,ok是一个bool类型,element是interface变量,T是断言的类型。

如果element里面确实存储了T类型的数值,那么ok返回true,否则返回false。

var i []interface{}i = append(i, 10, 3.14, "aaa", demo15)for _, v := range i {if data, ok := v.(int); ok {fmt.Println("整型数据:", data)} else if data, ok := v.(float64); ok {fmt.Println("浮点型数据:", data)} else if data, ok := v.(string); ok {fmt.Println("字符串数据:", data)} else if data, ok := v.(func()); ok {//函数调用data()}
}

switch测试

var i []interface{}i = append(i, 10, 3.14, "aaa", demo15)for _,data := range i{switch value:=data.(type) {case int:fmt.Println("整型",value)case float64:fmt.Println("浮点型",value)case string:fmt.Println("字符串",value)case func():fmt.Println("函数",value)}
}

Go语言:面向对象编程相关推荐

  1. c语言面向对象编程中的类_C ++中的面向对象编程

    c语言面向对象编程中的类 Object oriented programming, OOP for short, aims to implement real world entities like ...

  2. C语言面向对象编程(四):面向接口编程

    Java 中有 interface 关键字,C++ 中有抽象类或纯虚类可以与 interface 比拟,C 语言中也可以实现类似的特性. 在面试 Java 程序员时我经常问的一个问题是:接口和抽象类有 ...

  3. C语言面向对象编程(二):继承详解

    在  C 语言面向对象编程(一)里说到继承,这里再详细说一下. C++ 中的继承,从派生类与基类的关系来看(出于对比 C 与 C++,只说公有继承): 派生类内部可以直接使用基类的 public .p ...

  4. C语言面向对象编程(六):配置文件解析

    在实际项目中,经常会把软件的某些选项写入配置文件. Windows 平台上的 INI 文件格式简单易用,本篇文章利用<C语言面向对象编程(五):单链表实现>中实现的单链表,设计了一个&qu ...

  5. java 168转换成861_java实验-java语言面向对象编程基础

    java实验-java语言面向对象编程基础 (12页) 本资源提供全文预览,点击全文预览即可全文预览,如果喜欢文档就下载吧,查找使用更方便哦! 8.90 积分 广州大学学生实验报告广州大学学生实验报告 ...

  6. 我所偏爱的 C 语言面向对象编程范式

    我所偏爱的 C 语言面向对象编程范式 面向对象编程不是银弹.大部分场合,我对面向对象的使用非常谨慎,能不用则不用.相关的讨论就不展开了. 但是,某些场合下,采用面向对象的确是比较好的方案.比如 UI ...

  7. c语言面向对象编程显示,c语言面向对象编程

    场景:C语言面向对象编程(6):配置文件解析 C语言面向对象编程(六):配置文件解析 在实际项目中,经常会把软件的某些选项写入配置文件. Windows 平台上的 INI 文件格式简单易用,本篇文章利 ...

  8. mcem r语言代码_R语言面向对象编程:S3和R6

    R语言面向对象编程:S3和R6 2017-06-10 0 R语言面向对象编程:S3和R6 一.基于S3的面向对象编程 基于S3的面向对象编程是一种基于泛型函数(generic function)的实现 ...

  9. C语言面向对象编程的类是指,c语言面向对象编程中的类_C ++中的面向对象编程...

    c语言面向对象编程中的类 Object Oriented programming is a programming style that is associated with the concept ...

  10. C语言面向对象编程(五):单链表实现

    前面我们介绍了如何在 C 语言中引入面向对象语言的一些特性来进行面向对象编程,从本篇开始,我们使用前面提到的技巧,陆续实现几个例子,最后呢,会提供一个基本的 http server 实现(使用 lib ...

最新文章

  1. Spring MVC 之拦截器(八)
  2. Mysql实现企业级日志管理、备份与恢复
  3. 交换机的一些常见网络命令
  4. 11月12号 用户登录输入密码错误达到指定次数后,锁定账户 004
  5. asynchronous vs non-blocking
  6. 论文|SDNE的算法原理、代码实现和在阿里凑单场景中的应用说明(附代码)
  7. 计算机算法设计与分析 递归实现归并排序和非递归实现归并排序
  8. 为什么接口在设计时所有的方法一般都要抛异常?
  9. 利用老毛桃启动盘制作三合一系统启动:WINPE + CDlinux + Ubuntu
  10. 【直线检测】【matlab】基于Hough变换的直线检测
  11. DBMS_SQLTUNE + SQL Performance Analyzer 实战小记
  12. 计算机修改了服务如何恢复,怎么修复MSDTC服务?
  13. 导出excel 并且处理长数字,处理科学计数法,以文本形式存储的数字
  14. Levenshtein Distance编辑距离应用实践——拼写检查(Java fork/join框架实现)
  15. 大寰机器人通讯转换系统(CTS-B1.0) 操作说明
  16. 如何正确注册Tushare
  17. 命运2服务器维护2021,命运2 2021年3月17日更新内容详解 冰影系列削弱一览[多图]...
  18. 携程算法岗笔试【20230525】
  19. 数据中心网络和计算存储方案
  20. 如何在python中制作超级玛丽_超级玛丽的 python 实现

热门文章

  1. 2. 配置Xdebug
  2. 循序渐进之Spring AOP(6) - 使用@Aspect注解
  3. php curl模拟织梦登录,PHP 模拟浏览器 CURL 采集阿里巴巴
  4. 在手动安装 Kubernetes 的基础上搭建微服务
  5. angular组件图标无法显示的问题
  6. corntab主调度脚步
  7. CSS3D写3d画廊滚动
  8. POJ3414(BFS+[手写队列])
  9. JVM--Garbage First
  10. JinlinOJ 通化邀请赛 E.GCD and LCM 最大公约数最小公倍数 关系