面向对象的程序设计语言-C+课件第7章_第1页
面向对象的程序设计语言-C+课件第7章_第2页
面向对象的程序设计语言-C+课件第7章_第3页
面向对象的程序设计语言-C+课件第7章_第4页
面向对象的程序设计语言-C+课件第7章_第5页
已阅读5页,还剩32页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

CHAPTER07继承与派生面向对象的程序设计语言——C++课程教学课件Contents目录面向对象的程序设计语言——C++第7章继承与派生01继承与派生的基础概念02派生类定义与访问控制03构造析构与复杂继承关系04继承应用实例与综合实践CHAPTER01继承与派生的基础概念理解继承的动机、基本语法与核心术语,建立面向对象复用的基本认知Chapter07·Inheritance为什么需要继承?继承机制的核心动机是实现代码复用与层次化设计。通过继承,新类可以自动获得已有类的数据成员和成员函数,在此基础上添加或修改功能,避免重复编码,同时建立起类之间的"一般-特殊"层次关系。代码复用需求当多个类共享相同的属性和方法时,继承机制允许新类直接复用已有类的代码,而非从零开始编写CODEREUSE层次化建模现实世界中事物存在"一般-特殊"关系(如"动物→狗"),继承使程序能够精确反映这种层次结构HIERARCHY扩展与修改继承允许在不修改原始类的前提下,为新类添加特有的成员变量和成员函数,实现功能的增量扩展EXTENSION多态基础继承为后续的多态机制提供了必要的类型层次关系,是面向对象三大特性(封装、继承、多态)的关键纽带POLYMORPHISMCHAPTER7·INHERITANCE继承与派生的基本概念继承与派生是同一过程的不同视角:从派生类角度看是"继承"基类的成员,从基类角度看是"派生"出新的类。基类(BaseClass)被继承的已有类,又称父类或超类,提供可被复用的数据成员和成员函数。基类定义了派生类的基础结构和行为模式,是面向对象程序设计中代码复用的核心机制。父类·超类派生类(DerivedClass)通过继承基类而产生的新类,又称子类,自动拥有基类的非私有成员并可添加新成员。派生类在保留基类特性的基础上进行功能扩展或特化,实现层次化的类结构设计。子类·扩展直接基类与间接基类若类B直接继承类A,则A是B的直接基类;若类C继承B、B继承A,则A是C的间接基类。直接基类是派生类声明时明确指定的父类,间接基类则通过继承链多层传递。A→B→C继承关系的传递性继承具有传递性,派生类不仅继承直接基类的成员,也继承所有间接基类的非私有成员。这一特性使得类层次结构能够逐层累积功能,构建复杂而有序的类型体系。传递性InheritanceHierarchy现实世界中的继承层次继承关系广泛存在于现实世界的分类体系中。从生物分类到交通工具、从组织机构到电子产品,"一般→特殊"的层次结构无处不在,C++的继承机制正是对这种自然分类方式的程序化抽象。生物分类体系生物→动物→哺乳动物→犬科→狗,每一层派生类在基类基础上增加更具体的属性与行为特征。这种层次化分类使得复杂生物世界得以系统化组织,便于研究和理解物种间的演化关系。5级派生交通工具分类交通工具→陆地交通工具→汽车→轿车/SUV/卡车,体现了从抽象到具体的逐层细化过程。每一层级都继承父类的通用特性,同时添加自身特有的功能属性和行为方式。4级细化组织机构层次人员→员工→技术人员/管理人员→高级工程师/部门经理,展示了属性与职责的层层递进。下层角色继承上级的基本权限和义务,同时获得更具体的职责范围和决策能力。职责递进程序建模启示通过继承层次,可以用基类定义通用接口和公共属性,派生类实现差异化功能,提高代码的可维护性和扩展性。这种设计模式允许在不修改现有代码的情况下添加新功能,实现开闭原则。可维护·可扩展INHERITANCESYNTAX派生类的定义语法C++通过"class派生类名:继承方式基类名"的语法定义继承关系,继承方式决定基类成员在派生类中的访问属性。01基本语法格式class派生类名:继承方式基类名{新增成员声明};冒号、继承方式关键字和基类名缺一不可classD:publicB02继承方式关键字public(公有)、protected(保护)、private(私有)三种,不写时class默认为private3种方式03新增成员派生类可在花括号内声明特有的数据成员和成员函数,是区别于基类的独有功能扩展能力04多重继承语法派生类可同时继承多个基类,各基类间用逗号分隔,各自指定继承方式classD:B1,B2INHERITANCE单继承与多重继承C++同时支持单继承和多重继承两种模式。单继承结构简单、关系清晰,是最常用的继承方式;多重继承功能强大但增加了二义性等复杂性,需要谨慎使用。单继承形成线性层次,多重继承构成汇聚网络单继承01派生类只有一个直接基类,形成清晰的线性继承链,如Student继承Person02关系简单直观,不存在成员名冲突的二义性问题,是最常见、最推荐的继承方式03大部分面向对象设计场景中,单继承配合组合模式即可满足需求多重继承01派生类同时拥有多个直接基类,如TeachingAssistant同时继承Teacher和Student02能更灵活地组合多个类的功能,但也带来了成员访问的二义性和菱形继承等问题03C++是少数支持真正多重继承的主流编程语言,使用时需配合虚基类等机制解决复杂问题Chapter07·Inheritance代码示例:Person基类与Student派生类通过Person→Student的继承示例,直观理解基类与派生类的代码结构。派生类通过公有继承获得基类的公有和保护成员,并在此基础上添加自己的特有成员,实现功能的增量扩展。01Person基类定义:包含name(姓名)、age(年龄)等私有成员变量,以及构造函数和show()公有方法作为对外接口02Student派生类定义:classStudent:publicPerson,公有继承Person后新增studentID(学号)、GPA(绩点)等特有成员03成员访问关系:Student对象可直接调用继承自Person的show()方法,但不能直接访问Person的private成员name和age04代码复用效果:Student无需重新定义姓名、年龄等通用属性和show方法,仅需添加学生特有的成员,显著减少重复代码CHAPTER02派生类定义与访问控制掌握三种继承方式下的成员访问权限变化规律,理解封装与继承的协同机制AccessControl回顾:类的三种访问限定符C++通过public、protected、private三种访问限定符实现封装,其中protected是专为继承机制设计的访问级别。理解这三种限定符的基本含义是掌握继承中访问控制规则的前提。public(公有)成员可以在类内部、类外部、派生类内部任意位置被直接访问,是类对外暴露的接口。任意访问private(私有)成员只能在定义它的类内部被访问,类外部和派生类内部均不能直接访问,实现数据隐藏。数据隐藏protected(保护)成员可以在类内部和派生类内部被访问,但在类外部不能直接访问,是专为继承设计的权限级别。继承专用作用域规则访问限定符的作用域从该关键字出现的位置开始,直到下一个访问限定符或类定义结束为止。{}边界AccessControl公有继承(public)的访问控制公有继承是最常用、最推荐的继承方式,它保持基类成员的原有访问级别不变:基类的public成员在派生类中仍为public,protected成员仍为protected,private成员不可直接访问。公有继承体现了'is-a'(是一个)的关系。01PUBLICpublic→public基类public成员在派生类中仍为public:派生类的对象和派生类的成员函数都可以直接访问这些成员。公开访问02PROTECTEDprotected→protected基类protected成员在派生类中仍为protected:派生类的成员函数可以直接访问,但派生类的对象不能直接访问。内部可见03PRIVATEprivate→不可访问基类private成员在派生类中不可直接访问:即使是公有继承,派生类也无法直接访问基类的私有成员。完全封装04SEMANTICS"is-a"语义关系公有继承表达"派生类是基类的一种",如StudentisaPerson,因此派生类对象可赋值给基类指针或引用。多态基础C++·PRIVATEINHERITANCE私有继承(private)的访问控制私有继承将基类的public和protected成员在派生类中全部降为private,限制了基类接口的对外暴露。私有继承表达"基于…实现"的关系,多数场景可用组合替代。PUBLIC基类public→派生类private仅派生类的成员函数可访问,派生类的对象和下层派生类均不可访问。privatePROTECTED基类protected→派生类private与public成员相同,在派生类中被私有化,进一步限制了访问范围。privatePRIVATE基类private→不可直接访问与公有继承一致,基类的私有成员对派生类始终不可见。invisibleCOMPOSITION私有继承vs组合"基于…实现"关系通常可用成员对象替代,组合方式更直观、耦合度更低。has-aInheritance·AccessControl保护继承(protected)的访问控制保护继承将基类的public成员在派生类中降为protected,使基类接口在派生类层次内部可用但对外不可见。这种方式在多层继承体系中保留了对下层派生类的开放性,同时限制了外部访问,实际使用频率低于公有继承。public→protected基类的public成员在派生类中变为protected,派生类的成员函数可访问,但派生类对象不可直接访问,对外隐藏了基类接口。对外隐藏protected→protected基类的protected成员在派生类中保持保护级别不变,派生类成员函数可以继续访问这些成员,权限无降级。级别不变private→不可访问基类的private成员在派生类中不可直接访问,三种继承方式对此处理完全一致,基类私有成员始终不可见。始终不可见多层继承的影响保护继承下,更下层的派生类(如孙类)仍可通过protected权限访问原始基类的成员,保持了层次内的开放性。层次内开放ACCESSCONTROL三种继承方式权限变化对照表三种继承方式对基类成员访问权限的影响存在规律性差异:公有继承保持权限不变,私有继承将可见成员全部降为private,保护继承将public降为protected。无论哪种方式,基类的private成员在派生类中均不可直接访问。基类成员原始权限公有继承后私有继承后保护继承后publicpublicprivateprotectedprotectedprotectedprivateprotectedprivate不可直接访问不可直接访问不可直接访问公有继承保持原权限,私有继承全部私有化,保护继承public降为protected,private成员始终不可直接访问AccessControl·Inheritance代码示例:公有继承的访问控制验证通过Base→Derived的公有继承代码示例,验证三种不同权限的基类成员在派生类内、派生类外的实际访问行为。实际编码中,应优先将基类数据成员设为protected或private,通过public方法提供访问接口。基类定义classBase包含public的pub_data、protected的pro_data和private的pri_data三个数据成员及对应方法public·protected·private派生类成员函数内可直接访问继承的pub_data和pro_data,但访问pri_data会编译报错,验证了private的不可访问性✓pub_data·✓pro_data·✗pri_data派生类对象外部Derivedd;d.pub_data可访问,_data和d.pri_data均编译报错,验证了protected在类外的不可访问性✓pub_data·✗pro_data·✗pri_data最佳实践建议基类的数据成员应设为protected或private并提供public的getter/setter方法,避免在派生类中直接操作基类数据Getter/SetterInheritanceclass与struct在继承中的区别C++中class和struct在继承场景下存在两个关键差异:成员默认访问权限不同(class默认private,struct默认public)和默认继承方式不同(class默认private继承,struct默认public继承)。实际开发中推荐统一使用class进行面向对象设计。默认访问权限差异01class默认private:未加访问限定符的成员默认为private,外部不可直接访问02struct默认public:未加访问限定符的成员默认为public,外部可直接访问03C++兼容C语言struct用法,同时升级为支持成员函数的完整类定义默认继承方式差异01class默认私有继承:classD:B{}等价于privateB02struct默认公有继承:structD:B{}等价于publicB03建议始终显式写出继承方式关键字,避免默认行为差异导致权限错误CLASS默认Private成员默认私有访问,继承默认私有方式STRUCT默认Public成员默认公有访问,继承默认公有方式实践建议:统一使用class进行面向对象设计,始终显式声明访问权限与继承方式C++INHERITANCE·NAMEHIDING派生类对基类成员的隐藏(名称遮蔽)当派生类定义了与基类同名的成员时,派生类成员会隐藏基类的同名成员,这是作用域规则的自然结果。通过作用域解析运算符可以显式访问被隐藏的基类成员,避免命名冲突导致的逻辑错误。名称遮蔽规则派生类中定义的同名成员(变量或函数)会隐藏基类中所有同名成员,包括参数列表不同的重载版本。ALLHIDDEN访问被隐藏成员使用"基类名::成员名"语法可显式访问被遮蔽的基类成员,如obj.Base::func()调用基类版本的函数。Base::func()与函数重载的区别重载发生在同一作用域内,而名称遮蔽发生在不同作用域(基类与派生类),即使参数列表不同也会被遮蔽。作用域隔离设计建议在派生类中避免使用与基类相同的成员名称,除非确实需要覆盖基类行为;需要调用基类版本时应显式使用作用域限定。显式限定CHAPTER03构造析构与复杂继承关系掌握派生类的构造析构顺序,理解多重继承的二义性问题与虚基类的解决方案Chapter7·Inheritance派生类构造函数的构成与调用顺序派生类构造函数需要同时负责基类成员的初始化和自身新增成员的初始化。通过初始化列表显式调用基类构造函数,若未显式调用则自动调用基类的默认构造函数。执行顺序严格遵循"基类→成员对象→自身"的规则。构造函数格式Derived(参数列表):Base(参数),memberObj(参数),自身成员初始化{函数体}三部分通过初始化列表组织调用顺序(固定)①基类构造函数(按继承声明顺序)→②成员对象构造函数(按声明顺序)→③派生类构造函数体①→②→③默认调用行为若初始化列表中未显式调用基类构造函数,编译器自动调用基类的默认构造函数(无参版本),若基类无默认构造函数则编译报错基类构造函数的选择通过初始化列表传递参数来选择调用基类的哪个构造函数,实现对基类成员的精确初始化继承与派生·析构机制派生类析构函数的调用顺序派生类析构函数的执行顺序与构造函数严格相反:先执行派生类析构函数体,再析构成员对象,最后自动调用基类析构函数。编译器会自动处理基类析构函数的调用,无需在派生类析构函数中显式编写。01执行顺序(与构造相反)①派生类析构函数体→②成员对象析构(按声明的逆序)→③基类析构函数(按继承声明的逆序)02自动调用机制派生类析构函数执行完毕后,编译器自动调用基类的析构函数,不需要也不应该在派生类析构函数中显式调用03与构造函数的镜像关系构造时从基类到派生类(自底向上),析构时从派生类到基类(自顶向下),保证资源的正确分配与释放04虚析构函数的重要性当通过基类指针删除派生类对象时,若基类析构函数不是virtual的,只会调用基类析构函数,导致派生类资源泄漏Chapter7·Inheritance代码示例:构造析构顺序验证通过Base→Derived的构造析构实验,验证构造顺序为'基类→派生类'、析构顺序为'派生类→基类'的镜像规则。01构造顺序:创建Derived对象时,先输出BaseConstructor(基类构造),再输出DerivedConstructor(派生类构造),自底向上完成初始化。02析构顺序:Derived对象销毁时,先输出DerivedDestructor(派生类析构),再输出BaseDestructor(基类析构),自顶向下完成清理。03含成员对象:若Derived包含Member类型对象m,构造顺序为Base→Member→Derived,析构顺序为Derived→Member→Base。04多重继承:按继承声明列表中基类的书写顺序依次构造各基类,与初始化列表中的书写顺序无关。CHAPTER7·INHERITANCE多重继承中的二义性问题多重继承中最常见的问题是成员访问的二义性:当多个基类拥有同名成员时,派生类无法确定应访问哪个基类的版本。二义性成因派生类D继承自B1和B2,两者都有同名成员func(),调用d.func()时编译器无法判断应使用哪个基类的版本。同名冲突作用域解析使用d.B1::func()或d.B2::func()显式指定调用哪个基类的版本,通过作用域解析运算符消除编译器的歧义。B1::func()成员变量冲突若B1和B2都有data成员,D中实际包含两份data副本,不仅造成访问二义性,还浪费内存空间。2份副本设计预防避免在多个基类中使用相同成员名称,或通过引入中间基类统一接口定义来减少冲突,从根本上消除二义性。接口统一INHERITANCE·C++菱形继承与虚基类菱形继承导致派生类中包含共同基类的多份副本,引发数据冗余和访问二义性。C++通过虚基类机制确保共同基类在最终派生类中只保留一份实例。菱形继承结构类B1和B2都继承自类A,类D同时继承B1和B2,形成菱形拓扑结构,D中包含A的两份独立副本。菱形拓扑两份副本的问题D对象中A的成员变量存在两份拷贝,既浪费内存空间,又在访问时产生二义性——编译器无法确定访问路径。访问二义性虚基类声明语法在B1和B2继承A时添加virtual关键字:classB1:virtualpublicA,确保D中只保留一份A的实例。虚继承虚基类的构造规则虚基类的构造函数由最终派生类(D)直接调用,中间类(B1、B2)对虚基类构造函数的调用被编译器忽略。最终派生类INHERITANCE·C++菱形继承结构对比:普通继承vs虚基类对比普通菱形继承与虚基类方案的内存布局差异:普通继承中最终派生类包含共同基类的多份副本,虚基类方案中只保留一份共享实例。虚基类以少量运行时开销为代价,解决了数据冗余和二义性问题。普通菱形继承(无虚基类)D对象的内存中包含:B1子对象(含A副本1)+B2子对象(含A副本2)+D自身成员2份A副本A的成员变量在D中存在两份独立拷贝,修改一份不影响另一份,容易产生逻辑不一致数据冗余访问d.x时产生二义性,必须用d.B1::x或d.B2::x显式指定路径二义性虚基类菱形继承D对象的内存中A的实例只有一份,B1和B2通过虚基类指针共享引用同一份A的数据1份共享实例A的成员变量在D中仅一份拷贝,不存在冗余和数据不一致问题,内存布局更紧凑零冗余d.x可直接访问,无二义性;但虚基类引入虚基类表(vbtable),增加少量运行时寻址开销vbtable开销Chapter7·继承与派生虚基类的代码实现与关键规则虚基类声明需所有中间类统一使用virtual关键字,任何遗漏都会导致多份副本。虚基类构造函数由最终派生类直接负责,中间类调用被编译器忽略。01声明方式classB1:virtualpublicA和classB2:virtualpublicA,两个中间类都必须声明virtual,缺一不可02构造函数调用规则最终派生类D的初始化列表必须直接调用虚基类A的构造函数,如D():A(参数),B1(),B2(){}03中间类构造被忽略B1和B2的构造函数中对A的调用在作为D的基类时会被忽略,仅当B1或B2独立实例化时才生效04虚基类与虚函数的区别virtual在继承中表示虚基类(共享实例),在函数声明中表示虚函数(动态绑定),两者语义完全不同Inheritance·继承与派生赋值兼容规则与对象切片公有继承下,派生类对象可以隐式转换为基类类型(赋值给基类对象、指针或引用),这是多态的基础。但将派生类对象赋值给基类对象时会发生"对象切片"(ObjectSlicing),派生类新增的成员被丢弃,只保留基类部分的数据。01三种合法转换:派生类对象可赋值给基类对象(base=derv)、基类指针可指向派生类对象(Base*p=&derv)、基类引用可绑定派生类对象(Base&r=derv)Implicit02对象切片问题:Baseb=derived;只复制derived中Base部分的数据成员,derived新增的成员被"切掉",信息不可逆地丢失Slicing03指针/引用的优势:Base*p=&derived;不会发生切片,p仍指向完整的derived对象,配合虚函数可实现运行时多态Polymorphism04反向转换的限制:基类对象不能直接赋值给派生类对象,因为基类缺少派生类的特有成员;需要显式强制类型转换(static_cast或dynamic_cast)ExplicitCastINHERITANCE·THISPOINTER派生类中的this指针行为this指向完整派生类对象,调用基类虚函数时运行时仍按实际对象类型选择函数版本this指向派生类成员函数中的this指向完整的派生类对象,而非仅指向派生类新增的成员部分完整对象基类调用基类方法内部的this仍指向同一个派生类对象,但编译器将其静态类型识别为基类指针静态类型地址关系单继承中派生类与基类子对象地址通常相同;多重继承中不同基类子对象地址可能有偏移地址偏移虚函数关联基类方法为虚函数时,运行时根据this实际指向的对象类型选择正确的函数版本动态绑定CHAPTER04继承应用实例与综合实践通过人员管理系统与几何图形系统两个实例,综合运用继承与派生的各项技术CASESTUDY·INHERITANCE实例1:小型公司人员信息管理系统设计以Employee为基类,派生FullTime、PartTime、Manager等子类,通过继承层次实现代码复用与统一管理。基类Employee包含name(姓名)、empID(工号)、hireDate(入职日期)等公共属性和print()虚方法BASECLASS·公共接口FullTimeEmployee公有继承Employee,新增monthlySalary(月薪)成员,重写print()方法输出月薪信息月薪制PartTimeEmployee公有继承Employee,新增hourlyRate(时薪)和hoursWorked(工时),薪资=时薪×工时时薪×工时Manager公有继承Employee,新增department(部门)和bonus(奖金)成员,体现更复杂的层次关系部门+奖金Chapter7·Inheritance人员管理系统:类层次结构实现人员管理系统的类层次遵循"一般→特殊"的设计原则:Employee定义通用接口,FullTimeEmployee和PartTimeEmployee区分薪资模式,Manager在FullTimeEmployee基础上增加管理职责。类层次结构设计多层继承:Employee→FullTimeEmployee→Manager,经理是全职员工的特殊类型,具有多层继承关系独立派生:Employee→PartTimeEmployee,兼职员工独立派生,薪资计算方式与全职员工完全不同同质化管理:使用Employee基类指针数组统一管理所有员工:Employee*staff[100]3层继承关键实现要点虚函数print():基类声明为virtual,使派生类的重写版本可通过基类指针被正确调用虚析构函数:确保通过基类指针delete时能正确调用派生类析构函数释放资源纯虚函数calculateSalary():在基类中声明,强制每个派生类提供各自的薪资计算实现3个核心机制INHERITANCE·CHAPTER07实例2:几何图形系统的抽象基类设计几何图形系统展示了抽象基类在继承体系中的核心作用:Shape定义统一的接口规范(area、perimeter),各具体图形类(Circle、Rectangle、Triangle)提供各自的实现。这种"接口与实现分离"的设计是面向对象程序设计的精髓。Shape抽象基类包含area()和perimeter()两个纯虚函数及name()虚函数,定义所有几何图形的统一接口。ABSTRACTCircle派生类继承Shape,新增radius成员,实现area=πr²和perimeter=2πr的计算逻辑。πr²Rectangle派生类继承Shape,新增width和height成员,实现area=w×h和perimeter=2(w+h)的计算。w×hTriangle派生类继承Shape,新增三边长成员,利用海伦公式计算面积,三边之和为周长。HERONCHAPTER7·INHERITANCE&POLYMORPHISM几何图形系统:多态调用与统一处理通过Shape基类指针数组存储Circle、Rectangle、Triangle等不同派生类对象,利用虚函数的动态绑定实现统一接口调用。同一行代码shapes[i]->area()在运行时自动分发到不同派生类的实现,展示了继承+多态的强大灵活性。统一管理接口Shape*shapes[3]={&circle,&rect,&tri};用基类指针数组存储不同类型的图形对象,实现异构对象的统一管理。Shape*多态遍历计算for循环中调用shapes[i]->area(),运行时根据对象实际类型自动选择对应的area()实现,一次遍历完成所有面积计算。->area()新增图形无需修改代码若要添加Pentagon(五边形),只需新建派生类实现接口,原有的遍历处理代码无需任何修改即可支持新类型。Pentagon开闭原则体现对扩展开放(

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论