继承与静态成员变量
基类定义了static静态成员,则整个继承体系⾥⾯只有⼀个这样的成员。⽆论派⽣出多少个派⽣类,都只有⼀个static成员实例。
cpp
class Person
{
public:
string _name;
static int _count;
};
int Person::_count = 0;
class Student : public Person
{
protected:
int _stuNum;
};
int main()
{
Person p;
Student s;
// 这⾥的运⾏结果可以看到⾮静态成员 _name 的地址是不⼀样的
// 说明派⽣类继承下来了,⽗派⽣类对象各有⼀份
cout << &p._name << endl;
cout << &s._name << endl;
// 这⾥的运⾏结果可以看到静态成员 _count 的地址是⼀样的
// 说明派⽣类和基类共⽤同⼀份静态成员
cout << &p._count << endl;
cout << &s._count << endl;
// 公有的情况下,⽗派⽣类指定类域都可以访问静态成员
cout << Person::_count << endl;
cout << Student::_count << endl;
return 0;
}

可以看到基类中定义的static成员变量在基类自己那和派生类都是同一个地址,而指定类域对象count值和指定派生类类域count值相同,能证明static静态成员变量在基类和派生类都是公用一个地址
多继承
单继承:⼀个派⽣类只有⼀个直接基类时称这个继承关系为单继承
多继承:⼀个派⽣类有两个或以上直接基类时称这个继承关系为多继承,多继承对象在内存中的模型
是,先继承的基类在前⾯,后⾯继承的基类在后⾯,派⽣类成员在放到最后⾯。
cpp
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; // 主修课程
};
int main()
{
// 编译报错:error C2385: 对"_name"的访问不明确
Assistant a;
a._name = "peter";
// 需要显⽰指定访问哪个基类的成员可以解决⼆义性问题,但是数据冗余问题⽆法解决
a.Student::_name = "xxx";
a.Teacher::_name = "yyy";
return 0;
}
多继承的语法就如上图的代码,派生类的基类也会有它的基类,上面的意思是,我假设为博士生,我可能是某科院长的学生,我还可以是某主修课程的老师,我这两个身份的我的真实名字是我这两个身份公共的,所以我定义一个Person作为它们的基类
然后你照着写的话然后调试窗口看一下创建的对象,会发现同一个对象有两个Person类,一个是student中的,一个是teacher中的,我不管是什么身份我的名字都是同一个,所以这就导致数据冗余
还有你如果这样写:
cpp
Assistant a;
a._name = "peter";
会报错,编译器会报不知道指定哪一个_name,因为你Assistant中继承的student和teacher中都有继承Person的_name,所以编译器才会报这个错误,这就是二义性
所以在这里可以把name定义为Assistant的私有成员,只是这样违背了实际生活
这种情况是多继承的特殊情况:菱形继承
菱形继承
菱形继承:菱形继承是多继承的⼀种特殊情况。菱形继承的问题,从下⾯的对象成员模型构造,可以
看出菱形继承有数据冗余和⼆义性的问题,在Assistant的对象中Person成员会有两份。⽀持多继承就⼀定会有菱形继承,像Java就直接不⽀持多继承,规避掉了这⾥的问题,所以实践中我们也是不建议设计出菱形继承这样的模型的所以要解决数据冗余和二义性的话就要引出下面的知识: 虚继承
虚继承
很多⼈说C++语法复杂,其实多继承就是⼀个体现。有了多继承,就存在菱形继承,有了菱形继承就有菱形虚拟继承,底层实现就很复杂,性能也会有⼀些损失,所以最好不要设计出菱形继承。多继承可以认为是C++的缺陷之⼀,后来的⼀些编程语⾔都没有多继承,如Java。
语法就是在会产生冗余的类加上virtual,比如在上面案例中,Student和Teacher里都有Person的成员,那就在它们继承Person中加vitrual
cpp
class Person
{
public:
Person(const char* name)
:_name(name)
{ }
string _name; // 姓名
};
class Student :virtual public Person
{
public:
Student(const char* name,int num)
:Person(name)
,_num(num)
{ }
protected:
int _num; //学号
};
class Teacher : virtual public Person
{
public:
Teacher(const char* name,int id)
:Person(name)
,_id(id)
{ }
protected:
int _id; // 职⼯编号
};
只看创建类的名称那,就是增加了virtual,里面的构造和单继承是一样的
那Assistant的构造有点不一样我先写出来引出个题来,然后再解答
a对象的名字最终是什么?
cpp
class Assistant : public Student, public Teacher
{
public:
Assistant(const char* name1,const char* name2,const char* name3,int num,int id,const char* majorCourse)
:Person(name3)
,Student(name2, num)
,Teacher(name1, id)
,_majorCourse(majorCourse)
{ }
protected:
string _majorCourse; // 主修课程
};
int main()
{
Assistant a("张三","李四","王五",25565,1005,"计算机工程");
这里的答案是名字最后是王五
原因: 这里编译器做了特殊处理在构造函数中虽然你写了派生类Assistant在各个基类中构造基类它们自己的成员,但是它不会走它们构造的Person()这个初始化列表中的,只会走student和teacher它的基类Person中的构造去构造name,这也是为什么在Assistant中多了额外要写的Person
cpp
a._name = "peter";
a.Student::_name = "xxx";
a.Teacher::_name = "yyy";

而且这里改的话在调试窗口中student和teachet中的name都会改,就证明虚继承中是公用一个继承的基类成员,
所以虚继承的特点:
1.在这里的Assistant中的student和teacher中的Person是指针偏移量或指针(具体看编译器如何处理)
2. 如果是student和teachet的对象,那构造函数会正常走它的基类的整体构造后面我再写的多态会很好体现

