1.继承和友元
友元函数不能继承,换句话说父类的友元不能访问子类私有和保护成员。(不作为重点)
class Student;//提前声明,因为友元关系不能被继承,那么Person //类里的友元函数中的Student找不到对应的类(程序是从上而下寻找的) class Person { public: friend void Test(const Person& p, const Student& s); protected: string _name; }; class Student : public Person { public: friend void Test(const Person& p, const Student& s); protected: string _id; }; void Test(const Person& p, const Student& s) { cout << p._name << endl; cout << s._id << endl;//“Student::_id”: 无法访问 protected 成员(在“Student”类中声明) //如果想要解决这种问题,把Test也变成Student的友元即可 } int main() { Person person; Student stu; Test(person, stu); return 0; }
2.继承与静态成员
父类定义类static成员,那么整个继承体系里只有一个这样的成员。无论派生出多少个子类,都只有一个static成员实例。
class Person { public: string _name; static string _sex; }; string Person::_sex = "女";//静态成员变量在类外进行声明 class Student : public Person { protected: string _id; }; int main() { Person person; Student stu; cout << &person._sex << endl; cout << &stu._sex << endl;//地址一样,均为00007FF7DCA45440 //公有情况下,父子类指定类域都可以访问静态成员 cout << Person::_sex << endl; cout << Student::_sex << endl; }
3.多继承及其菱形继承问题
3.1继承模型
单继承:一个父类只有一个直接父类时称这个继承关系为单继承
多继承:一个父类有两个或两个以上继承时称这个继承为多继承,多继承对象在内存中的模型是:先继承的父类在前面,后继承的父类在后面,子类成员房贷最后面。
菱形继承:菱形继承是多继承的一种特殊情况。菱形继承有数据冗余和二义性的问题,在D的对象中A成员会有两份。实践中不建议设计出菱形继承这种模型。
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 a; //a._name = "李三";//报错,因为这是个菱形继承,会有二义性问题,编译器并不知道_name所指的是哪一个 // 需要显⽰指定访问哪个基类的成员可以解决二义性问题,但是数据冗余问题⽆法解决 a.Student::_name = "xxx"; a.Teacher::_name = "yyy"; return 0; }
3.2虚继承
为了解决菱形继承所存在的问题,C++增加了virtual关键字,也就是虚继承。
注意:哪个类产生数据冗余和二义性,继承那个类的时候加virtual
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; // 主修课程 };
3.3 多继承中指针偏移问题
看下面一道题:
class Base1 { public: int _b1; }; class Base2 { public: int _b2; }; class Derive : public Base1, public Base2 { public: int _d; }; int main() { Derive d; Base1* p1 = &d; Base2* p2 = &d; Derive* p3 = &d; return 0; }
p1、p2和p3是什么关系?
首先,可以从代码看出是一个多继承问题,Derive继承Base1和Base2。其次,根据基类指针或引用指向的是派生类中切出来的基类那部分,以及先继承的在前面可以看出,p1和p3指向同一处地方。最后,用画图来解释一下:
4.继承和组合
- public继承是is-a关系。
- 组合是has-a关系。
- 继承允许你根据基类的实现来定义派生类的实现。这种通过生成派生类的复用通常被称为白箱复用(white-box reuse)。术语“白箱”是相对可视性而言:在继承方式中,基类的内部细节对派生类可见。继承一定程度破坏了基类的封装,基类的改变,对派生类有很大的影响。派生类和基类间的依赖关系很强,耦合度高。
- 对象组合是类继承之外的另一种复用选择。新的更复杂的功能可以通过组装或组合对象来获得。对象组合要求被组合的对象具有良好定义的接口。这种复用风格被称为黑箱复用(black-box reuse), 因为对象的内部细节是不可见的。对象只以“黑箱”的形式出现。组合类之间没有很强的依赖关系,耦合度低。优先使用对象组合有助于你保持每个类被封装。
优先使用组合,而不是继承。实际尽量多去用组合,组合的耦合度低,代码维护性好。不过也不太那么绝对,类之间的关系就适合继承(is-a)那就用继承,另外要实现多态,也必须要继承。类之间的关系既适合用继承(is-a)也适合组合(has-a),就用组合。
over~