佛山网站建设邓先生,沈阳做网站找黑酷科技,wordpress投稿者后台,做哪种类型网站赚钱继承和多态 继承继承的权限继承的子父类访问派生类的默认成员函数菱形继承#xff08;C独有#xff09;【了解】虚拟继承什么是菱形继承#xff1f;菱形继承的问题是什么#xff1f;什么是菱形虚拟继承#xff1f;如何解决数据冗余和二义性的继承和组合的区别#xff1f;… 继承和多态 继承继承的权限继承的子父类访问派生类的默认成员函数菱形继承C独有【了解】虚拟继承什么是菱形继承菱形继承的问题是什么什么是菱形虚拟继承如何解决数据冗余和二义性的继承和组合的区别什么时候用继承什么时候用组合 多态构成多态的条件多态的原理虚函数表的打印 虚函数的重写虚函数重写的第一个例外--- 析构函数的重写虚函数重写的第二个例外---协变 重载、覆盖重写、隐藏重定义的对比重写和隐藏的详细解释 抽象类C11的override和finalfinaloverride 面试问题inline函数可以是虚函数吗静态成员可以是虚函数吗构造函数可以是虚函数吗析构函数可以是虚函数吗什么场景下析构函数是虚函数对象访问普通函数快还是虚函数更快虚函数表是在什么阶段生成的存在哪的什么是抽象类抽象类的作用 继承
继承的权限 继承的子父类访问 派生类的默认成员函数 菱形继承C独有【了解】
iostream就是菱形继承 可以去查库
虚拟继承
虚拟继承可以解决菱形继承的二义性和数据冗余的问题。如上面的继承关系在Student和 Teacher的继承Person时使用虚拟继承即可解决问题。需要注意的是虚拟继承不要在其他地 方去使用
当一个类从多个类中继承时这些类又共同继承自另一个基类这时可以使用虚拟继承来确保基类的共享实例。具体来说就是在派生类声明中使用virtual关键字来继承基类这样在进一步的派生中基类的成员就会被共享而不是重复复制。
例如如果Father和Mother类都虚拟继承自GrandParent类那么当GrandSon类继承Father和Mother时GrandSon对象中只会有一个GrandParent的实例这样就避免了数据冗余。同时由于虚拟基类的引入对于基类成员的访问也变得明确解决了二义性问题。
class GrandParent {
public:void sayHello() {std::cout Hello from GrandParent! std::endl;}
};class Father : virtual public GrandParent {
};class Mother : virtual public GrandParent {
};class GrandSon : public Father, public Mother {
};int main() {GrandSon son;son.sayHello(); // 输出 Hello from GrandParent!return 0;
}
什么是菱形继承菱形继承的问题是什么
菱形继承是一种多继承的特殊情况它涉及四个类形成一个菱形结构。
在菱形继承中存在一个基类两个派生类继承这个基类然后另一个类同时继承这两个派生类。这种继承方式在类的层次结构图中看起来像一个菱形因此得名。
菱形继承的主要问题是数据冗余和二义性。
由于最底层的派生类继承了两个基类而这两个基类又继承了同一个基类所以会造成最顶部基类的两次调用。这会导致相同数据的重复存储即冗余性。更重要的是当访问某个继承自基类的属性或方法时会产生歧义因为不清楚应该访问哪个派生类中的版本这就是所谓的二义性。
总而言之菱形继承是多继承中特有的一种复杂情况在设计类的继承关系时应谨慎使用以避免引起数据冗余和二义性问题。
什么是菱形虚拟继承如何解决数据冗余和二义性的
菱形虚拟继承是一种特殊的多继承方式它通过虚拟基类来解决菱形继承中的数据冗余和二义性问题。
在C中菱形虚拟继承是通过使用关键字virtual来实现的。当一个类从多个类中继承时这些类又共同继承自另一个基类这时可以使用虚拟继承来确保基类的共享实例。具体来说就是在派生类声明中使用virtual关键字来继承基类这样在进一步的派生中基类的成员就会被共享而不是重复复制。
例如如果Father和Mother类都虚拟继承自GrandParent类那么当GrandSon类继承Father和Mother时GrandSon对象中只会有一个GrandParent的实例这样就避免了数据冗余。同时由于虚拟基类的引入对于基类成员的访问也变得明确解决了二义性问题。
总的来说虽然菱形虚拟继承可以解决这些问题但它也会增加代码的复杂性。因此在设计类的继承结构时应当谨慎考虑是否真的需要使用多继承和虚拟继承以及它们带来的复杂性和可能的性能影响。
继承和组合的区别什么时候用继承什么时候用组合
下面以表格形式对比继承和组合的区别以及它们的适用场景
特性继承组合定义继承是一种从现有类派生新类的关系。组合是指一个类包含另一个类的实例。耦合性通常较高因为子类与父类紧密相关。较低因为类之间通过接口进行交互。封装性可能破坏封装性因为子类能访问父类保护成员。维护良好的封装性只通过接口交互。代码重用允许子类重用父类的代码和行为。通过聚合或包含实现代码重用。多态性支持子类可以覆盖或扩展父类方法。不直接支持需要通过其他机制实现。设计灵活性修改父类可能会影响所有子类。更灵活整体与部分独立变化。使用场景适用于“是一个”关系如猫是动物。适用于“有一个”关系如车有引擎。示例Dog继承自MammalMammal继承自Animal。Car包含Engine对象作为其组成部分。
何时使用继承
当你想表达一种类型层级例如所有的猫都是哺乳动物所有的哺乳动物都是动物。当子类需要父类的属性和方法并且可能还需要在子类中添加额外的特性或重写父类的方法。
何时使用组合
当你想表达的是聚合关系例如一辆车有一个引擎但车不是引擎的一种类型。当你希望保持类之间的松耦合使得一个类的内部实现可以独立于使用它的类而变化。
在实际的软件开发中组合通常被认为是比继承更有优势的设计选择因为它提供了更好的灵活性和封装性。然而在某些情况下继承仍然是合适的特别是在表示自然的层次关系时。
多态
构成多态的条件
继承中要构成多态还有两个条件
必须通过基类的指针或者引用调用虚函数被调用的函数必须是虚函数且派生类必须对基类的虚函数进行重写
多态的原理
虚函数表的打印
class Base {
private:int _b1;
public:Base():_b(10){_b;}virtual void fun1() {cout Base::fun1 endl;}virtual void fun2() {cout Base::fun2 endl;}void fun3() {cout Base::fun3 endl;}
};class Derive :public Base {
private:int _d 2;
public:virtual void fun1() {cout Derive::fun1 endl;}virtual void fun4() {cout Derive::fun4 endl;}
};typedef void(*VF_PTR)();
void PrintVFTable(VF_PTR* table) {for (int i 0;table[i] ! nullptr;i) {printf([%d]:%p-, i, table[i]);VF_PTR f table[i];f();}cout endl;
}
int main() {Base b;Derive d;PrintVFTable((*(VF_PTR**)b));PrintVFTable((*(VF_PTR**)d));return 0;
}虚函数的重写
虚函数的重写(覆盖)派生类中有一个跟基类完全相同的虚函数(即派生类虚函数与基类虚函数的 返回值类型、函数名字、参数列表完全相同)称子类的虚函数重写了基类的虚函数。
虚函数重写的第一个例外— 析构函数的重写
基类与派生类析构函数的名字不同
如果基类的析构函数为虚函数此时派生类析构函数只要定义无论是否加virtual关键字 都与基类的析构函数构成重写虽然基类与派生类析构函数名字不同。虽然函数名不相同 看起来违背了重写的规则其实不然这里可以理解为编译器对析构函数的名称做了特殊处 理编译后析构函数的名称统一处理成destructor
虚函数重写的第二个例外—协变
基类与派生类虚函数返回值类型不同
派生类重写基类虚函数时与基类虚函数返回值类型不同。即基类虚函数返回基类对象的指 针或者引用派生类虚函数返回派生类对象的指针或者引用时称为协变。了解
重载、覆盖重写、隐藏重定义的对比
在C中重载Overload、覆盖Override也称为重写和隐藏Hide也称为重定义是三种不同的函数关系。它们的区别可以通过下表进行总结
概念作用域参数列表返回类型重载同一作用域必须不同可相同也可不同覆盖/重写派生类与基类之间相同必须相同C11起返回类型也可以被协变隐藏不同作用域如基类与派生类可以相同也可以不同无特定要求
具体解释如下
重载
作用域发生在同一作用域内通常是同一个类中。参数列表同名函数必须有不同的参数列表参数类型、个数或顺序至少有一项不同。返回类型可以相同也可以不同。
覆盖/重写
作用域发生在基类与派生类之间。参数列表派生类中的函数必须与基类中的虚函数有完全相同的参数列表。返回类型从C11开始返回类型可以是相同的或者是派生类类型的派生类协变返回类型。
隐藏
作用域发生在不同作用域例如基类与派生类中的非虚函数。参数列表同名函数的参数列表可以相同也可以不同。返回类型没有特定的要求。
综上所述重载允许在同一作用域内有多种接受不同参数的同名函数覆盖/重写是指派生类重新定义了基类的虚函数通常用于实现多态而隐藏则是当派生类中的函数与基类中的函数同名时无论参数列表是否相同基类中的函数都会被隐藏。理解这些概念对于编写正确的C面向对象程序至关重要。
重写和隐藏的详细解释
重写Overriding 当你在派生类中定义一个与基类中同名且函数签名包括参数类型和返回类型完全相同的虚函数时你实际上是在提供一个新的实现。当通过基类的指针或引用调用这个函数时C运行时将动态地在程序运行时决定执行基类的版本还是派生类的版本这是多态的一个特征。
class Base {
public:virtual void doSomething() {cout Bases doSomething endl;}
};class Derived : public Base {
public:virtual void doSomething() override { // 重写基类的方法cout Deriveds doSomething endl;}
};在这个例子中Derived类重写了Base类的虚函数doSomething。
隐藏Hiding 当派生类定义了一个与基类中同名的成员函数哪怕是参数个数或类型不同或者不是虚函数基类的那个成员函数在派生类的对象上就无法直接访问了。这被称为隐藏。这不是多态的表现而是简单的名字覆盖这种情况下不会有运行时的动态调用。
class Base {
public:void doSomething() {cout Bases doSomething endl;}
};class Derived : public Base {
public:void doSomething(int x) { // 隐藏了基类的doSomethingcout Deriveds doSomething with int endl;}
};在这个例子中Derived类隐藏了Base类的doSomething函数。
总结一下
重写是多态的一个体现重写必须涉及到虚函数函数签名必须相同C通过虚函数表来实现运行时的动态绑定。隐藏发生在派生类声明了一个与基类同名的函数后而不管签名是否相同此时通过派生类的对象将不能访问到基类中被同名函数隐藏的成员除非显式指定作用域。隐藏不涉及虚函数或运行时的动态绑定。
抽象类
在虚函数的后面写上 0 则这个函数为纯虚函数。**包含纯虚函数的类叫做抽象类也叫接口 类抽象类不能实例化出对象。**派生类继承后也不能实例化出对象只有重写纯虚函数派生 类才能实例化出对象。纯虚函数规范了派生类必须重写另外纯虚函数更体现出了接口继承。
class Car
{
public:
virtual void Drive() 0;
};
class Benz :public Car
{
public:virtual void Drive(){cout Benz-舒适 endl;}
};
class BMW :public Car
{
public:virtual void Drive(){cout BMW-操控 endl;}
};
void Test()
{
Car* pBenz new Benz;pBenz-Drive();Car* pBMW new BMW;pBMW-Drive();
}C11的override和final
final
final修饰虚函数表示该虚函数不能再被重写 我的理解是这个虚函数是父类特有的功能不能被子类所继承。
class Car
{
public:virtual void Drive() final {}
};
class Benz :public Car
{
public:virtual void Drive() {cout Benz-舒适 endl;}//这里会报错说不能继承
};override
override: 检查派生类虚函数是否重写了基类某个虚函数如果没有重写编译报错。 我的理解是这功有点鸡肋就是你在子类继承后面写overrideoverride会帮你检查该函数是否是需要虚函数重写。
面试问题
inline函数可以是虚函数吗
答可以不过编译器就忽略inline属性这个函数就不再是inline因为虚函数要放到虚表中去。
静态成员可以是虚函数吗
答不能因为静态成员函数没有this指针使用类型::成员函数的调用方式无法访问虚函数表所以静态成员函数无法放进虚函数表。
构造函数可以是虚函数吗
答不能因为对象中的虚函数表指针是在构造函数初始化列表阶段才初始化的。
析构函数可以是虚函数吗什么场景下析构函数是虚函数
答可以并且最好把基类的析构函数定义成虚函数。
对象访问普通函数快还是虚函数更快
答首先如果是普通对象是一样快的。如果是指针对象或者是引用对象则调用的普通函数快因为构成多态运行时调用虚函数需要到虚函数表中去查找。
虚函数表是在什么阶段生成的存在哪的
答虚函数表是在编译阶段就生成的一般情况下存在代码段(常量区)的。
什么是抽象类抽象类的作用
答参考3.抽象类。抽象类强制重写了虚函数另外抽象类体现出了接口继承关系。