济南外贸网站推广,网站seo相关设置优化,给网站可以怎么做外链,做外贸如何浏览国外网站【C】——继承#xff08;下#xff09; 5 继承与友元6 继承与静态成员7 多继承7.1 继承模型7.2 菱形继承的问题7.3 虚继承7.4 多继承中的指针偏移问题 8 组合与继承 5 继承与友元 友元关系不能被继承。即一个函数是父类的友元函数#xff0c;但不是子类的友元函数。也就是说… 【C】——继承下 5 继承与友元6 继承与静态成员7 多继承7.1 继承模型7.2 菱形继承的问题7.3 虚继承7.4 多继承中的指针偏移问题 8 组合与继承 5 继承与友元 友元关系不能被继承。即一个函数是父类的友元函数但不是子类的友元函数。也就是说父类的友元不能访问子类的私有和保护成员
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;
}int main()
{Person p;Student s;Display(p, s);return 0;
}这里出现了许多报错但没关系我们先看框出来的这一条 这条报错说缺少,一般出现这种报错而我们检查出并没有,的相关问题时通常都是 类型出问题了一般是我们没有定义某个类型但我们直接去使用就会出现这种报错。 编译器遇到一个类型、变量、函数时都只会向上查找这是为了提高编译速度 出现报错的原因是friend void Display(const Person p, const Student s);中我们使用了 S t u d e n t Student Student 类型它的定义在下面。但 S t u d e n t Student Student 是 P e r s o n Person Person 的继承又不可能将其放在 P e r s o n Person Person 的前面我们可以在 P e r s o n Person Person 前加上 S t u d e n t Student Student 的前置声明
class Student;解决这个问题后报错就少很多啦我们再看看剩下的报错 这里就是友元函数的问题啦。 D i s p l a y ( ) Display() Display() 是 P e r s o n Person Person 的友元但友元关系不能继承下来因此 D i s p l a y ( ) Display() Display() 不是 S t u d e n t Student Student 的友元。解决方法也很简单在 S t u d e n t Student Student 中加一个有元声明就好
//前置声明
class Student;class Person
{
public :friend void Display(const Person p, const Student s);
protected:string _name; // 姓名
};
class Student : public Person
{
public:friend void Display(const Person p, const Student s);
protected :int _stuNum; // 学号
};void Display(const Person p, const Student s)
{cout p._name endl;cout s._stuNum endl;
}6 继承与静态成员 父类定义了 s t a t i c static static 静态成员则整个继承体系里面只有一个这样的成员。无论派生出多少个子类都只有一个 s t a t i c static static 成员实例。 而普通成员假设父类有一个_name子类继承下来也有另一个_name但是他们两个_name不是同一个各自是各自的。
我们来演示一下
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;// 也可以通过对象进行访问cout p._count endl;cout s._count endl;return 0;
}运行结果 虽然静态变量可以通过对象访问但一般不这么做大多数都是直接指定类域去访问 7 多继承
7.1 继承模型
单继承一个子类只有一个直接父类时称为这个继承关系为单继承 单继承 多继承一个子类有两个或以上直接父类时称这个继承关系为多继承。多继承对象在内存中的模型是先继承的父类在前面后继承的父类在后面子类成员放在最后面 多继承 菱形继承菱形继承是多继承的一种特殊情况。菱形继承的问题从下面的对象成员模型构造可以看出菱形继承有数据冗余和二义性的问题在 A s s i s t a n t Assistant Assistant 的对象中 P e r s o n Person Person 成员会有两份。支持多继承就一定会有菱形继承像 J a v a Java Java 就直接不支持多继承规避掉了这里的问题所以实践中我们是不建议设计出菱形继承这样的继承模型的 菱形继承 7.2 菱形继承的问题 菱形继承是很坑的有数据冗余浪费空间和二义性不知访问哪个的问题现实中不想被打就不要设计出菱形继承多继承是没问题的不要搞出菱形继承就行。 我们通过代码来看一下菱形继承存在的问题
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; // 主修课程
};上述就是菱形继承 P e r s o n Person Person 成员在 A s s i s t a n t Assistant Assistant 对象中有两份。我们试着访问 P e r s o n Person Person 的成员 _ n a m e name name
int main()
{Assistant a;a._name peter;return 0;
}我们需要指定访问那个父类成员的成员可以解决二义性的问题但是数据冗余问题无法解决
int main()
{Assistant a;a.Student::_name xxx;a.Teacher::_name yyy;return 0;
}虽然解决了二义性但数据冗余问题无法解决Person 有两份 不仅如此因为数据冗余导致菱形继承对象的大小特别大
int main()
{Assistant a;cout sizeof(Assistant) endl;return 0;
}那么现实中有没有人设计出菱形继承呢还真有我们简单看一下 虽然但是别学他
7.3 虚继承 为了解决菱形继承的问题C 引入了虚继承的概念新增关键字 v i r t u a l 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; // 主修课程
};注意因为是Person有数据冗余和二义性所以是 Student 和 Teacher 继承 Person 时是虚继承加 virtual 关键字
int main()
{Assistant a;a._name peter;a.Student::_name xxx;a.Teacher::_name yyy;return 0;
}加了虚继承后就只有一份 P e r s o n Person Person 成员了共用了。既可以直接访问也可以指定类域访问。 这里虽然监视窗口显示的是 3 个 P e r s o n Person Person到那实际上他们是共用的。 其实库中的菱形继承也是用虚继承来解决的
templateclass CharT, class Traits std::char_traitsCharT
class basic_ostream : virtual public std::basic_iosCharT, Traits
{};
templateclass CharT, class Traits std::char_traitsCharT
class basic_istream : virtual public std::basic_iosCharT, Traits
{};那虚继承对 S t u d e n t Student Student 和 T e a c h e r Teacher Teacher 有什么影响吗 底层的角度有一些影响用的角度没有影响。从用的角度来说 S t u d e n t Student Student 和 T e a c h e r Teacher Teacher 就是一个单继承。 v i r t u a l virtual virtual 真正影响的是下面的 A s s i s t a n t Assistant Assistant
注 S t u d e n t Student Student 和 T e a c h e r Teacher Teacher 都要给虚继承不能只给其中一个虚继承 那这样算不算菱形继承呢 算的菱形继承并不是看是否构成菱形而是看某个类是否被重复继承是否产生数据冗余和二义性。 那如果我们要加虚继承该加在哪里呢 B B B 和 C C C。因为虚继承是谁会产生数据冗余和二义性谁继承它时就要虚继承。在 E E E 中是 A A A 有数据冗余二义性所以 B B B、 C C C 继承 A A A 时使用虚继承 那能不能全部加上虚继承呢 D D D 和 E E E 都加上 不要毕竟是药三分毒。 总结单继承和多继承可以用但使用多继承时不要设计出菱形继承 7.4 多继承中的指针偏移问题
下面说法中正确的是 Ap1 p2 p2 Bp1 p2 p3 Cp1 p3 ! p2 Dp1 ! p2 ! p3 Ep2 p3 ! p1
class Base1 { public: int _b1; };
class Base2 { public: int _b2; };
class Derive : public Base2, public Base1 { public: int _d; };
int main()
{Derive d;Base1* p1 d;Base2* p2 d;Derive* p3 d;return 0;
}要做对这道题我们要知道下面两个知识点 子类给给父类的对象/指针/引用会发生切片子类对象给子类指针指向的是整个子类对象子类对象给父类指针指向的是子类对象中父类的那一部分多继承对象在内存中的声明是先继承的基类在前⾯后⾯继承的基类在后⾯派⽣类成员在放到最后⾯ 首先 p 3 p3 p3 指向开始肯定是没问题的。对 p 2 p2 p2将子类对象给父类指针其会指向子类中父类的那一部分 p 2 p2 p2 指向 B a s e 2 Base2 Base2因为 B a s e 2 Base2 Base2 先继承所以 p 2 p2 p2 也指向开始。 p 1 p1 p1 则指向子类对象 B a s e 1 Base1 Base1 的部分 p 1 p1 p1 不可能再指向开始它发生了偏移。这题选 E 8 组合与继承 p u b l i c public public 继承是一种 i s is is- a a a 的关系。也就是说每个子类对象都是一个父类对象组合 是一种 h a s has has- a a a 的关系。假设 B B B 组合了 A A A每个 B 对象都有一个 A 对象 什么意思呢我举个例子大家就明白了
//组合
class Stack
{
public://成员函数private:vectorint v;
};//继承
class Stack : public vectorint
{};我们再来看下 i s is is- a a a 与 h a s has has- a a a 组合是一种 h a s has has- a a a 的关系栈有一个数组继承是一种 i s is is- a a a 的关系栈是一个数组 继承允许你根据父类的实现定义子类的实现。这种通过生成子类的复用通常称为白箱复用white-box reuse。术语“白箱”是相对可视性而言在继承方式中父类的内部细节对子类可见。继承一定程度破坏了父类的封装父类的改变对子类有很大的影响。子类和父类间的依赖关系很强耦合度很高对象组合是类继承之外的另一种复用选择。新的更复杂的功能可以通过组装或组合对象类获得。对象组合要求被组合的对象具有良好定义的接口。这种复用风格被称为黑箱复用black-box reuse因为对象的内部细节是不可见的。对象只以“黑箱”的形式出现。组合之间没有很强的依赖关系耦合度低。优先使用对象组合有助于你保持每个类的封装 什么是黑什么是白呢 测试中分为黑盒测试和白盒测试 黑盒测试指看不见里面的实现也不需要看见你的实现。比如现在有一个新的 APP我只需要从用户用的角度去测试。黑盒测试有些地方也叫功能测试白盒测试白盒测试要了解其底层实现从代码运行逻辑角度进行测试。 白盒测试明显比黑盒测试更难 那是耦合度高好还是耦合度低好呢肯定是耦合度低好 比如现在有两个模块模块一有100个接口函数且全部对模块二透明那模块二就能使用模块一的任意多个函数接口来实现自己的功能。但如果模块一今天把这个函数的参数类型改了明天吧那个函数的参数个数给改了因为模块二是依赖模块一的模块二也只能跟着改。 但如果模块一虽然有100个函数接口但只提供5个最关键函数接口给模块二。这时两模块之间的耦合度就大大降低只要模块一不改那5个函数其他95个函数随便改都不影响模块二。 所以软件工程中提出了一个低耦合、高内聚的概念。高内聚可以认为是一个模块里面关系越紧密越好 没关系的就拿出去 所以两个类的关系是继承好还是组合好呢明显是组合更好因为继承关系下父类的任何改动都可能会影响子类 优先使用组合而不是继承。实际尽量多去用组合组合的耦合度低代码维护性好。不过也不是那么绝对类之间的关系更适合继承 i s is is- a a a那就用继承另外要实现多态也必须要继承。类之间的关系既适合用继承 i s is is- a a a也适合组合 h a s has has- a a a就用组合。 比如 T i r e Tire Tire(轮胎) 和 C a r Car Car(车) 更符合 h a s has has- a a a 的关系
class Tire
{
protected:string _brand Michelin; // 品牌size_t _size 17; // 尺⼨
};class Car {
protected:string _colour ⽩⾊; // 颜⾊string _num 陕ABIT00; // ⻋牌号Tire _t1; // 轮胎Tire _t2; // 轮胎Tire _t3; // 轮胎Tire _t4; // 轮胎
};车 h a s has has- a a a 轮胎是正常的但车 i s is is- a a a 轮胎就是错的 但 C a r Car Car 和 B M W BMW BMW / B e n z Benz Benz宝马/奔驰更符合 i s is is- a a a 的关系
class BMW : public Car
{
public:void Drive() { cout 好开-操控 endl; }
};
// Car和BMW/Benz更符合is-a的关系
class Benz : public Car {
public:void Drive() { cout 好坐-舒适 endl; }
};只能说宝马/奔驰 i s is is- a a a 车不能说宝马/奔驰 h a s has has- a a a 车
//组合
class Stack
{
public://成员函数private:vectorint v;
};//继承
class Stack : public vectorint
{};但既可以说栈有一个数组也可以说栈是一个数组这种情况下优先使用组合。 判断两个类型适合组合还是继承就用 i s is is- a a a 和 h a s has has- a a a 来判断 好啦本期关于 priority_queue 与仿函数 的知识就介绍到这里啦希望本期博客能对你有所帮助。同时如果有错误的地方请多多指正让我们在 C 的学习路上一起进步