浅谈继承和多态

继承

继承可以使现有的代码具有可重用性可扩展性。它可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。其继承的过程,就是从一般到特殊的过程。

通过继承创建的新类称为”子类”或”派生类”。被继承的类称为”基类”、”父类”或”超类”.C++类继承通过三个方式来实现,包括:公有继承(public),保护继承(protected),私有继承(private),它们的不同在哪里呢?

继承方式基类的public成员基类的protected成员基类的private成员继承引起的访问控制符变化
public继承仍为public成员仍为protected成员不可见基类的非私有成员在子类的访问属性都不变
protected继承变为protected成员变为protected成员不可见基类的非私有成员成为子类保护成员
private继承变为private成员变为private成员不可见基类的非私有成员成为子类私有成员

分析
1.基类的私有成员在派生类中不能被访问,如果一些基类成员不想被基类对象直接访问,但需要在派生类中能访问,就定义为保护成员,可以看出保护成员限定符是因继承才出现的
2.public继承是一个接口继承,每个父类可用的成员对子类也都是开放的,因此每个子类对象也都是一个父类对象
3.protected/private继承只是拥有基类的部分成员,因此在绝大多数情况下用的都是公有继承
4.无论何种继承方式,都可以访问基类的public成员和protect成员

为单继承和多继承

单继承:

class Base
{
public:
    void Print()
    {}
private:
    int _b;
};
class Derived:public Base
{
public:
    void Print()
    {}
private:
    int _d;
}

多继承:

class Base1
{
public:
    void Print()
    {}
private:
    int _b1;
};
class Base2
{
public:
    void Print()
    {}
private:
    int _b2;
};
class Derived:public Base1,public Base2
{
public:
    void Print()
    {}
private:
    int _d;
}

继承中的默认成员函数

在C++中类有六个默认构造函数,非常重要,我们来重点来看一下构造和析构函数。

class Base
{
public:
    Base()
    {
        cout << "Base()" << endl;
    }

    ~Base()
    {
        cout << "~Base()" << endl;
    }
private:
    int _b;
};
class Derived :public Base
{
public:
    Derived()
    {
        cout << "Derived()" << endl;
    }
    ~Derived()
    {
        cout << "~Derived()" << endl;
    }
private:
    int _d;
};
void test()
{
    Derived d;
}

int main()
{
    test();
    system("pause");
    return 0;
}

运行结果为:
Base()
Derived()
~Derived()
~Base()

由此得出,在创建子类对象是同时调用了子类和父类的构造函数,并且父类的构造函数先于子类构造函数,析构函数也同样调用了子类和父类。多继承同样如此,只是调用顺序需要按照继承顺序。

菱形继承

菱形继承就是通常所说的钻石继承

C1和C2都继承了Base,D又继承了C1和C2,这样继承有一个问题那就是数据冗余,C1里面有Base,C2里面也有Base,在D这里相当于有两份Base,并且当根据D所创建的对象,访问Base里的成员时候,存在二义性,是访问C1中的还是C2中的呢,为了解决这种多重继承提出了虚继承。

虚继承

虚继承 是面向对象编程中的一种技术,是指一个指定的基类,在继承体系结构中,将其成员数据实例共享给也从这个基类型直接或间接派生的其它类
虚基类:在虚继承体系中的通过virtual继承而来的基类
具体我们来看代码
1.菱形继承(类中有虚函数)


#include<iostream>
using namespace std;

class A
{
public:
    int _a;
    virtual void t()
    {
        cout << "A::t()" << endl;
    }
};

class B1 : public A
{
public:
    int _b1;
    virtual void b1()
    {
        cout << "B1::b1()" << endl;
    }
    void t()
    {
        cout << "B1::t()" << endl;
    }
};
class B2 : public A
{
public:
    int _b2;
    virtual void b2()
    {
        cout << "B2::b2()" << endl;
    }
    void t()
    {
        cout << "B2::t()" << endl;
    }
};
class D:public B1,public B2
{
public:
    int _d;
    void t()
    {
        cout << "D::t()" << endl;
    }
    void b1()
    {
        cout << "D::b1()" << endl;
    }
    void b2()
    {
        cout << "D::b2()" << endl;

    }
};

void test()
{
    D d;
    d.B1::_a = 1;
    d._b1 = 2;
    d.B2::_a = 3;

    d._b2 = 4;
    d._d = 5;
}

int main()
{
    test();
    system("pause");
    return 0;
}

这里写图片描述
由结果可知,D类自己新加入的虚函数存放在了B1(派生类的继承列表中的第一个基类)的虚表中,再将在D类中对B1类和B2类的某些虚函数进行重写,所以就用D类重写的虚函数的地址覆盖了基类B1和B2中的某些虚函数。
2.菱形虚拟继承 (D中没有特有的虚函数,只是重写了B1和B2类的某些虚函数)

#include<iostream>
using namespace std;
class A
{
public:
    int _a;
    virtual void t()
    {
        cout << "A::t()" << endl;
    }
};

class B1 :virtual public A
{
public:
    int _b1;
    virtual void b1()
    {
        cout << "B1::b1()" << endl;
    }
    void t()
    {
        cout << "B1::t()" << endl;
    }
};
class B2 :virtual public A
{
public:
    int _b2;
    virtual void b2()
    {
        cout << "B2::b2()" << endl;
    }
    void t()
    {
        cout << "B2::t()" << endl;
    }
};
class D:public B1,public B2
{
public:
    int _d;
    void t()
    {
        cout << "D::t()" << endl;
    }
    void b1()
    {
        cout << "D::b1()" << endl;
    }
    void b2()
    {
        cout << "D::b2()" << endl;

    }
};

void test()
{
    D d;
    d._a = 1;
    d._b1 = 2;

    d._b2 = 3;
    d._d = 4;
}

int main()
{
    test();
    system("pause");
    return 0;
}

这里写图片描述
注意 :这幅图中偏移量表地址这一项不存在,也就是只有B1类虚表地址 B1类成员变量,和B2类虚表地址,B2类成员变量,下面是正确的

由上图分析可知:将A类经过虚继承后只保存了一份,也就是说A类中的虚函数经过重写后成为所有类共有的, 而D将B1和B2的某些虚函数进行了重写。 可以看出,虚菱形继承增加了一个超类(A)。

易混淆概念解析
这里写图片描述

在讨论多态前,我们需要先对多态做一个分类
这里写图片描述

多态

多态可以简单的概括为”一个接口,多种方法”(接口重用),程序在运行时才决定调用的函数。C++的多态性是通过虚函数来实现的,虚函数允许子类重新定义成员函数,而子类重新定义父类的做法称为重写,多态和非多态的实质区别在于函数地址的早还是晚绑定,如果编译期间就可以确定的,是早绑定也就是静态的。而如果函数调用的地址不能在编译器期间确定,需要在运行时才确定,这就属于晚绑定。

#include<iostream>
using namespace std;
class Base
{
public:
    virtual void Print()
    {
        cout << "Base::Print()" << endl;
    }
};
class Derived:public Base
{
public:
    virtual void Print()
    {
        cout << "Derived::Print()" << endl;
    }
};
void Print(Base* p)
{
    p->Print();
}
void test()
{
    Base b;
    Derived d;
    Print(&b);
    Print(&d);
}

int main()
{
    test();
    system("pause");
    return 0;
}

运行结果:
这里写图片描述

原理解析:编译器在编译的时候,发现Base类中有虚函数,此时编译器会为每个包含虚函数的类创建一个虚表(即 vtable),该表是一个一维数组,在这个数组中存放每个虚函数的地址,既然有了虚表那该如何定位呢?

定位就需要说到虚表指针(即vptr),这个指针指向了对象所属类的虚表,在程序运行时,根据对象的类型去初始化vptr,从而让vptr正确的指向了所属类的虚表,从而在调用虚函数的时候,能够找到正确的函数,虚表指针是在什么时候,或者什么地方初始化呢?

答案是在构造函数中进行虚表的创建和虚表指针的初始化,在构造子类对象时,要先调用父类的构造函数,此时编译器只“看到了”父类,并不知道后面是否还有继承者,它初始化父类对象的虚表指针,该虚表指针指向父类的虚表,当执行子类的构造函数时,子类对象的虚表指针被初始化,指向自身的虚表
通过代码我们可以看出实现多态必须满足继承和在子类中重写父类的虚函数这两个条件,既然虚函数在多态中这么重要,那定义时有哪些需要注意的呢?

虚函数的定义遵循一下重要规则:
1.如果虚函数在基类与派生类中出现,仅仅是名字相同,而形式参数不同,或者是返回类型不同,那么即使加上了virtual关键字,也是不会进行滞后联编的。
2.只有类的成员函数才能说明为虚函数,因为虚函数仅适合用与有继承关系的类对象,所以普通函数不能说明为虚函数,虚函数在类外实现时,在类内声明必须加关键字virtual,类外不能加
3.静态成员不能是虚函数,因为静态成员函数的特点是不受限制于某个对象
4.内联函数不能是虚函数,因为内联函数不能再运行中动态确定位置,即使虚函数在类的内部定义,但是在编译的时候系统仍然将它看作是非内联的。
5.构造函数不能是虚函数,因为构造的时候吗,对象还没有初始化完成,只有构造完成后,对象才是具体的实例
6.析构函数通常声明为虚函数(编译器特殊处理,派生类和基类析构函数构成覆盖)
7.虚表可以继承,如果子类没有重写虚函数,那么子类虚表中仍然会有该函数的地址,只不过这个地址指向的是基类的虚函数实现,如果基类有3个虚函数,那么基类的虚表中就有三项(虚函数地址),派生类也会虚表,至少有三项,如果重写了相应的虚函数,那么虚表中的地址就会改变,指向自身的虚函数实现,如果派生类有自己的虚函数,那么虚表中就会添加该项。
8.派生类的虚表中虚地址的排列顺序和基类的虚表中虚函数地址排列顺序相同。


2018-4-23更新
友元关系:不可以继承,也就是基类的友元不可以访问子类的私有和保护成员

抽象类型:在成员函数的形参后面写上=0,则成员函数为纯虚函数。包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象。纯虚函数在派生类中重新定义以后,派生类才能实例化出对象。

继承与转换–赋值兼容规则–public继承:
1. 子类对象可以赋值给父类对象(切割/切片)
2. 父类对象不能赋值给子类对象
3. 父类的指针/引用可以指向子类对象
4. 子类的指针/引用不能指向父类对象(可以通过强制类型转换完成)

  • 2
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值