24.C++- 抽象类(存虚函数)、接口、多重继承

抽象类和接口

什么是抽象类

  • 用来表示现实世界中的抽象概念
  • 是一种只能定义类型,而不能产生对象的类
  • 只能被子类继承,且抽象类的相关成员函数没有完整的体现,用来被子类重写.

比如图形(Shape)类, 就是一个抽象的概念,因为我们无法计算这个“图形”的面积,所以它的成员函数area()是空的。

而继承它的子类(矩形,圆形,三角形等)就可以去重写area()成员函数. 里面通过数学公式,计算出面积.

参考图形(Shape)类,代码如下:

class Shape
{
public:
       double area()
    {
         return 0;
    }
};

既然Shape是个抽象的类,那就根本没有该类的对象,我们该如何避免他人使用Shape类创建对象呢?

答:

在C++中,通过纯虚函数来避免 

  • 纯虚函数只需要声明函数名,不用实现函数内容.通过子类去实现
  • 当类中有纯虚函数时,该类就无法创建对象,因为纯虚函数里没有具体内容,所以这个类便成为了抽象类.
  • 如果子类没有实现存虚函数,则子类也会成为抽象类

纯虚函数

纯虚函数需要在声明函数名前面加上virtual,在最后面加个=0;

比如:

class Shape
{
public:
       virtual double area()=0;               //不需要实现函数内容
};

接口

当类满足下面条件,则称为接口

  • 类中没有定义任何成员变量
  • 所有的成员函数都是公有的,并且都是纯虚函数
  • 接口是一种特殊的抽象类

举个例子

比如我们的蓝牙,可以打开,关闭,收发数据

网卡也一样,可以打开,关闭,收发数据.

类似的还有串口等等

这些类都拥有同样的行为,只是内容不同,所以它们的父类Channel只需要构造纯虚函数,所以便被称为接口,该父类代码如下:

class Channel{
public
       virtual bool open()=0;
       virtual bool close()=0;
       virtual bool send(char* buf,int len)=0;
       virtual bool recv(char* buf,int len)=0;
};

多重继承

  • 一个类可以继承于多个父类
  • 子类拥有所有父类的成员变量和函数
  • 子类对象可以当做任意父类对象使用

例如:

class Derived : public BaseA,
                public BaseB,
                public BaseC
{  
       //... ...
}

多重继承的问题1

多个不同的父类指针指向同一个多重继承的子类时,可能拥有不同地址

比如:

#include <iostream> 

using namespace std;

class BaseA
{
    int ma;
public:
    BaseA(int a)
   {
        ma = a;
   }
    int getA()
   {
        return ma;
   }
};

class BaseB
{
    int mb;
public:
    BaseB(int b)
   {
        mb = b;
   }
    int getB()
   {
        return mb;
   }
};

class Derived : public BaseA, public BaseB
{
    int mc;
public:
     Derived(int a, int b, int c) : BaseA(a), BaseB(b)
    {
        mc = c;
    }
};

int main()
{ 
    Derived d(1, 2, 3);
    BaseA* pa = &d;
    BaseB* pb = &d;

    if((void *)pa==(void *)pb)
   {
       cout<<"true"<<endl;
   }
    else
   {
       cout<<"false"<<endl;
   }    

     cout << "&d= " << &d  << endl;
     cout << "pa= " << pa  << endl;
     cout << "pb= " << pb << endl;
}

运行打印:

false
&d= 0x28fefc
pa= 0x28fefc
pb= 0x28ff00

为什么,pa指针和pb指针都指向d对象,它们的地址却有所不同?

这是因为Derived d对象地址里依次存了两个不同的父类成员变量值,如下图所示:

从上图看到,其实pa和pb还是位于d对象地址里,只是指向的位置不同而已.所以在多重继承里,最好不要使用等号直接判断两个指针对象是否相等.

多重继承的问题2

多重继承可能产生冗余的成员

比如:

老师teacher类,学生student类都继承于people类

有些老师,为了工作还要考博士学位,既是老师又是学生,所以同时继承于老师teacher类,学生student类,则该类的成员便会拥有两个people类成员,从而产生冗余

在工程中,如何正确使用多重继承

  • 只继承一个父类多个接口
  • 由于接口只有存虚函数,从而避免了冗余的成员
  • 在父类中提供equal()成员函数,
  • 通过equal()成员函数来判断指针是否指向当前对象,使用dynamic_cast强制转换 

例如:

#include <iostream>

using namespace std;

class Base
{
protected:
    int mi;
public:
     Base(int i)
    {
        mi = i;
    }

     int getI()
    {
        return mi;
    }

     bool equal(Base* obj)
    {
        return (this == obj);
    }
};

class Interface1
{
public:
    virtual void add(int i) = 0;
    virtual void minus(int i) = 0;
};

class Interface2
{
public:
    virtual void multiply(int i) = 0;
    virtual void divide(int i) = 0;
};

class Derived : public Base, public Interface1, public Interface2
{
public:
     Derived(int i) : Base(i)
    {
    }
     void add(int i)
    {
        mi += i;
    }
     void minus(int i)
    {
        mi -= i;
    }
     void multiply(int i)
    {
        mi *= i;
    }
    void divide(int i)
    {
         if( i != 0 )
        {
            mi /= i;
        }
    }
};

int main()
{
    Derived d(100);
    Derived* p = &d;
    Interface1* pInt1 = &d;
    Interface2* pInt2 = &d;

    cout << "p->getI() = " << p->getI() << endl;    // 100
    pInt1->add(10);
    pInt2->divide(11);
    pInt1->minus(5);
    pInt2->multiply(8);

    cout << "p->getI() = " << p->getI() << endl;    // 40

    cout << "pInt1 == p : " << p->equal(dynamic_cast<Base*>(pInt1)) << endl;    
    cout << "pInt2 == p : " << p->equal(dynamic_cast<Base*>(pInt2)) << endl;
    cout << "&d == p : " << p->equal(dynamic_cast<Base*>(&d)) << endl;

    return 0;
}

运行打印:

p->getI() = 100
p->getI() = 40
pInt1 == p : 1
pInt2 == p : 1
&d== p : 1

可以发现,使用dynamic_cast转换,判断出来的地址就是相等的.

p->equal(dynamic_cast<Base*>(pInt1))为例,我们编译时,编译器就会去检查pInt1所在的地址,然后找到是d对象,通过d对象找到Base父类,从而去修正pInt1指针的地址.

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏数据结构与算法

17:字符串判等

17:字符串判等 总时间限制: 1000ms 内存限制: 65536kB描述 判断两个由大小写字母和空格组成的字符串在忽略大小写,且忽略空格后是否相等。 ...

3759
来自专栏about云

spark开发基础之Scala详解apply方法

问题导读 1.什么情况下调用apply方法? 2.apply你认为有哪些作用? 3.方法调用属于apply什么功能? apply网上很多资料,但是总感觉总...

3776
来自专栏小樱的经验随笔

析构函数的用法【简单理论讲解】

析构函数是“反向”的构造函数。它们在对象被撤消(回收)时调用。析构函数的名 称除了最前面的“~”符号外,与类的名称相同。例如,类String的析构函数是~str...

28911
来自专栏desperate633

LeetCode 115. Distinct Subsequences题目分析代码

给出字符串S和字符串T,计算S的不同的子序列中T出现的个数。 子序列字符串是原始字符串通过删除一些(或零个)产生的一个新的字符串,并且对剩下的字符的相对位置没...

772
来自专栏cloudskyme

memset,memcpy,strcpy 的区别

一.函数原型    strcpy    extern char *strcpy(char *dest,char *src);    #include <stri...

40112
来自专栏技术专栏

Scala入门与进阶(四)- Scala面向对象

1062
来自专栏我和PYTHON有个约会

19. 再说函数~那些不得不知道的事儿

前面的课程中,我们已经对函数有了简单的了解 函数的声明、函数的的调用、函数的参数以及返回值等等

793
来自专栏一个爱吃西瓜的程序员

每天学习一点儿算法--递归

递归是很多算法都使用的一种编程方法。听说递归是一种十分优雅的问题解决办法,可是对于初涉递归的我,还没有形成这种独特的体会。 学习使用递归的关键在于:如何将问题分...

3098
来自专栏Phoenix的Android之旅

Java 强/弱/软引用

来排列。在JVM运行内存不足时,这三种之中最先被回收的是 弱引用,依次到最后才是强引用(不会被回收)。 但是对于强引用来说,JVM在内存不足时宁可抛出 OOM,...

1072
来自专栏夏时

PHP 特色:可变变量

1434

扫码关注云+社区

领取腾讯云代金券