继承与友元
友元关系不能继承,也就是说基类友元不能访问子类私有和保护成员。
#include<iostream> using namespace std; class Student; class Person { public: friend void Display(const Person& p, const Student& s); protected: string _name; // 姓名 }; class Student : public Person { protected: int _stuNum; // 学号 }; void Display(const Person& p, const Student& s) { cout << p._name << endl; cout << s._stuNum << endl; } void main() { Person p; Student s; Display(p, s); }
继承与静态成员
基类定义了static静态成员,则整个继承体系里面只有一个这样的成员。无论派生出多少个子
类,都只有一个static成员实例 。
#include<iostream> using namespace std; class Person { public: Person() { ++_count; } protected: string _name; // 姓名 public: static int _count; // 统计人的个数。 }; int Person::_count = 0; class Student : public Person { protected: int _stuNum; // 学号 }; int main() { Person s1; Student s2; cout << s1._count << endl; cout << s2._count << endl; cout << &s1._count << endl; cout << &s2._count << endl; return 0; }
这是因为静态成员存储的区域不一样,是储存在静态区的。
这里还要注意一种情况:
这样子是可以的,因为虽然是*和->但是不会去真正的在这个对象里面找,因为静态区是不在这个对象中的,就像之前的成员函数一样,定义的内容也不是在对象内部,只是声明在类中,传递过去的也是空指针,并没有真正的去访问空指针。
其实这样子就相当于告诉你去哪个类里面找。
多继承
一个类继承了多个个类,这就是多继承。
菱形继承
最麻烦的就是这种菱形继承,因为数据会冗余。
#include<iostream> using namespace std; 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() { Assistant s; //s._name;//编译器不知道我们要访问哪个name,产生了二义性 s.Student::_name = "xxx"; s.Teacher::_name = "yyy"; return 0; }
虚继承
但是指定作用域去访问并没有彻底的解决这里的问题,所以有了一个关键字,virtual。
虚继承要在菱形继承中间的位置。
#include<iostream> using namespace std; class Person { public: string _name; // 姓名 }; class Student : virtual public Person { protected: int _num; //学号 }; class Teacher : virtual public Person { protected: int _id; // 职工编号 }; class Assistant : public Student, public Teacher { protected: string _majorCourse; // 主修课程 }; int main() { Assistant s; s._name = "xxx";//编译器不知道我们要访问哪个name //s.Student::_name = "xxx"; //s.Teacher::_name = "yyy"; return 0; }
现在这三个域中的name就都是同一个name了。
虚继承的原理
先来看看普通类,因为编译器内部做了处理,这里只能用内存窗口去观察了。
#include<iostream> using namespace std; class A { public: int _a; }; class B : public A //class B : virtual public A { public: int _b; }; class C : public A //class C : virtual public A { public: int _c; }; class D : public B, public C { public: int _d; }; int main() { D d; d.B::_a = 1; d.C::_a = 2; d._b = 3; d._c = 4; d._d = 5; return 0; }
这里我锁定了d地址。
然后再来看看虚继承的:
这是成员在内存中的分布位置。
那么橙框中的内容又是什么呢?
这里面其实是地址,地址在调用两个内存窗口看一下:
那么这里橙色框里面又是什么。
我们看一下距离,第一个距离20,第二个距离12,也就是说上面橙框里面的是找到虚基类对象的偏移量!
那么为什么要储存偏移量呢?
因为子类在给父类赋值的情况下,会发生切片,虚继承的成员因为是在最下面,所以中间如果少了内容位置就会不一样。
也就是说,虚继承之后的对象就不是在类内部了,而是放在下面某一处了,但是这个位置不确定,所以用偏移量来算才是准确的。
像最后一个赋值给父类,他又指向了原来d对象的位置,进入之后也是通过地址+偏移量找到虚继承对象。
那么虚继承看起来好像更加占用空间了,那么如果虚继承对象的成员很大,不用虚继承会有多份,但是用了虚继承就只有一份。
存偏移量的地方叫做虚继表,对象中储存了虚继表的地址。
总结
继承与组合:
//继承 class A { int _a; }; class B:public A { int _b; }; //组合 class X { int _x; }; class Y { X x; int _y; };
这两种都是复用。
但是继承是保护能用,组合不能用,所有就有人提出了两个概念。
继承叫做白箱复用,组合叫黑箱复用。
白箱意思就是能看清楚里面的内容,根据某些底层原理去实现对应的功能,黑箱是不注重底层的内容,只要能完成任务就行。
这里还有一个区别,继承的耦合度比组合的高。
我们一般追求的都是低耦合。
继承当中改了父类的内容,子类的内容大概率也会被修改,组合的概率就会小,因为继承跟保护和公有成员有联系,组合只和公有成员有联系。
继承:例如学生和人都有很多共同特征,可以说学生是一个人,这个人是学生。
组合:例如头和眼睛,眼睛应该在头上,但是他们细节没相似之处,不能说头是眼睛。
继承和组合要根据实际情况去使用,如果都差不多就用组合。