继承的相关知识

来源:互联网 发布:linux fm驱动 编辑:程序博客网 时间:2024/06/01 09:43

1. 继承的概念

  1. 定义:继承是面向对象复用的重要手段,即面向对象的可重用性是通过“继承”来实现的。解决在已有的类中增加新的特性,减少重复的工作量的问题。
  2. 已经存在的类,叫“基类”或“父类”;建立的新的类,叫“派生类”或“子类”。继承是类型之间的关系模型,共享公有的东西,实现类内各自不同的东西。
  3. 继承的定义格式为:
    class DeriveLcassName(派生类):acess-label(继承类型)BaseClassName,其中冒号后面的统称为派生类列表。
  4. 继承的分类:
    继承分为单继承和多继承。
    (1) 单继承是一个派生类只从一个基类派生,称为单继承。
    示意如下:
    这里写图片描述
    图中,箭头表示继承的方向,从派生类继承于基类。
    下面是简单的单继承代码:
class Person{public:    void Display()    {        cout<<_name<<endl;    }protected:  string _name;   //姓名private:    int _age;};class Student: public Person{protected:    int _num;    //学号};void Test(){    Person p;    Student s;    //1.子类给父类    p = s;    //2.父类给子类    // s = p;    //3.父类通过指针/引用给子类    Person *p1 = &s;    //Person &p1 = s;    //4. 子类的指针/引用不能指向父类对象(可以通过强制转换完成)    Student *p2 = (Student *)&p;    //Student &s = (Student &)p;    //p2->_num = 10;//不可访问    //p2._num = 10;}

(2)多继承:是一个派生类有两个或多个基类,称为多重继承。
简单示意如下:

这里写图片描述

简单的对多继承的理解,代码如下:

class Person{public:    friend void Display(Person &p,Student &s);    void buy()   //虚函数    {        cout<<"买东西"<<endl;    }   protected:    string _name;};class Student:public Person{public:    //virtual//    void display_1()    {        cout<<"买半价"<<endl;        cout<<Person::_name<<endl;    }protected :    int _strNum;  protected:    //int _num;};void Display(Person &p,Student &s){    cout<<p._name<<endl;    cout<<s._strNum<<endl;}void Fun(){    Person p;    Student s;    Display (p, s);}int main(){    Person p;    Student s;    //cout<<p._name<<endl;    //Fun(s);    return 0;}

2. 继承中的规则

  1. 继承关系中构造、析构函数调用顺序
    (1)构造函数调用顺序
    这里写图片描述
    (2)析构函数调用顺序
    这里写图片描述
  2. 继承与转换–赋值兼容规则
    (1)子类对象可以全部赋值给父类;
    (2)父类对象不能赋值给子类对象;
    (3)父类的指针或引用可赋值给子类对象;
    (4)子类对象的指针或引用不能赋值给父类对象,但可通过强制类型转换完成。

3. 菱形继承

菱形继承是由单继承和多继承组合的一种复杂的继承关系,存在二义性和数据冗余的问题。而菱形虚拟继承则是为了解决菱形继承所存在的问题。菱形继承的简单示意图如下:

这里写图片描述
其中派生类D既继承于C1,又继承于C2,通过派生类调用基类中的成员或成员函数时会产生二义性的问题
模型如下图:
这里写图片描述
简单模拟代码如下:

//菱形继承#include<iostream>using namespace std;class B{public:    int _b;    int _b1;};class c1:public B{public:    int _c1;    int _c;};class c2:public B{public:    int _c2;};class D:public c1,public c2{public:    int _d;    int _d1;};int main(){    D d1;    d1._d = 1;    d1._d1 = 4;//  d1._b1;            //这里不能通过编译,现象对D::_b1访问不明确,出现模糊调用的情况    d1.B::_b1 = 3;    //这里能通过编译,但仍会显示对基类“B”访问不明确    return 0;}

在对基类中的成员变量访问时,必须使用域访问限定符,否则编译器无法识别是哪个对象中的_b1赋值,虽然解决了二义性问题,但是产生了数据冗余.
为了解决菱形继承产生的二义性和数据冗余的问题,采用了菱形虚拟继承的方法,现对菱形虚拟继承概括:

4 菱形虚拟继承

  1. 定义:菱形虚拟继承是在继承类型前加入虚拟(virtual)关键字,且使用的是偏移量表格。
  2. 同名隐藏规则:
    虚基类是只有一个拷贝;
    作用分辨符(::)的访问方式:基类::成员(参数)/成员
    同名隐藏规则:没有虚函数的情况下,派生类中新增的与基类中的函数同名的函数,即使参数个数不同,则基类中的同名函数被覆盖;如果派生类中的多个基类拥有同名成员函数,派生类也新增了同名函数,则基类都被隐藏(这种情况,对派生类的访问用对象名。成员名,对基类的用作用分辨符)。
    1. 菱形虚拟继承关系:
      继承关系如下图所示:
      这里写图片描述

菱形虚拟继承模型如下:
这里写图片描述
如图所示:
Address1,Address2这里均存的是指针,指针指向偏移量表格。偏移量表格中先存该派生类相对于自己的地址,后存该派生类相对于基类的地址。
代码及分析如下:

 //菱形虚拟继承#include<iostream>using namespace std;class B{public:    int _b;    int _b1;};class C1:virtual public B{public:    int _cc;    int _c;};class C2:virtual public B{public:    int _c2;};class D:public C1,public C2{public:    int _d;    int _d1;};void test(){    D d1;    d1.C1::_b = 2;    d1.C2::_b = 7;    d1._b1 = 3;    d1._d = 1;    d1._d1 = 4;}int main(){    test();    return 0;}   

在调试过程中,先看看d1中存了些什么:
这里写图片描述
能看出d1存储了指针变量的地址,指针变量的内容是指向虚基类的地址,是4个字节,然后再看看这个地址中存放了什么。

这里写图片描述
1c转换成十进制是28,32位平台下一个地址是4个字节,因此,指向的是继承关系的模型中的_d1
好了,基本的先总结到这里~

原创粉丝点击