蹒跚学Go第四天-面向对象

面向对象

我们可以将这些具有相同属性和相同方法的对象进行进一步的封装,抽象出来 类这个概念。
类就是个模子,确定了对象应该具有的属性和方法。先有类后有对象

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

  • Go是面向工程的实用主义者,其继承了面向对象、函数式和过程式等各个范式语言的优点,使用函数、接口、类型组合、包等简单的语言特性,组合产生强大的表现力,可以轻松地构建大规模程序。

继承

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

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

匿名字段创建与初始化

  • 那么怎样实现属性的继承呢? -> 可以通过匿名字段(也叫匿名组合)来实现
type person struct {
    id int
    name string
    age int
}

type student struct {
    person  //匿名字段继承了person的
    score float64
}

func main() {
    //顺序初始化,自动推倒类型
    s := student{person{1, "ccy", 18},1}
    fmt.Printf("%v\n",s)    //%v显示更详细
    //指定成员初始化,没有初始化的 -> 整数为0,字符串为空
    s1 :=student{score:45}
    fmt.Printf("%v\n",s1)
    //针对特定的Person成员初始化
    s2 := student{person: person{name:"ccy"},score:123}
    fmt.Printf("%v\n",s2)
}
输出的值:
{{1 ccy 18} 1}
{{0  0} 45}
{{0 ccy 0} 123}

成员操作

  • 创建完成对象后,可以根据对象来操作对应成员属性,是通过“.”运算符来完成操作的

  • 根据类(结构体)可以创建出很多的对象,这些对象的成员(属性)是一样的,但是成员(属性)的值是可以完全不一样的

func main() {
    //顺序初始化,自动推倒类型
    s := student{person{1, "ccy", 18},1}
    fmt.Printf("%v\n",s)    //%v显示更详细
    //指定成员初始化,没有初始化的 -> 整数为0,字符串为空
    s1 :=student{score:45}
    fmt.Printf("%v\n",s1)
    //针对特定的Person成员初始化
    s2 := student{person: person{name:"ccy"},score:123}
    fmt.Printf("%v\n",s2)
    //由于Student继承了Person,所以Person具有的成员,Student也有
    //所以根据Student创建出的对象可以直接对age成员项进行修改。
    s.score=123
    s1.name="cz"
    s2.name="cc"
    s1.person = person{12,"sd",2}
}

同名字段

  • 通过结果发现是对Student中的name进行赋值,所以在操作同名字段时,有一个基本的原则:如果能够在自己对象所属的类(结构体)中找到对应的成员,那么直接进行操作,如果找不到就去对应的父类(结构体)中查找。这就是所谓的就近原则。
type pay struct {
    id int
    name string
    age int
}
type people struct {
    pay
    name string //和pay结构体中同名
    none float64
}

func main() {
    var a people
    a.name = "ccy"
    fmt.Printf("a->%v",a)
}
输出值:
a->{{0  0} ccy 0}

指针类型匿名字段

  • 结构体(类)中的匿名字段的类型,也可以是指针

  • new( )的作用是分配空间,new( )函数的参数是一个类型,这里为Person结构体类型,返回值为指针类型,所以赋值给Person,这样Person也就指向了结构体Person的内存

type pay struct {
    id int
    name string
    age int
}
type people struct {
    *pay //指针类型匿名字段
    none float64
}

func main() {
    var a people
    a = people{&pay{1,"ccy",12},90} //因为pay是指针必须取地址否则为nil报错
    fmt.Printf("%v\n",a)        //取出a的地址
    fmt.Println(a.id,a.name,a.age)  //取出值
    //第二种定义方式new()
    var v people
    v.pay = new(pay)
    v.age=12
    v.name="dd"
}

多重继承

多重继承在生活中经常遇见,例如孩子继承父母的特征,父母是两个父级类。在大部分面向对象语言中,是不允许多重继承的,因为这会导致编译器变得复杂,不过由于Go语言并没有类的概念,所谓继承其实是内嵌结构体,通过在类型中嵌入所有必要的父类型,可以很简单地实现多重继承。Go语言的多重继承不支持多重嵌套(即父级类型内部不允许有匿名结构体字段),简而言之:多重继承指的是一个类可以继承另外一个类,而另外一个类又可以继承别的类,比如A类继承B类,而B类又可以继承C类,这就是多重继承。

  • 注意:尽量在程序中,减少多重继承,否则会增加程序的复杂度
package main

import "fmt"

type Camera struct{}

func (c*Camera) TakeAPicture()string{
    return "拍照"
}
type phone struct{}

func (p*phone) Cal1()string{
    return "响铃"
}
type CameraPhone struct{
    Camera
    phone
}
func main() {
    cp := new(CameraPhone)
    fmt.Println("我们的新款拍照手机有多种功能:")
    fmt.Println("打开了相机:", cp.TakeAPicture())
    fmt.Println("电话来电:", cp.Cal1())
}
输出值:
我们的新款拍照手机有多种功能:
打开了相机: 拍照
电话来电: 响铃

方法

Go语言的类型方法是一种对类型行为的封装。Go语言的方法非常纯粹,可以看作特殊类型的函数,其显式地将对象实例或指针作为函数的第一个参数,并且参数名可以自己指定,而不强制要求一定是this或self。这个对象实例或指针称为方法的接收者(reciever)。

  • 方法是函数,所以不允许方法重载,对于一个类型只能有一个给定名称的方法。但如果基于接收者类型,则允许重载,具有同样名字的方法可以在多个不同的接收者类型上存在,比如在同一个包里这样做是允许的

  • 定义方法语法

func (receiver ReceiverType) funcName(parameters) (results)
1.参数 receiver 可任意命名。如方法中未曾使用,可省略参数名。
2.参数 receiver 类型可以是 T 或 *T。基类型 T 不能是接口或指针。
3.不支持重载方法,也就是说,不能定义名字相同但是不同参数的方法。
  • 基本使用创建方法
type intself int    //为int定义别名

func (a intself) test(b intself) intself{   //表示定义了一个方法,方法的定义与函数的区别
    return a + b
}

func main(){
    var result intself = 3
    r := result.test(3)
    fmt.Println(r)
}
  • 第一:在关键字后面加上( a Integer), 这个在方法中称之为接收者所谓的接受者就是接收传递过来的第一个参数,然后复制a的类型是Integer ,由于Integer是int的别名,所以a的类型为int

  • 第二:在表示参数的类型时都使用了对应的别名。通过方法的定义可以看出方法其实就是给某个类型绑定的函数。整型绑定的函数,只不过在给整型绑定函数(方法)时,一定要通过type来指定一个别名,因为int类型是系统已经规定好了,无法直接绑定函数,所以只能通过别名的方式。

  • 第三:调用方式不同 -> var result Interger=3 -> result.Test(3)

通过result变量,完成方法的调用。因为,Test( )方法,是为int类型绑定的函数,而result变量为int类型。所以可以调用Test( )方法。result变量的值会传递给Test( )方法的接受者,也就是参数a, 而实参Test( 3),会传递形参b.当然,我们也可以将Test( )方法,理解成是为int类型扩展了,追加了的方法。因为系统在int类型时,是没有改方法的。以上的定义,发现方法其实方法就是函数的语法糖

给结构体添加方法

  • 给结构体添加方法的方式与前面给int类型添加方法的方式,基本一致。唯一不同的是,不需要给结构体指定别名,因为结构体pay就是相当于其所有成员属性的别名(id,name,age),所以这里不要在给结构体Student创建别名

  • 调用方式:根据结构体(类)创建的对象,完成了方法的调用

  • 在创建方法时,接收者类型为指针类型,所以在调用方法时,创建一个结构体变量,同时将结构体变量的地址,传递给方法的接收者,然后调用EditInfo( )方法,完成要修改的数据传递。

type pay struct {
    id int
    name string
    age int
    none float64
}

func (stu pay) show(){  //方法接受者
    fmt.Println(stu)
}
//使用指针来修改结构体值
func (p *pay) editinfo(id int,name string,age int,none float64){
    p.id=id
    p.name=name
    p.age=age
    p.none=none
}

func main(){
    //初始化赋值
    s := pay{1,"ccy",3,13.00}
    s.show()
    //使用方法 -> 将结构体的指针传递给接受者,同时将要修改的数据传输到方法中
    (&s).editinfo(11,"312",12,123)
    s.show()    //创建完对象调用方法
}

注意点

  • (1)只要接收者类型不一样,这个方法就算同名,也是不同方法,不会出现重复定义函数的错误

  • (2)但是,如果接收者类型一样,但是方法的参数不一样,是会出现错误的在GO中没有方法重载(所谓重载,指的是方法名称一致,参数类型,个数不一致)

指针变量的方法值

  • 接收者为普通变量,非指针,值传递
type pay struct {
    id int
    name string
    age int
    none float64
}

func (stu pay) show(id int,name string,age int, none float64){
    stu.id=id
    stu.name=name
    stu.age=age
    stu.none=none
}

func main(){
    var a pay //定义为结构类型的变量
    a.show(2,"ccy",2,1231)
    fmt.Println(a)
}
输出值:
{0  0 0}
  • 接收者为指针变量,引用传递
type pay struct {
    id int
    name string
    age int
    none float64
}

func (stu *pay) show(id int,name string,age int, none float64){
    stu.id=id
    stu.name=name
    stu.age=age
    stu.none=none
}

func main(){
    var a pay //定义为结构类型的变量
    (&a).show(2,"ccy",2,1231)
    fmt.Println(a)
}
输出的值:
{2 ccy 2 1231}
  • 定义一个结构体指针变量,能否调用edit( )方法呢?

  • 可以的,先将指针stu, 转换成*stu在调用

  • 如果结构体变量是一个指针变量,它能够调用哪些方法,这些方法就是一个集合,简称方法集

type pay struct {
    id int
    name string
    age int
    none float64
}

func (a pay) show(id int,name string,age int,none float64){
    a.id=id
    a.name=name
    a.age=age
    a.none=none
    fmt.Println(a)
}

func (p *pay) edit(id int,name string,age int,none float64){
    p.id=id
    p.name=name
    p.age=age
    p.none=none
    fmt.Println(p)
}

func main(){
    var b pay
    (&b).show(1,"a",2,3)
    (&b).edit(2,"b",3,4)
        //等价于如下代码
    (*(&b)).show(3,"c",5,6)
}
输出的值:
{1 a 2 3}
&{2 b 3 4}
{3 c 5 6}
  • 实现公里数/价格换算(distance/price)
          价格表
-------------------------
0-100公里     票价不打折
101-200公里    总额打9.5折
201-300公里    总额打9折
300公里以上    总额打8折
-------------------------
type ticket struct {
    distance int
    price float64
}

func (way *ticket) show(distance int,price float64){
    way.distance=distance
    way.price=price
    if way.distance <=100 && way.distance>0 {
        way.price = price
    }else if way.distance >= 101 && way.distance < 200{
        way.price = price * 0.95
    }else if way.distance >= 201 && way.distance < 300 {
        way.price = price * 0.9
    }else {
        way.price = price * 0.8
    }
    way.distance=distance
    fmt.Println("公里数:",way.distance,"价格:",way.price)
}

func main(){
    var a ticket
    (&a).show(230,100)
}

方法继承

使用type定义的新类型不会继承原有类型的方法,有个特例就是命名结构类型,命名结构类型可以嵌套其他的命名类型的字段,外层的结构类型是可以调用嵌入字段类型的方法,这种调用既可以是显式的调用,也可以是隐式的调用。这就是Go的“继承”,准确地说这就是Go的“组合”。因为Go语言没有继承的语义,结构和字段之间是“hasa”的关系,而不是“isa”的关系;没有父子的概念,仅仅是整体和局部的概念,所以后续统称这种嵌套的结构和字段的关系为组合。

  • 使用方法继承来现实以下的例子:

  • 记者:我是记者 我的爱好是偷拍 我的年龄是34 我是一个男狗仔

  • 程序员:我叫孙权 我的年龄是23 我是男生 我的工作年限是 3年

//相同的部分
type carrer struct {
    name string
    age  int
}
//定义狗仔
type show struct {
    carrer
    job string
    love string
}

func (a *show) reporter(name string,age int,job string,love string) {
    a.name=name
    a.age=age
    a.job=job
    a.love=love

}
//定义程序员
type show2 struct {
    carrer
    sex string
    workingyear string
}

func (b *show2) programmer(name string,age int,sex string,workingyear string){
    b.name=name
    b.age=age
    b.sex=sex
    b.workingyear=workingyear
}

func main() {
    var q show
    var w show2
    q.reporter("我是记者",34,"我是一个男狗仔","我的爱好是偷拍")
    w.programmer("我叫孙权",23,"男","我是程序员")
    fmt.Println(q,"\n",w)
}
输出的值:
{{我是记者 34} 我是一个男狗仔 我的爱好是偷拍} 
{{我叫孙权 23} 男 我是程序员}

方法重写

子类(结构体)可以继承父类中的方法,但是,如果父类中的方法与子类的方法是重名方法会怎样?

  • 如果子类(结构体)中的方法名与父类(结构体)中的方法名同名,在调用的时候是先调用子类(结构体)中的方法,这就方法的重写。所谓的重写:就是子类(结构体)中的方法,将父类中的相同名称的方法的功能重新给改写了。

为什么要重写父类(结构体)的方法呢?

通常,子类(结构体)继承父类(结构体)的方法,在调用对象继承方法的时候,调用和执行的是父类的实现。但是,有时候需要
对子类中的继承方法有不同的实现方式。例如,假设动物存在“跑”的方法,从中继承有狗类和马类两个子类,但是它们的跑是不一样的。

//定义一个动物类的结构体
type animal struct {
    age int
}
//定义一个狗结构体并且继承动物类的结构体
type Dog struct {
    animal
    name string
}
//定义一个猫结构体并且继承动物类的结构体
type Cat struct {
    animal
    name string
}
//定义狗方法
func (p *animal) bark(){
    fmt.Println("旺旺")
}
//定义猫方法
func (c *Cat) bark(){
    fmt.Println("喵喵")
}
func main() {
    var dog Dog
    var cat Cat
    dog.bark()
    cat.bark()
}
输出值:
旺旺
喵喵
  • 练习:设计一个加减的计算机
type object struct {
    num1 int
    num2 int
}

func (a *object) jiafa(num1 int,num2 int)  int{
    a.num1=num1
    a.num2=num2
    return num1+num2
}

func (a *object) jian(num1 int,num2 int)  int{
    a.num1=num1
    a.num2=num2
    return num1-num2
}

func main(){
    var zhi object
    s := zhi.jiafa(5,3)
    s1 := zhi.jian(16,12)
    fmt.Println(s,s1)
}

接口

Go语言是“非传统”的面向对象编程语言,它没有类和继承的概念,但是Go语言里有非常灵活的接口概念,通过它可以实现很多面向对象的特性。接口提供了一种方式来说明对象的行为:如果谁能搞定这件事,它就可以在这里调用。
接口定义了一组方法(方法集),但是这些方法不包含(实现)代码——它们没有被实现(它们是抽象的),接口里也不能包含变量。接口是一个编程规约,也是一组方法签名的集合。Go的接口是非侵入式的设计,也就是说,一个具体类型实现接口不需要在语法上显式地声明,只要具体类型的方法集是接口方法集的超集,就代表该类型实现了接口,编译器在编译时会进行方法集的校验。接口是没有具体实现逻辑的,也不能定义字段。

  • 接口类型是一种抽象的类型,它不会暴露出它代表的对象的内部值的结构和这个对象支持的基础操作的集合,他们只会展示出它们自己的方法,因此接口类型不能将其实例化

  • Go通过接口实现了,不关心对象具体是什么类型只关心对象的行为

接口声明

  • Go语言的接口分为接口字面量类型和接口命名类型,接口的声明使用interface关键字。

  • 接口是用来定义行为的类型。这些被定义的行为不由接口直接实现,而是通过方法由用户定义的类型实现,一个实现了这些方法的具体类型是这个接口类型的实例。

interface {
    MethodSignature1
    MethodSignature1
}
  • 接口命名类型使用type关键字声明
type InterfaceName interface{
    MethodSignaturel
    MethodSignature2
}
  • 使用接口字面量的场景很少,一般只有空接口interface{}类型变量的声明才会使用

接口的特点

  • 类型不需要显式声明它实现了某个接口:接口被隐式实现。多个类型可以实现同一个接口,实现某个接口的类型(除了实现接口方法外)可以有其他的方法。一个类型可以实现多个接口,接口类型可以包含一个实例的引用,该实例的类型实现了此接口(接口是动态类型)。

  • 即使接口在类型实现之后才定义,二者处于不同的包中,被单独编译,只要类型实现了接口中的方法,它就实现了此接口,所有这些特性使得接口具有很大的灵活性。

  • (1)接口的命名一般以“er”结尾。

  • (2)接口定义的内部方法声明不需要func引导。

  • (3)在接口定义中,只有方法声明没有方法实现,没有数据字段

  • (5)接口可以匿名嵌入其他接口,或嵌入到结构中

接口的用法

  • 1.容器类型,存储该接口的实现类的对象

  • 2.函数的参数,可以传入任意实现类的对象

  • 3.函数的返回值,返回任意实现类的对象

接口的实现

  • 接口是用来定义行为的类型。这些被定义的行为不由接口直接实现,而是由用户定义的类型实现,一个实现了这些方法的具体类型是这个接口类型的实例。

定义一个接口

type square interface {
    stat() float32
}

type m struct {
    side float32
}

func (tmp *m) stat() float32{
    return tmp.side * tmp.side
}

func main(){
    var jirguo m
    jirguo.side = 5
    s := jirguo
    fmt.Printf("面积为:%f\n",s.stat())
}
输出结果:
面积为:25.000000

解释:
1.程序定义了一个结构体m和一个接口square,接口有一个方法stat。
2.在main()方法中创建了一个Square的实例。在主程序外边定义了一个接收者类型是Square方法的stat,用来计算正方形的面积,结构体Square实现了接口Shaper。所以可以将一个Square类型的变量赋值给一个接口类型的变量:s=jieguo。
3.现在接口变量包含一个指向Square 变量的引用,通过它可以调用Square上的方法Area0。当然也可以直接在Square的实例上调用此方法,但是在接口实例上调用此方法更具通用性。接口变量里包含了接收者实例的值和指向对应方法表的指针。
4.这是Go语言版本的“多态”,多态是面向对象编程中一个广为人知的概念:根据当前的类型选择正确的方法,或者说同一种类型在不同的实例上似乎表现出不同的行为。

接口类型与约定

  • 接口类型实际上是描述了一系列方法的集合,一个实现了这些方法的具体类型是这个接口类型的实例。

  • io.Writer类型是用得最广泛的接口之一,因为它提供了所有的类型写入bytes(字节)的抽象,包括文件类型、内存缓冲区、网络链接、HTTP客户端、压缩工具、哈希等。io包中定义了很多其他有用的接口类型。io.Reader可以代表任意可读取bytes的类型,io.Closer可以是任意可关闭的值

//写法一
type reader interface {
    read(p []bye)(n int,err error)
}
type closer interface {
    close() error
}
type write interface {
    write() error
}
//写法二
type ReadWrite interface {
    reader
    write
}
type ReadWriteClose interface {
    reader
    write
    closer
}
  • 上面用到的语法和结构内嵌相似,可以用这种方式以一个简写命名另一个接口,而不用声明它所有的方法,这种方式被称为接口内嵌。尽管略失简洁性,可以像下面这样,不使用内嵌来声明io.Writer接口
type ReadWriter interface {
    read(p []bye)(n int,err error)
    //write(p []bye)(n int,err error)
    //混合方式
    write
}
  • 上面3种定义方式都是一样的效果。方法的顺序变化也没有影响,唯一重要的就是这个集合里面的方法

接口转换

  • 超集接⼝对象可转换为⼦集接⼝,反之出错
type Humaner interface {
    SayHi()
}

type Personer interface {
    Humaner //这里像写了SayHi()一样
    Sing(lyrics string)
}

type Student struct { //学生
    name  string
    score float64
}

//Student实现SayHi()方法
func (s *Student) SayHi() {
    fmt.Printf("Student[%s, %f] say hi!!\n", s.name, s.score)
}

//Student实现Sing()方法
func (s *Student) Sing(lyrics string) {
    fmt.Printf("Student sing[%s]!!\n", lyrics)
}

func main() {
    //Personer为超集,Humaner为子集
    var i1 Personer = &Student{"mike", 88.88}
    var i2 Humaner = i1
    i2.SayHi() //Student[mike, 88.880000] say hi!!
}

接口的动态类型

接口绑定的具体实例的类型称为接口的动态类型。接口可以绑定不同类型的实例,所以接口的动态类型是随着其绑定的不同类型实例而发生变化的。

  • 一个接口类型的变量varl中可以包含任何类型的值,必须有一种方式来检测它的动态类型,即运行时在变量中存储的值的实际类型。在执行过程中动态类型可能会有所不同,但是它一定是可以分配给接口变量的类型。通常可以使用类型断言(Go语言内置的一种智能推断类型的功能)来测试在某个时刻varl是否包含类型T的值
func main() {
    dog := Dog{"little pig"}
    var pet Pet = &dog
}
比如:我们把取址表达式&dog的结果值赋给了变量pet,这时这个结果值就是变量pet的动态值,而此结果值的类型*Dog就是该变呈的动态类型。
动态类型这个叫法是相对于静态类型而言的。对于变量pet来讲,它的静态类型就是Pet,并且永远是Pet,但是它的动态类型却会随着我们赋给它的动态值而变化。

接口的静态类型

接口被定义时,其类型就已经被确定,这个类型叫接口的静态类型。接口的静态类型在其定义时就被确定,静态类型的本质特征就是接口的方法签名集合。两个接口如果方法签名集合相同(方法的顺序可以不同),则这两个接口在语义上完全等价,它们之间不需要强制类型转换就可以相互赋值。原因是Go编译器校验接口是否能赋值,是比较二者的方法集,而不是看具体接口类型名。a接口的法集为A,b接口的法集为B,如果B是A的子集合,则a的接口变量可以直接赋值给B的接口变量。反之,则需要用到接口类型断言。

  • 相较于动态类型就是静态类型赋予的为值

类型断言

  • 语法格式
i.(TypeNname)
注意:i必须是接口变量,如果是具体类型变量,则编译器会报non-interface type xxx on left,TypeNname可以是接口类型名,也可以是具体类型名。
  • 接口查询的两层语义:
    • (1)如果TypeNname是一个具体类型名,则类型断言用于判断接口变量i绑定的实例类型是否就是具体类型TypeNname。

    • (2)如果TypeName是一个接口类型名,则类型断言用于判断接口变量i绑定的实例类型是否同时实现了TypeName接口。

  • 实际实例:

type Inter interface {
    ping()
    pang()
}
type Anter interface {
    Inter
    String()
}
type St struct {
    Name string
}
func (St) ping(){
    println("ping")
}
func (*St) pong(){
    println("pang")
}
func main(){
    st := &St{"andes"}
    var i interface{} = st
    //判断i绑定的实例是否实现了接口类型Inter
    if a,ok:= i.(Inter) ;ok{
        a.ping()
        a.pang()
    }
    //i没有实现接口Anter,所以程序不会执行这个
    if p,ok := i.(Anter);ok{
        p.String()
    }
    //判断i绑定的实例是否就是具体的类型St
    if s,ok := i.(*St);ok{
        fmt.Printf("%s",s.Name)
    }
}

接口优点和使用形式

  • (1)解耦:复杂系统进行垂直和水平的分割是常用的设计手段,在层与层之间使用接口进行抽象和解耦是一种好的编程策略。Go的非侵入式的接口使层与层之间的代码更加干净,具体类型和实现的接口之间不需要显式声明,增加了接口使用的自由度。

  • (2)实现泛型:由于现阶段Go语言还不支持泛型,使用空接口作为函数或方法参数能够用在需要泛型的场景中。

接口使用形式

  • 接口类型是“第一公民”,可以用在任何使用变量的地方,使用灵活,方便解耦
    • (1)作为结构内嵌字段。

    • (2)作为函数或方法的形参。

    • (3)作为函数或方法的返回值。

    • (4)作为其他接口定义的嵌入字段。

多态

多态是面向对象编程中一个广为人知的概念:根据当前的类型选择正确的方法,或者说同一种类型在不同的实例上似乎表现出不同的行为。

  • 实现类的对象 -> 接口类型:访问接口中的方法

  • 实现类的对象 -> 实现类类型:访问实现类的方法和属性

//定义一个接口
type Shape interface {
    peri() float64
    area() float64
}

//定义实现类:三角形
type Triangle struct {
    a,b,c float64
    //三条边定义
}

type Circle struct {
    radius float64  //圆半径
}

func (tmp Circle) peri() float64{
    return tmp.radius*2*math.Pi //求周长
}

func (tmp Circle) area() float64  {
    return math.Pow(tmp.radius,2)*math.Pi
}

func (tmp Triangle)peri() float64{
    return tmp.a + tmp.b + tmp.c
}
func (tmp Triangle)area() float64{
    //半周长
    a := tmp.peri() / 2
    s := math.Sqrt(a*(a-tmp.a)*(a-tmp.b)*(a-tmp.c))
    return s
}
//多态调用函数
func test(i Shape){
    fmt.Println("周长:",i.peri(),"面积:",i.area())
}
//转型使用方法一
func GetTyoe(s Shape){
    //instance,ok := 接口对象.(实际类型)
    //如果改接口对象是对应的实际类型,那么instance就是转型之后的对象,ok值=true

    if instance,ok := s.(Triangle);ok{  //判断是否符合
        fmt.Println("是三角形,三条边为:",instance.a,instance.b,instance.c)
    }else if instance,ok := s.(Circle);ok{
        fmt.Println("是圆形,半径为:",instance.radius)
    }else {
        fmt.Println("未知!")
    }
}
//转型使用方法二
func GetType2(s Shape){
    //方法二:接口对象.{type} -> 配合swich/case语句使用
    switch instance:=s.(type) {
    case Triangle:
        fmt.Println("是三角形,三条边为:",instance.a,instance.b,instance.c)
    case Circle:
        fmt.Println("是圆形,半径为:",instance.radius)
    }
}

func main(){
    t1 := Triangle{3,4,5}
    //多态:一个事物的多种形态,GO语言通过接口来模拟多态性
    //一个实现类的对象看做一个实现类的类型:能够访问实现类当中的方法和属性
    //还可以看做对应的接口类型:只能够访问接口定义中的方法
    fmt.Println(t1,"面积:",t1.peri(),"周长:",t1.area())
    var c Circle
    c = Circle{5}
    fmt.Println("面积:",c.area(),"周长",c.peri())

    //用法一:一个函数如果接受接口类型作为参数,实际上可以传入该接口的任意实现类对象作为参数
    //用法二:如果定义一个类型为接口类型,那个可以赋值为任意实现类的对象
    test(t1)
    test(c)

    //方法三:使用数组实现
    //如果定义了一个接口类型的容器 -> 实际上容器中看可以存储任意的实现类对象
    arr := [2]Shape{t1,c}
    fmt.Println(arr)

    fmt.Println("--------------------------------")
    //接口类型的对象 -> 对应的实现类的类型
    GetTyoe(t1)
    GetTyoe(c)
}
输出值:
{3 4 5} 面积: 12 周长: 6
面积: 78.53981633974483 周长 31.41592653589793
周长: 12 面积: 6
周长: 31.41592653589793 面积: 78.53981633974483
[{3 4 5} {5}]
--------------------------------
是三角形,三条边为: 3 4 5
是圆形,半径为: 5

空接口

没有任何方法的接口,我们称之为空接口。空接口表示为interface}。系统中任何类型都符合空接口的要求,空接口有点类似于Java语言中的Object。不同之处在于,Go中的基本类型int、float和string也符合空接口。Go的类型系统里面没有类的概念,所有的类型都是一样的身份,没有Java里面对基本类型的开箱和装箱操作,所有的类型都是统一的。Go语言的空接口有点像C语言中的void,只不过void是指针,而Go语言的空接口内部封装了指针而已。

  • 也是一个接口,但是该接口中没有任何的方法,可以将任意类型接口的实现

空接口

  • 空接口不包含任何的方法。因此空接口可以存储任意类型的数值,因此空接口是可以存储任意类型的数值
var vl interface{}=123
var v2 interface{}="abcd"
var v3 interface{}=&v1
var v4 interface{}=struct{a int }{123}
  • 当函数可以接受任意的对象实例时,我们会将其声明为interfaceft.最典型的例子是标准库mt中PrintXXX系列的函数
例子:
func Printf(fmt string, args... interface{})
func Println(args... interface})
type A interface {

}

type Cat struct {
    name string
    age int
}

type Person struct{
    name string
    sex string
}
//空接口:任意类型 -> 将匿名空接口作为参数表示可以接受任意类型的参数
func test(a interface{}){

}

func test2(slice2 []interface{}){
    for i:=0;i<len(slice2);i++{
        fmt.Println("当前",i+1,"个数据:")
        switch ins := slice2[i].(type) {
        case Cat:
            fmt.Println("\t猫对象:",ins.name,ins.age)
        case Person:
            fmt.Println("\t人对象:",ins.name,ins.sex)
        case int:
            fmt.Println("\tint类型:",ins)
        case string:
            fmt.Println("\tstring类型",ins)
        }
    }   
}

func main(){
    var a1 A = Cat{"猫",1}
    var a2 A = Person{"狗子","小鸡鸡"}
    var a3 int =1
    var a4 string="傻逼"
    //一个空接口的使用:定义一个map -> string作为key,任意类型作为value
    map1 := make(map[string]interface{})
    //定义一个切片,存储任意类型的数据
    slice1 := make([]interface{},0,10)
    slice1 = append(slice1,a1,a2,a3,a4)
    test2(slice1)
    fmt.Println(a1,a2,map1,slice1)
}

投票系统

var students[]student
var flag = true

type student struct {
    no int
    name string
    count int
}
//初始化接受参数
func initinfo(){
    sum := 0
    fmt.Println("请输入要参加的人数:")
    fmt.Scanln(&sum)
    students = make([] student,0,sum)
    for i:=1;i<=sum;i++{
        name := ""
        fmt.Printf("请输出第%d候选人的名字:\n",i)
        fmt.Scanln(&name)
        s := student{i,name,0}
        students = append(students,s)
    }
}
//排序
func printinfo(){
    for i := 1;i < len(students);i++{
        for j := 0;j < len(students)-i;j++{
            if students[j].count < students[j+1].count{
                students[j],students[j+1]=students[j+1],students[j]
            }
        }
    }
    for i:= 0;i<len(students);i++{
        s:=students[i]
        fmt.Printf("【%d】%s同学,当前票数为:%d\n",s.no,s.name,s.count)
    }
}
func getResult(){
    sum:=0
    highcount:= students[0].count
    for i := 0;i<len(students);i++{
        if students[i].count == highcount{
            sum ++
        }
    }
    if sum == 1 {
        fmt.Printf("%s同学,获取的票数最高为:%d,撒花留恋!",students[0].name,students[0].count)
        flag = false
    }else{
        for i :=0;i<sum;i++{
            fmt.Printf("%s同学,\t",students[i].name)
        }
        fmt.Printf("均获取最多的票数:%d,需要重新投票\n",highcount)
        students = students[:sum]
        for i:=0;i<len(students);i++{
            students[i].no = i+1
            students[i].count = 0
        }
    }
}

func vote(){
    for {
        fmt.Println("请输入要投的编号,当你输入0表示退出投票!")
        n := -1
        fmt.Scan(&n)
        if n == 0{
            fmt.Println("投票结束!")
            break
        }else if n>0 && n<= len(students){
            students[n-1].count = students[n-1].count + 1
        }else {
            fmt.Println("本次投票无效请重新投票!重新输入:")
        }
    }
}
func main(){
    //初始化Info
    initinfo()
    for flag{
        //显示信息
        printinfo()
        //投票
        vote()
        //计算票数
        printinfo()
        //处理结果
        getResult()
    }
}

发表评论

电子邮件地址不会被公开。 必填项已用*标注