c++的组合/继承与多态

版权声明:转载需注明出处,若有不足,欢迎指正。 https://blog.csdn.net/qq_28992301/article/details/53575023

c++的组合/继承与多态

类与类之间,存在组合关系与继承关系。组合关系是比较简单方便的,能用就用,别滥用继承关系

1.类的组合关系

  • 所谓组合关系,就是一个类中包含了其他类。具体的实现方法很简单,将其他类的对象作为当前类的成员使用,那么就构成了组合关系
class Computer //电脑类
{
    Memory mMem;  //内存类的对象
    Disk mDisk;   //硬盘类的对象
    CPU mCPU;   //cpu类的对象
    MainBoard mMainBoard;   //主板类的对象
public:
    Computer()
    {
    }
};
  • 这些对象仅仅就是普通的成员变量罢了,没什么值得注意的

2.类的继承关系

继承,是面向对象中代码复用的重要手段

继承关系的特性

  • 所谓继承关系,就是子类继承父类所有的特质(元素),同时可新增父类没有的特质(元素),也可重写继承得到的特质(元素)。继承关系有一些独特的性质:
    • 子类重写/新增的成员函数,无法直接访问继承到的成员变量。为了克服这一点,父类中一般不用private权限,而用protected权限。如下面,Dell中的test便可直接访问mv
    • 尽管子类在概念上是父类的子集,但是子类所含的特质(元素)是父类所含特质(元素)的超集,所以子类可以直接赋值给父类
class Computer 
{
protected:
    int mv;
public:
    Computer()
    {
        mv = 100;
    }
    void change()
    {
        mv = 100;
    }
};

class Dell : public Computer 
{
public:
    void test()   //这个成员函数是子类新增的
    {
        mv = 123;
    }
};

int main()
{   
    Dell dell;   
    Computer pc = dell;  //可以直接用子类初始化父类
    pc = dell;   //也可以用子类赋值父类
    return 0;
}
  • c++支持三种继承方式,上面代码中用的是最常见的public方式,其实在工程中一般只使用public继承,不推荐其他继承方法!!
    • public继承:父类成员在子类中保持原有的访问级别
    • private继承:父类所有成员在子类中变为private权限
    • protect继承:父类public成员在子类中变为public权限,其他成员保持不变

父子间的同名覆盖

  • 子类中可以定义父类中同名的成员变量,子类中的同名成员变量将覆盖(隐藏)父类成员变量,可以用父类名::成员变量名来获取被覆盖(隐藏)的父类成员
  • 子类中可以定义父类中同名的成员函数,不论参数是否相同,子类中的同名成员函数都会覆盖(隐藏)父类成员函数,可以用父类名::成员函数名来获取被覆盖(隐藏)的父类成员函数。不难得出结论,子类不能重载父类的成员函数
class Parent
{
public:
    int mi;
    void add(int v)
    {
        mi += v;
    }
};
class Child : public Parent
{
public:
    int mi;
    void add(int a, int b)
    {
        mi += (a + b);
    }
};

int main()
{
    Child c;
    c.mi = 100;    
    c.Parent::mi = 1000;//只能这样访问父类中的成员变量
    c.add(1);
    c.Parent::add(4, 5, 6);//只能这样访问父类中的函数
    return 0;
}

3.继承和组合类的构造与析构

当一个类是子类,同时它又包含了其他类,那么当它实例化时,如何初始化成员变量?

  • 先执行父类中的构造函数:若子类初始化列表中,调用了父类中的构造函数,则执行之;初始化列表中若未调用,则执行父类中显式定义的无参构造函数
  • 之后执行自己内部对象的构造函数
  • 最后执行子类自己的构造函数
  • 口诀:先父母,后客人,再自己
  • 当对象的声明周期结束时,析构函数的触发顺序和构造顺序完全相反,即:先自己,后客人,再父母

4.重写、多态、虚函数

多态的目的,其实是用来在子类中实现良好的、可靠的函数重写

  • 我们可以在子类中重写父类的成员函数,但是有时会发生一种子类退化的现象,那么这是重写就会失效
  • 子类退化:当我们用一个父类类型的指针/引用,去指向一个子类对象时,该指针和引用指向的将会是一个退化为父类的子类对象
  • 造成的结果就是,利用这种指针/引用,无法访问子类中独有的成员,只能访问父类中的成员,重写也会失效。根本原因是,编译器只能根据指针/引用的类型,来判断指向的对象的类型
class Parent
{
public:
    void add()
    {
       cout << "123" << endl;
    }
};
class Child : public Parent
{
public:
    void add()//重写过的函数
    {
       cout << "abc" << endl;
    }
};

int main()
{
    Child c;
    Parent *p = c;//父类型的指针指向了子类对象
    p ->add();//由于指针类型的关系,这里访问的是父类中原本的函数,访问不了子类重写过的函数
    return 0;
}
  • 由此,我们为了避免子类退化引起的重写失效,引入了多态。即使用virtual来修饰父类、子类中重写的函数,使其成为“虚函数”。这样,即使子类退化,仍然可以调用重写过的成员函数
class Parent
{
public:
    virtual void add()//理论上父类子类中重写的函数都要修饰,其实只需在父类中修饰即可
    {                 //因为virtual属性也会被继承给子类中的add函数
       cout << "123" << endl;
    }
};
  • 值得注意的是,构造函数不能成为虚函数!只有构造函数执行后才可建立虚函数表。不过作为补偿,编译器对构造函数还是实现了多态(详见第5节继承和组合类的构造与析构),也没必要设置为虚函数了。与之相反,析构函数一定要设置为虚函数来实现多态,否则可能会内存泄漏
  • 构造函数和析构函数本身可以多态,但是内部却不能发生多态行为(调用虚函数的效果仅仅为调用当前类中对应的那个成员函数),因为虚函数表的生命周期“始于构造,终于析构”
  • 所以,会被重写的函数以及析构函数,必须使用在父类中使用virtual修饰,以此让它们成为虚函数,以实现多态

多态的本质,就是动态链编:让程序在运行时才去确认函数的具体调用,而不是在编译期间就确认具体的函数调用

5.安全多重继承

c++中一个子类可以继承多个父类,这也是对现实世界的一种描述,然而直接使用多重继承是不可靠的,工程中不会使用纯粹的多重继承

  • 实际开发中,常常使用单继承多接口来实现多重继承
class Base
{
protected:
    int mi;
public:
    Base(int i)
    {
        mi = i;
    }
};

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

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

class Derived : public Base, public Interface1, public Interface2
{
public:
    Derived(int i) : Base(i)
    {  }
    void add(int i)
    {
        mi += i;
    }
    void multiply(int i)
    {
        mi *= i;
    }
};
  • 这种方法实现的多重继承,可以完美的支持多态和重写,

6.用继承实现抽象类

所谓抽象类,就是用类表达一种概念而非实体,比如定义一个电脑类

抽象类的概念

  • 抽象类有如下的特质
    • 抽象类作为父类而存在,只能被继承
    • 抽象类自己不能实例化,因为其实例化没有意义
    • 抽象类中,通常有些成员函数不提供具体实现
  • 一般来说,如果一个父类没有实例化的意义,那么我们就应该将其实现为抽象类

抽象类的实现方法

只可惜,c++中没有原生的抽象类,需要用纯虚函数来实现

  • 当虚函数没有具体实现,并且我们对其赋值为0时,编译器就会认为这个虚函数是纯虚函数(若不赋值为0,链接器将报错)
  • 将父类中的成员函数定义为纯虚函数(即函数没有具体的实现,只能由子类重写来实现),那么这个父类将无法被实例化,这样这个父类就成功变成抽象类了
class Shape
{
public:
    virtual double area() = 0;//纯虚函数,通过给虚函数赋值0来实现
};

接口的概念

所谓接口,就是一组行为的规范(一组函数集合)

  • c++中没有原生的接口,而是通过抽象类实现。接口是一种特殊的抽象类,它要满足:
    • 抽象类中没有任何成员变量,只有成员函数
    • 所有的成员函数都是public的、都是纯虚函数
  • 如下,就实现了一个接口,该接口可以被继承为串口、usb、蓝牙等等具体的通信方式
class Channel
{
public:
    virtual bool open() = 0;
    virtual void close() = 0;
    virtual bool send(char* buf, int len) = 0;
    virtual int receive(char* buf, int len) = 0;
};

猜你喜欢

转载自blog.csdn.net/qq_28992301/article/details/53575023