继承:单继承、派生类成员的访问属性、多继承、菱形继承、虚继承,菱形虚拟继承

1,派生类的声明方式

class Student
{
public:
    void display()
    {
        cout << "num:" << num << endl;
        cout << "name:" << name << endl;
        cout << "sex:" << sex << endl;
    }
private:
    int num;
    char name[10];
    char sex;
};

class Student1 : public Student//声明派生类
{
public:
    void display_1()
    {
        cout << "age:" << endl;
        cout << "address:" << addr << endl;
    }
private:
    int age;
    char addr[10];
};

2,派生类成员的访问属性
(1)public(公用继承)

class student
{
public:
    void getvalue()
    {
        cin >> num >> name >> sex;
    }
    void display()
    {
        cout << "num:" << num << endl;
        cout << "name::" << name << endl;
        cout << "sex:" << sex << endl;
    }
private:
    int num;
    char name[10];
    char sex[5];
};

class student1:public student
{
public:
    void getvalue_1()
    {
        getvalue();
        cin >> age>>addr;
    }
    void display_1()
    {
        display();//调用基类公有成员函数来访问基类私有成员变量
        cout << "num:" << num << endl;//报错,基类私有成员不可访问
        cout << "age:" << age << endl;
        cout << "addr:" << addr << endl;
    }
    int age;
    char addr[10];
};

int main()
{
    Student1 stud;
    stud.getvalue_1();
    stud.display_1();
    system("pause\n");
    return 0;
}

下面用一张表来说明公用基类的成员在派生类中的访问属性
继承:单继承、派生类成员的访问属性、多继承、菱形继承、虚继承,菱形虚拟继承_第1张图片

(2)private(私有继承)
将上面的程序改为私有继承

class student
{
public:
    void getvalue()
    {
        cin >> num >> name >> sex;
    }
    void display()
    {
        cout << "num:" << num << endl;
        cout << "name::" << name << endl;
        cout << "sex:" << sex << endl;
    }
private:
    int num;
    char name[10];
    char sex[5];
};

class Student1 :private student
{
public:
    void getvalue_1()
    {
        getvalue();
        cin >> age >> addr;
    }
    void display_1()
    {
        display();
        //cout << "num:" << num << endl;
        cout << "age:" << age << endl;
        cout << "addr:" << addr << endl;
    }
private:
    int age;
    char addr[10];
};

int main()
{
    Student1 stud;
    //stud.getvalue();//报错,私有基类的公用成员函数在外界不可访问
    stud.display_1();
    //stud.age = 18;//错误,外界无法访问派生类的私有成员
    system("pause\n");
    return 0;

}

结论:(1)不能通过派生类对象引用从私有基类继承过来的任何成员
(2)派生类的成员函数不能访问私有基类的私有成员,但可以访问私有类的公用成员函数
继承:单继承、派生类成员的访问属性、多继承、菱形继承、虚继承,菱形虚拟继承_第2张图片

(3)protected(保护继承)

class student
{
private:
    void display();

protected:
    int num;
    char name[10];
    char sex[5];
};

class Student1 :protected student
{
public:
    void getvalue_1()
    {
        cin >> num >> name >> sex;
        cin >> age >> addr;
    }
    void display_1()
    {
        cout << "num:" << num << endl;
        cout << "name:" << num << endl;
        cout << "sex:" << num << endl;
        cout << "age:" << age << endl;
        cout << "addr:" << addr << endl;
    }
private:
    int age;
    char addr[10];
};

int main()
{
    Student1 stud;
    stud.display_1();
    //stud.display();//报错,保护基类的公用成员在外界不可访问
    stud.display_1();
    //stud.age = 18;//报错,基类的保护成员对派生类的外界来说是不可访问的
    system("pause\n");
    return 0;

}

保护基类的公有成员和保护成员在派生类中都成了保护成员,其私有成员仍为基类私有,也就是把基类原有的公有成员也保护起来,不让类外任意访问。
3,多级派生时的访问属性

class A
{
public:
    int i;
protected:
    void f1(){}
    int j;
private:
    int k;
};

class B :public A
{
public:
    void f2(){}
protected:
    void f3(){}
private:
    int m;
};

class C :protected B
{
public:
    void f4();
private:
    int n;
};

int main()
{
    C c;
    B b;
    b.i = 10;//可访问
    //c.f2();//不可访问
}

各成员在不同类中的访问属性
继承:单继承、派生类成员的访问属性、多继承、菱形继承、虚继承,菱形虚拟继承_第3张图片
无论哪一种继承方式,在派生类中是不能访问基类的私有成员的,私有成员只能被本类的成员函数访问。
4,派生类的构造函数和析构函数
先定义一个简单的派生类的构造函数

class Student
{
public:
    Student(int n, string nam, char s)//定义基类构造函数
        :num(n), name(nam), sex(s)
    {
        cout << "Student()" << endl;
    }
    ~Student()
    {
        cout << "~Student()" << endl;
    }
protected:
    int num;
    string name;
    char sex;
};

class Student1 :public Student
{
public:
    Student1(int n, string nam, char s, int a, string add)//定义派生类构造函数
        :Student(n, nam, s), age(a), addr(add)//在初始化列表初始化
    {
        cout << "Student1()"<void show()
    {
        cout << "num:" << num << endl;
        cout << "name:" << name << endl;
        cout << "sex:" << sex << endl;
        cout << "age:" << age << endl;
        cout << "add:" << addr << endl;
    }
    ~Student1()
    {
        cout << "~Student1()" << endl;
    }
private:
    int age;
    string addr;
};

int main()
{
    Student1 stud1(1000, "wangli", 'n', 19, "shanxikejidaxue");
    Student1 stud2(1001, "liuyulin", 'n', 18, "xibeihzengfadaxue");
    stud1.show();
    cout << endl;
    stud2.show();
    //system("pause\n");
    return 0;
}

分别在两个构造函数和两个析构函数处大断点,对程序进行调试,打印的结果如图
继承:单继承、派生类成员的访问属性、多继承、菱形继承、虚继承,菱形虚拟继承_第4张图片
从结果上看,程序是先调用了基类的构造函数,再调用派生类的构造函数,程序结束后,先析构派生类,再析构基类。
事实上,从程序的调试过程可以看出,程序是先调用派生类的构造函数,此时,执行调用基类构造函数完成对基类的初始化,然后再继续执行派生类构造函数的函数体。此处可上机实践进行验证。

若派生类中有子对象,则在派生类构造函数中对子对象进行初始化。因此,执行派生类构造函数的次序是
(1)调用基类构造函数,对基类数据成员初始化
(2)调用子对象构造函数,对子对象数据成员初始化
(3)再执行派生类构造函数本身,对派生类数据成员初始化

派生类是不能继承基类的析构函数的,也需要通过派生类的析构函数去调用基类的析构函数。
5,多重继承引起的二义性问题及同名覆盖

class A
{
public:
    int a;
    void display();
};
class B
{
public:
    int a;
    void display();
};
class C :public A, public B
{
public:
    int b;
    //void show();
};

int main()
{
    C c;
    c.a = 10;//报错
    c.display();//报错
}

编译系统无法判别要访问的是哪个基类的成员,编译出错,这就是多重继承存在的二义性问题。
如果将C类改为如下形式

class C :public A, public B
{
public:
    int a;
    void display();
};

int main()
{
    C c;
    c.a = 10;//编译通过
    c.display();//编译通过
}

此时编译通过,那么程序执行时访问的到底是哪一个类中的成员?
此时,访问的是C类的成员,规则:基类的同名成员在派生类中被屏蔽,成为“不可见”的,或者说,派生类新增加的同名成员覆盖了基类中的同名成员。
注意:不同的成员函数,只有在函数名和参数个数相同,类型匹配的情况下才发生同名覆盖
6,菱形继承及虚基类
菱形继承时多重继承中的一种继承方式,也存在二义性问题
继承:单继承、派生类成员的访问属性、多继承、菱形继承、虚继承,菱形虚拟继承_第5张图片
这样的继承方式成为菱形继承

class A
{
public:
    int data;
    void fun(){}
};

class B :public A
{
public:
    int data;
    void fun(){}
    int _b;
};

class C :public A
{
public:
    int data;
    void fun(){}
    int _c;
};

class D :public B, public C
{
public:
    int _d;
    void fun_d(){}
};

继承:单继承、派生类成员的访问属性、多继承、菱形继承、虚继承,菱形虚拟继承_第6张图片
从上图看出,不同类中相同的成员在D中产生了多份拷贝,不仅占用内存,而且对访问这些成员增加困难。
下面我们介绍虚基类
虚基类可使得在继承间接共同基类时只保留一份成员
将上面的代码改为

class A
{};
class B:virtual public A
{};
class C:virtual public A
{};

继承:单继承、派生类成员的访问属性、多继承、菱形继承、虚继承,菱形虚拟继承_第7张图片

之前学习继承多态的时候学的是似懂非懂的,所以这篇博客内容也只是一些概念性的东西,并未深入探索,再回过头学习时,以前模棱两可的东西突然就明白了一些,所以决定将这篇博客更新。

菱形继承中含有虚函数:

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

class B :public A
{
public:
    virtual void Fun1()
    {
        cout << "B::Fun1" << endl;
    }
    virtual void Fun3()
    {
        cout << "B::Fun3" << endl;
    }
    int _b;
};

class C :public A
{
public:
    virtual void Fun1()
    {
        cout << "C::Fun1()" << endl;
    }
    virtual void Fun4()
    {
        cout << "C::Fun4" << endl;
    }
    int _c;
};

class D :public B, public C
{
public:
    virtual void Fun1()
    {
        cout << "D::Fun1" << endl;
    }
    virtual void Fun5()
    {
        cout << "D::Fun5" << endl;
    }
    int _d;
};

//打印虚表
typedef void(*V_FUNC) ();

void PrintVTable(int* vtable)
{
    printf("vtable 0x%p\n", vtable);
    int** ppVtable = (int**)vtable;
    for (size_t i = 0; ppVtable[i] != 0; ++i)
    {
        printf("vatable[%u]:0x%p->", i, ppVtable[i]);
        V_FUNC f = (V_FUNC)ppVtable[i];
        f();
    }
    cout << "——————————————————————————————" << endl;
}

void Test1()
{
    D d;
    d.B::_a = 1;
    d._b = 2;
    d.C::_a = 3;
    d._c = 4;
    d._d = 5;

    PrintVTable(*((int**)&d));//第一个虚表B的
    PrintVTable(*((int**)((char*)&d + sizeof(B))));//C的虚表
}

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

我们通过内存窗口来查看D的虚表:
继承:单继承、派生类成员的访问属性、多继承、菱形继承、虚继承,菱形虚拟继承_第8张图片

可以证明,多继承时,子进程中新增的虚函数是写入了第一个继承的父类的虚表中。

菱形虚拟继承中含有虚函数:

class A
{
public:
    virtual void Fun1()//去掉Func1崩溃,因为BC是同级的。虚继承不明确
    {
        cout << "A::Fun1" << endl;
    }
    virtual void Fun2()
    {
        cout << "A::Fun2" << endl;
    }
    int _a;
};

class B :virtual public A
{
public:
    virtual void Fun1()
    {
        cout << "B::Fun1" << endl;
    }
    virtual void Fun3()
    {
        cout << "B::Fun3" << endl;
    }
    int _b;
};

class C :virtual public A
{
public:
    virtual void Fun1()
    {
        cout << "C::Fun1()" << endl;
    }
    virtual void Fun4()
    {
        cout << "C::Fun4" << endl;
    }
    int _c;
};

class D :public B, public C
{
public:
    virtual void Fun1()
    {
        cout << "D::Fun1" << endl;
    }
    virtual void Fun5()
    {
        cout << "D::Fun5" << endl;
    }
    int _d;
};

typedef void(*V_FUNC) ();

void PrintVTable(int* vtable)
{
    printf("vtable 0x%p\n", vtable);
    int** ppVtable = (int**)vtable;
    for (size_t i = 0; ppVtable[i] != 0; ++i)
    {
        printf("vatable[%u]:0x%p->", i, ppVtable[i]);
        V_FUNC f = (V_FUNC)ppVtable[i];
        f();
    }
    cout << "——————————————————————————————" << endl;
}

void Test1()
{
    D d;
    d.B::_a = 1;
    d._b = 2;
    d.C::_a = 3;
    d._c = 4;
    d._d = 5;


//(*(int**)&d)解引用二级指针,在32位和64位下均能正确使用

    PrintVTable(*((int**)&d));//第一个虚表B的
    PrintVTable(*((int**)((char*)&d + sizeof(B)-sizeof(A))));//C的虚表
    PrintVTable(*((int**)((char*)&d + sizeof(D)-sizeof(A))));//A的虚表
    //B的虚表里其实是含有A的
    cout << sizeof(A) << endl;
    cout << sizeof(B) << endl; 
    cout << sizeof(C) << endl;
    cout << sizeof(D) << endl;

    B b;
    b._a = 10;
    b._b = 11;
}

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

继承:单继承、派生类成员的访问属性、多继承、菱形继承、虚继承,菱形虚拟继承_第9张图片

通过上图可以看出,菱形虚拟继承中,为了避免二义性,公共父类A有单独的虚表,且图中有两张表,一个是虚基表,里面保存的是虚继承中子类的偏移量,而虚表中保存的是虚函数。
公共数据a在D对象的最底层,且B类和C类中均包含A类,所以的它们的大小为虚表指针+虚基表指针+各自数据大小+A类中公共数据大小。

你可能感兴趣的:(cpp)