继承与虚继承

1:继承
(1).一个简单继承实现
#include<iostream>
using namespace std;
class Person
{
public:
    String _name;
};
class Teacher :: public Person
{
public:
    int _id;
};
int main()
{
    return 0;
}

其中,Person 称为父类(或基类);Teacher 称为子类(或派生类)
“class Teacher :: public Person” 中public 为继承关系
(2).三种继承关系
继承与虚继承
简而言之就是:不论是继承方式还是基类的类成员访问限定符  两者取其范围小的那一种限定符修饰子类。

注意:
1.使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,但是最好显示的写出继承方式。
2. 不管是哪种继承方式,在子类中都可以访问父类的公有成员和保护成员,但是父类的私有成员存在但是在子类中不可见(不能
访问)。
3.public继承是一个接口继承,保持is-a原则,每个父类可用的成员对子类也可用,因为每个子类对象也都是一个父类对象。

(3)public继承--赋值兼容规则

#include<iostream>
using namespace std;
class Person
{
public:
    void Display()
    {
         cout<<_name <<endl;
    }
protected :
    string _name ; // 姓名
};
class Student : public Person
{
public:
    int _num ; // 学号
};

i.子类对象可以赋值给父类对象(切片/切割),父类对象不能赋值给子类对象
void Test ()
{
    Person p ;
    Students;
    p = s;//子类对象赋值给父类对象
    s = p;//父类对象赋值给子类对象  错的!!!
}
ii.子类对象的指针或引用可以赋值给父类对象的指针或引用
void Test ()
{
    Person p ;
    Students;

    Person* p1 = &s;
    Person& r1 = s;

}
iii.父类对象的指针或引用 不可以 赋值给子类对象的指针或引用(除非进行类型强转)

void Test ()
{
    Person p ;
    Students;

           Student* p2 = (Student*)&p;
    Student&r2 = (Student&) p;

}

(4)隐藏(重定义)
    定义:子类和父类中有同名成员,子类成员将屏蔽父类对成员的直接访问。(在子类成员函数中,可以使用基类 :: 基类成员访问)
注意在实际中在继承体系里面最好不要定义同名的成员

2.派生类默认成员函数
   在继承关系里面,在派生类中如果没有显示定义这六个成员函数,编译系统则会默认合成这六个默认的成员函数。
#include<iostream>
using namespace std;
#include<string>
class Person
{
public:
    Person(const char*name = " ")
         :_name(name)
    {
         cout<<"Person(const char*name = " ")"<<endl;
    }
    Person(const Person& p)
         :_name(p._name)
    {
         cout<<"Person(const Person& p)"<<endl;
    }
    Person& operator=(const Person& p)
    {
         cout<<"Person& operator=(const Person& p))"<<endl;
         if(this != &p)
         {
             _name = p._name;
         }
         return *this;
    }
    ~Person()
    {
         cout<<"~Person()"<<endl;
    }
protected :
    string _name ; // 姓名
};
class Student : public Person
{
public:
    Student(const char*name = " ",int num = 0)
         :Person(name)
         ,_num(num)
    {
         cout<<"Student(const char*name = " ",int num = 0)"<<endl;
    }
    Student(const Student& s)
         :Person(s)
         ,_num(s._num)
    {
         cout<<"Student(const Student& s))"<<endl;
    }
    Student& operator=(const Student& s)
    {
         cout<<"Student& operator=(const Student& s)"<<endl;
         if(this != &s)
         {
             Person::operator =(s);
             _num = s._num;
         }
         return *this;
    }
    
    ~Student()
    {
         cout<<"~Student()"<<endl;
    }
    void Display()
    {
         cout<<"姓名:"<<_name<<endl;
         cout<<"学号:"<<_num<<endl;
    }
protected:
    int _num ; // 学号
};
void Test()
{
    Student s1("noora",1);
    Student s2(s1);
    Student s3("Joe",2);
    s2 = s3;
    s1.Display();
    s2.Display();
    s3.Display();
}

3.单继承&多重继承
(1). 单继承--一个子类只有一个直接父类时称这个继承关系为单继承
继承与虚继承
(2).多继承--一个子类有两个或以上直接父类时称这个继承关系为多继承
继承与虚继承
(3)菱形继承
继承与虚继承
 菱形继承存在的问题:二义性 和 数据冗余
#include<iostream>
using namespace std;
#include<string>
class Person
{
public:
    string _name; //姓名
};
class Student : public Person
{
protected:
    int _num;//学号
};
class Teacher : public Person
{
protected:
    int _id;//职工编号
};
class Assistant : public Student, public Teacher
{
protected:
    string _majorCourse;//主修课程
};
void Test()
{
    Assistant a;
           //显示指定访问哪个父类的成员
    a.Student::_name = "Tom";
    a.Teacher::_name = "Jerry";
}
int main()
{
    Test();
    return 0;
}

继承与虚继承
对于子类Assistant而言, name存在两种,一个是Student类中的name ,一个是Teacher类中的name.
但是实际上只需要一个name 就可以,两个name既导致了二义性,也导致了数据冗余。

如何解决二义性和数据冗余?————虚继承
    继承与虚继承

在所要继承的共同基类前加 virtual 将共同基类设置为虚基类,这时从不同的路径继承过来的同名数据成员在内存中就只有一个拷贝,
同一个函数名也只有一个映射。
继承与虚继承  继承与虚继承

Student,Teacher中都分别包含了一个指向Person的虚基类指针列表vbptr(virtual base table pointer)(虚基表指针),其中记录的是从Student,Teacher的vbtable的首地址(vbptr)到Person的元素之间的偏移量