C++继承、重载、多态相关问题(简单但通俗易懂)
第九章 组合与继承一、比较 is-a 关系和 is-like-a 关系1 is-a 关系表示严格的继承关系。含义派生类是基类的一种特殊类型。例如Dog is a Animal代码classAnimal{};classDog:publicAnimal{};特点派生类对象可以当作基类对象使用满足替代原则例如Animal*pnewDog();2 is-like-a 关系表示行为相似但不是严格继承关系。例如Airplane is like a Bird飞机和鸟都会飞但飞机不是鸟不应该继承3 区别总结关系含义is-a真实继承关系is-like-a功能相似但不是继承原则只有满足 is-a 才应该使用继承二、public / private / protected 继承区别C中继承方式分为public继承、protected继承和private继承。它们的主要区别在于基类成员在派生类中的访问权限不同。在public继承中基类的public成员在派生类中仍为publicprotected成员仍为protected在protected继承中基类的public和protected成员在派生类中均变为protected在private继承中基类的public和protected成员在派生类中均变为private而基类的private成员在派生类中始终不可直接访问。三、派生类对象的内存布局与初始化派生类对象的内存由基类子对象和派生类自身成员组成基类部分通常位于对象的前部多个基类时按继承顺序依次排列。在初始化过程中构造函数的执行顺序为首先调用基类的构造函数按继承顺序然后初始化成员对象按声明顺序最后执行派生类自身的构造函数体。析构顺序与构造顺序相反。1 内存布局派生类对象在内存中---------------- | 基类成员 | ---------------- | 派生类成员 | ----------------例如classA{intx;};classB:publicA{inty;};内存B对象 ------ | x | ------ | y | ------四、多重继承可能导致的问题多重继承可能带来以下问题二义性问题当多个基类中存在同名成员时派生类对象访问时会产生不明确性菱形继承问题当多个基类继承自同一父类时会导致基类数据被重复继承从而产生数据冗余和访问二义性结构复杂性多重继承会使类层次结构复杂增加程序理解和维护难度构造与析构复杂多个基类的初始化顺序和调用关系较复杂。通常可通过虚继承来解决菱形继承带来的问题。多重继承classC:publicA,publicB{};1 命名冲突如果两个基类有同名成员classA{voidfunc();};classB{voidfunc();};调用C c;c.func();// 二义性解决c.A::func();2 菱形继承问题例如A / \ B C \ / D代码classA{};classB:publicA{};classC:publicA{};classD:publicB,publicC{};问题D中会有两个A导致内存浪费二义性解决方法使用虚继承classB:virtualpublicA{};classC:virtualpublicA{};这样D只有一个A五、替代原则Liskov Substitution Principle替代原则里氏替换原则是指在程序中子类对象必须能够替代其父类对象使用并且不影响程序的正确性和功能。即在任何使用父类对象的地方都可以透明地使用子类对象而不会改变程序的行为。该原则要求子类在继承父类时不能改变父类的原有语义应保证行为的一致性。举例classBird{public:virtualvoidfly();};classSparrow:publicBird{};使用Bird*bnewSparrow();b-fly();是合理的。错误设计例子classBird{public:virtualvoidfly();};classPenguin:publicBird{};企鹅不会飞。如果写Bird*bnewPenguin();b-fly();// 不合理违反替代原则。原则总结设计继承关系时必须满足子类可以完全替代父类否则不应该使用继承七.如果要在派生类中覆盖基类的成员函数在定义派生类成员函数时必须满足什么要求?如果派生类要覆盖基类的成员函数基类函数必须声明为虚函数派生类函数的函数名、参数列表必须与基类完全相同返回类型必须一致或满足协变返回类型同时访问权限不能比基类更严格。通常建议使用override关键字以保证函数正确覆盖。一.继承和组合是面向对象的两种重要的代码复用机制阐述它们有什么区别如何选择应用?继承和组合是面向对象中两种重要的代码复用机制。继承表示类之间的“is-a”关系子类可以继承父类的属性和方法实现代码复用并支持多态。但继承耦合性较高灵活性较差且容易破坏封装。组合表示“has-a”关系一个类通过包含另一个类的对象来实现功能复用。组合具有较低的耦合性更好的封装性和更高的灵活性可以在运行时动态替换组件。1 继承Inheritance继承表示is-a关系。含义派生类是一种特殊的基类。例如class Animal{}; class Dog : public Animal{};这里Dog is a Animal特点可以复用基类的成员支持多态体现类之间的层次关系优点代码复用支持运行时多态缺点耦合度较高继承结构不易修改2 组合Composition组合表示has-a关系。含义一个类包含另一个类的对象。例如class Engine{}; class Car{ private: Engine engine; };这里Car has a Engine特点类中包含其他类对象通过对象组合实现功能优点灵活性高耦合度低更容易扩展缺点不能直接复用接口3 区别总结项目继承组合关系is-ahas-a耦合程度高低是否支持多态支持一般不支持灵活性较低较高4 如何选择一般原则如果是 is-a 关系 → 使用继承 如果是 has-a 关系 → 使用组合现代软件设计中常说优先使用组合而不是继承第十章 虚函数与多态性1. 派生类向基类转换导致类型信息丢失怎么办当派生类对象通过基类指针或引用访问时会发生向上转型upcasting此时只能看到基类部分的信息。如果希望根据对象的实际类型调用对应函数需要使用虚函数virtual function。示例classBase{public:virtualvoidshow(){coutBaseendl;}};classDerived:publicBase{public:voidshow(){coutDerivedendl;}};Base*pnewDerived();p-show();// 调用 Derived::show()如果没有virtual只会调用 Base::show()2. 什么是动态绑定它与虚函数有什么关系动态绑定Dynamic Binding在程序运行时根据对象的实际类型决定调用哪个函数。特点又称晚绑定Late Binding发生在通过指针或引用调用虚函数时关系虚函数是实现动态绑定的机制没有虚函数函数调用在编译时确定静态绑定3. 使用虚函数实现多态的一般过程实现多态通常需要三个步骤① 在基类中声明虚函数classBase{public:virtualvoidshow();};② 在派生类中重写虚函数classDerived:publicBase{public:voidshow();};③ 使用基类指针或引用调用函数Base*pnewDerived();p-show();运行结果调用 Derived 的 show()这就是运行时多态。4. 抽象类有什么作用如何定义作用抽象类用于描述一类对象的共同特征作为基类供派生类继承实现接口规范特点抽象类不能创建对象定义方式只要类中包含纯虚函数这个类就是抽象类。纯虚函数写法virtual返回类型 函数名()0;示例classShape{public:virtualdoublearea()0;};5. 什么时候使用抽象类或纯抽象类一般在以下情况使用① 只定义接口不提供实现例如图形类 动物类 设备接口② 需要统一操作不同类型对象例如Shape ├─ Circle ├─ Rectangle └─ Triangle统一调用Shape*p;p-area();③ 需要强制派生类实现某些函数纯虚函数要求派生类必须重写6. dynamic_cast 的作用举例dynamic_cast用于运行时安全类型转换通常用于基类指针转换为派生类指针。示例classBase{public:virtual~Base(){}};classDerived:publicBase{public:voidfunc(){coutDerived functionendl;}};Base*pnewDerived();Derived*ddynamic_castDerived*(p);if(d){d-func();}如果转换失败返回 nullptr作用总结用于多态类型的安全向下转型7. 如何理解C中的多态机制**多态Polymorphism**指同一个接口对不同对象产生不同的行为。在 C 中主要通过虚函数 继承 基类指针/引用实现。示例classAnimal{public:virtualvoidspeak(){coutAnimal soundendl;}};classDog:publicAnimal{public:voidspeak(){coutDog barkendl;}};classCat:publicAnimal{public:voidspeak(){coutCat meowendl;}};调用Animal*p;pnewDog();p-speak();pnewCat();p-speak();输出Dog bark Cat meow同一个函数speak()表现不同。一页速记版考试背诵版动态绑定是指在程序运行时根据对象的实际类型决定调用哪个函数它通常通过虚函数实现。实现多态的一般过程是在基类中声明虚函数在派生类中重写该函数然后通过基类指针或引用调用函数。抽象类是包含纯虚函数的类不能实例化对象主要用于定义接口规范并作为基类供派生类继承。dynamic_cast用于运行时安全类型转换常用于基类指针向派生类指针的转换。C的多态机制是指通过继承和虚函数使得同一个接口在不同对象上表现出不同的行为。这两题是C虚函数章节最经典的简答题我给你整理成考试标准答案 易理解版。1. 什么是虚函数什么是纯虚函数为什么要引入它们1什么是虚函数**虚函数Virtual Function**是指在基类中使用virtual关键字声明的成员函数。特点可以在派生类中被重写override通过基类指针或引用调用时根据对象的实际类型决定调用哪个函数示例classBase{public:virtualvoidshow(){coutBase showendl;}};classDerived:publicBase{public:voidshow(){coutDerived showendl;}};Base*pnewDerived();p-show();// 调用 Derived::show()2什么是纯虚函数**纯虚函数Pure Virtual Function**是没有函数实现的虚函数。声明方式virtual返回类型 函数名()0;例如classShape{public:virtualdoublearea()0;};特点包含纯虚函数的类称为抽象类抽象类不能创建对象派生类必须实现纯虚函数3为什么引入虚函数引入虚函数是为了实现① 运行时多态同一个接口对不同对象产生不同的行为。例如Animal*pnewDog();p-speak();② 动态绑定函数调用在运行时决定。4为什么引入纯虚函数纯虚函数主要用于① 定义接口规范规定派生类必须实现某些函数。例如Shape ├── Circle ├── Rectangle └── Triangle所有图形都必须实现area()② 实现抽象类用于表示抽象概念。例如Shape Animal Device2. 析构函数应该是虚函数吗为什么一般来说如果类可能被继承析构函数应该定义为虚函数。原因当使用基类指针删除派生类对象时如果析构函数不是虚函数只会调用基类析构函数。示例classBase{public:~Base(){coutBase destructorendl;}};classDerived:publicBase{public:~Derived(){coutDerived destructorendl;}};Base*pnewDerived();deletep;如果析构函数不是虚函数只调用 Base destructorDerived的资源不会释放。正确写法classBase{public:virtual~Base(){coutBase destructorendl;}};此时执行deletep;调用顺序Derived destructor Base destructor总结如果一个类作为基类使用其析构函数通常应该声明为虚函数以保证通过基类指针删除派生类对象时能够正确调用派生类析构函数从而避免资源泄露。考试背诵版简化虚函数是在基类中使用virtual声明的成员函数在派生类中可以被重写通过基类指针或引用调用时根据对象的实际类型决定调用哪个函数从而实现运行时多态。纯虚函数是没有函数体的虚函数其形式为virtual 函数 0包含纯虚函数的类称为抽象类抽象类不能创建对象派生类必须实现纯虚函数。引入虚函数是为了实现动态绑定和多态引入纯虚函数是为了定义接口规范。如果一个类作为基类使用其析构函数通常应该声明为虚函数因为当通过基类指针删除派生类对象时虚析构函数可以保证先调用派生类析构函数再调用基类析构函数从而正确释放资源。这两题其实是在考C多态 容器存储对象时的对象切片问题很多人容易答不完整。我给你整理成考试标准答案 原理解释版。5. 能否用 vector 或 vectorComponent 代替 vectorComponent*结论不能使用 vectorComponent也不能使用 vectorComponent原因如下。1为什么不能使用 vector如果使用vectorComponent会发生对象切片Object Slicing。原因Component是基类而Part、Assembly等是派生类。当派生类对象放入vectorComponent时派生类对象 → 转换为基类对象结果派生类特有的数据和行为会被丢弃例如classComponent{};classPart:publicComponent{};存入vectorComponentv;Part p;v.push_back(p);结果p 会被复制成 Component 对象此时Part 的成员全部消失并且多态也无法实现。2为什么不能使用 vectorComponentC 标准规定容器不能存储引用类型因此vectorComponent是非法的。原因引用必须在初始化时绑定对象且不能重新绑定而容器需要频繁移动和赋值元素。3为什么使用 vectorComponent*使用指针vectorComponent*可以1️⃣ 保存不同派生类对象例如Part*Assembly*2️⃣ 保持对象真实类型3️⃣ 实现多态调用例如Component*cnewPart();c-print();运行时调用Part::print()总结vectorComponent 会导致对象切片丢失派生类信息 vectorComponent 在C中是不允许的 因此必须使用 vectorComponent* 来保存不同类型的组件对象 从而保持多态性。6. 为什么说“面向对象编程必须使用指针和引用”这句话的意思是要实现多态必须通过指针或引用访问对象1对象本身不能实现多态例如Base bDerived();b.show();发生对象切片结果只调用 Base::show()因为b 已经变成 Base 对象2只有指针或引用才能保持对象真实类型例如Base*pnewDerived();p-show();此时p 指向 Derived 对象运行时调用 Derived::show()这就是动态绑定3多态必须满足三个条件1️⃣ 继承2️⃣ 虚函数3️⃣基类指针或引用例如Animal*p;pnewDog();pnewCat();调用p-speak();不同对象表现不同。总结理解这句话的含义是在C中如果直接使用对象进行赋值或传递 会发生对象切片丢失派生类信息 而使用指针或引用可以保持对象的真实类型 从而通过虚函数实现运行时多态。因此在面向对象编程中经常需要使用指针或引用来操作对象。考试简答版推荐写法在组件库存管理程序中不能使用vectorComponent因为当派生类对象存入该容器时会发生对象切片派生类特有的数据和行为会丢失从而无法实现多态也不能使用vectorComponent因为C标准容器不允许存储引用类型。因此需要使用vectorComponent*来保存组件对象从而能够存储不同派生类对象并保持多态。“无法直接使用对象进行面向对象编程”是因为如果直接使用对象赋值或传递会发生对象切片丢失派生类信息而使用基类指针或引用可以保持对象的真实类型并通过虚函数实现运行时多态因此在C面向对象编程中通常需要通过指针或引用来操作对象。这些是C模板Template章节的典型简答题我给你整理成标准答案 简洁考试版考试基本按这个写就可以。第十一章 模板与泛型编程1. C的模板机制解决什么问题**模板Template**主要用于解决代码复用问题。在很多情况下不同数据类型需要执行相同的算法或操作如果不用模板就需要为每种类型写一套函数或类。例如intmax(inta,intb);doublemax(doublea,doubleb);floatmax(floata,floatb);代码会大量重复。使用模板templatetypenameTTmax(T a,T b){returnab?a:b;}一个模板即可适用于多种类型。总结C模板机制通过参数化类型使函数或类能够适用于多种数据类型从而提高代码复用性减少重复代码并提高程序的通用性。2. 什么叫函数模板实例函数模板在什么时候被实例化函数模板实例是指编译器根据函数模板和具体的数据类型生成的具体函数。例如模板templatetypenameTTadd(T a,T b){returnab;}调用add(1,2);add(1.5,2.3);编译器会生成intadd(int,int);doubleadd(double,double);这些具体函数就叫函数模板实例。实例化时刻函数模板在被调用时由编译器自动实例化也就是说只有当模板被使用时才会生成具体函数3. 什么叫类模板实例类模板在什么时候被实例化类模板实例是指根据类模板和具体类型参数生成的具体类。例如templatetypenameTclassBox{public:T data;};使用Boxintb1;Boxdoubleb2;编译器会生成classBoxintclassBoxdouble这些就是类模板实例。实例化时刻类模板在使用具体类型创建对象时进行实例化也就是说Boxint b1;这一行代码会触发实例化。4. 模板的显式实例化有什么作用**显式实例化Explicit Instantiation**是指程序员显式指定模板生成某种类型的实例。语法template返回类型 函数名类型(参数);例如templateintaddint(int,int);作用1️⃣ 强制编译器生成指定类型的模板实例2️⃣ 提前生成代码避免多次实例化3️⃣ 在模板实现分离.h/.cpp时解决链接问题例如模板定义在cpp中时需要显式实例化一页考试简答版推荐背1️⃣ C模板机制通过参数化类型使函数或类能够适用于多种数据类型从而提高代码复用性减少重复代码提高程序的通用性。2️⃣ 函数模板实例是编译器根据函数模板和具体类型参数生成的具体函数。函数模板通常在被调用时由编译器自动实例化。3️⃣ 类模板实例是根据类模板和具体类型参数生成的具体类。类模板一般在使用具体类型创建对象时进行实例化。4️⃣ 模板的显式实例化是指程序员显式指定模板生成某种类型的实例其作用是强制编译器生成特定类型的模板代码并可用于解决模板分离编译时的链接问题。一、模板的特点C模板是一种参数化类型机制它允许函数或类在定义时不指定具体类型而在使用时再确定具体类型。模板具有代码复用性高、类型安全、编译期实例化和支持泛型编程等特点。模板通常用于不同数据类型执行相同操作、实现通用数据结构以及编写通用算法等场景。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2423475.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!