C程序设计(自考4737)第6章.ppt
《C程序设计(自考4737)第6章.ppt》由会员分享,可在线阅读,更多相关《C程序设计(自考4737)第6章.ppt(86页珍藏版)》请在装配图网上搜索。
从已有的对象类型出发建立一种新的对象类型 使它继承原对象类型的特点和功能 这种思想是面向对象设计方法的主要贡献 通过对已有类进行特殊化 派生 来建立新的数据类型 就使得面向对象语言具有极大的能力和丰富的表现力 从概念上讲 类的派生创建了一种软件结构 它真实地反映了实际问题 从软件角度来看 类的派生创建了一种类族 派生类的对象也是基类的一种对象 它可以被用在基类对象所使用的任何地方 可以用多态成员函数仔细调整这种关系 以便使派生类在某些地方与它的基类一致 而在别的地方表现出它自身的行为特征 本章主要讨论C 语言继承方面的语法特征和一般的使用方法 第6章继承和派生 主要内容 6 1继承和派生的基本概念6 2单一继承6 3多重继承6 4二义性及其支配规则6 5典型问题分析 交通工具分类层次图的UML表示 交通工具 火车 汽车 飞机 轮船 卡车 旅行车 小汽车 工具车 轿车 面包车 类的派生 这种通过特殊化已有的类来建立新类的过程 叫做 类的派生 从类的成员的角度看 派生类自动地将基类的所有成员作为自己的成员 这叫做 继承 基类 派生类 原有的类叫做 基类 新建立的类则叫做 派生类 基类和派生类又可以分别叫做 父类 和 子类 有时也称为 一般类 和 特殊类 类的派生和继承是面向对象程序设计方法和C 语言最重要的特征之一 继承使得程序员可以在一个较一般的类的基础上很快地建立一个新类 而不必从零开始设计每个类 从一个或多个以前定义的类 基类 产生新类的过程称为派生 这个新类称为派生类 派生的新类同时也可以增加或重新定义数据和操作 这就产生了类的层次性 类的继承是指新类继承基类的成员 继承常用来表示类属关系 不能将继承理解为构成关系 当从现存类中派生出新类时 可以对派生类做如下几种变化 可以增加新的数据成员 可以增加新的成员函数 可以重新定义已有的成员函数 可以改变现有成员的属性 如图6 1所示 C 中有两种继承 单一继承和多重继承 对于单一继承 派生类只能有一个基类 对于多重继承 派生类可以有多个基类 图6 1类的单一继承和多重继承的UML结构图 C 派生类从父类中继承性质时 可使派生类扩展它们 或者对其做些限制 也可改变或删除 甚至不作任何修改 所有这些变化可归结为两类基本的面向对象技术 第一种称为性质约束 即对基类的性质加以限制或删除 第二种称为性质扩展 即增加派生类的性质 派生类的定义格式 单继承的定义格式如下 Class 多继承的定义格式如下 Class 常使用如下三种关键字给予表示 public表示公有继承private表示私有继承protected表示保护继承 派生类的三种继承方式 公有继承 public 基类的公有成员和保护成员作为派生类的成员时 它们都保持原有的状态 而基类的私有成员仍然是私有的私有继承 private 基类的公有成员和保护成员都作为派生类的私有成员 并且不能被这个派生类的子类所访问保护继承 protected 基类的所有公有成员和保护成员都作为派生类的保护成员 并且只能被它的派生类成员函数或友元访问 基类的私有成员仍然是私有的系统的默认值是私有继承 private 基类和派生类的关系 任何一个类都可以派生出一个新类 派生类也可以再派生出新类类A是类C的间接基类 类B是类A的直接派生类基类与派生类之间的关系 可复用的软件构件派生类是基类的具体化派生类是基类定义的延续派生类是基类的组合 6 2 1单一继承的一般形式在C 中 声明单一继承的一般形式为 class派生类名 访问控制基类名 private 成员声明列表protected 成员声明列表public 成员声明列表 6 2单一继承 这里和一般的类的声明一样 用关键字class声明一个新的类 冒号后面的部分指示这个新类是哪个基类的派生类 所谓 访问控制 是指如何控制基类成员在派生类中的访问属性 它是3个关键字public protected和private中的一个 一对大括号 中是用来声明派生类自己的成员的 这和类的声明一样 不再赘述 单继承 每一个类可以有多个派生类每一个派生类只能有一个基类从而形成树形结构 成员访问权限的控制 公有继承public私有继承private保护继承protected 公有继承 public 公有继承方式创建的派生类对基类各种成员访问权限如下 基类公有成员相当于派生类的公有成员 即派生类可以象访问自身公有成员一样访问从基类继承的公有成员 基类保护成员相当于派生类的保护成员 即派生类可以象访问自身的保护成员一样 访问基类的保护成员 对于基类的私有成员 派生类内部成员无法直接访问 派生类使用者也无法通过派生类对象直接访问 例 分析程序中的访问权限 includeclassA public voidf1 protected intj1 private inti1 classB publicA public voidf2 protected intj2 private inti2 classC publicB public voidf3 回答下列问题 派生类B中成员函数f2 能否访问基类A中的成员 f1 i1和j1呢 派生类B的对象b1能否访问基类A中的成员 f1 i1和j1呢 派生类C中成员函数f3 能否访问直接基类B中的成员 f2 和j2呢 能否访问间接基类A中的成员 f1 i1和j1呢 派生类C的对象c1能否访问直接基类B中的成员 f2 和j2呢 能否访问间接基类A中的成员 f1 i1和j1呢 从对 1 4 问题的回答可得出什么结论 Ans 可以访问f1 和j1 而不可以访问i1 可以访问f1 而不可以访问j1和i1 可以访问直接基类中的f2 和j2以及间接基类中的f1 和j1 而不可以访问i2和i1 可以访问直接基类中的f2 和间接基类中的f1 其它的都不可以访问 在公有继承时 派生类的成员函数可访问基类中的公有成员和保护成员 派生类的对象仅可访问基类中的公有成员 私有继承 private 派生类对基类各种成员访问权限如下 基类公有成员和保护成员都相当于派生类的私有成员 派生类只能通过自身的函数成员访问他们对于基类的私有成员 无论派生类内部成员或派生类使用者都无法直接访问 例 分析程序 回答问题 includeclassA public voidf inti cout i endl voidg cout g n classB A public voidh cout h n A f voidmain Bd1 d1 f 6 d1 g d1 h 回答下列问题 执行该程序时 哪个语句会出现编译错 为什么 去掉出错语句后 执行该程序后输出结果如何 程序中派生类B是从基类A继承来的 这种缺省继承方式是哪种继承方式 派生类B中 A f的含义是什么 将派生类B的继承改为公有继承方式该程序输出什么结果 Ans 1d1 g 语句出现编译错误 因为B是以私有继承方式继承类A的 所以B类的对象不可访问A类的成员函数 2d1 g 语句注释后 执行该程序输出以下结果 6h3使用class关键字定义类时 缺省的继承方式是private 4A f 是将基类中的公有成员说明为派生类的公有成员 5将classB A改为classB publicA以后 输出如下 6gh 保护继承 public 保护继承方式创建的派生类对基类各种成员访问权限如下 基类的公有成员和保护成员都相当于派生类的保护成员 派生类可以通过自身的成员函数或其子类的成员函数访问他们对于基类的私有成员 无论派生类内部成员或派生类使用者都无法直接访问 例 分析程序 回答问题 Include includeclassA public A constchar nm strcpy name nm private charname 80 classB publicA public B constchar nm A nm voidPrintName const voidB PrintName const cout name name endl voidmain Bb1 wangli b1 PrintName 回答下列问题 执行该程序将会出现什么编译错 对出现的编译错如何在访问权限上进行修改 修改后使该程序通过编译 执行执行该程序后输出结果是什么 Ans 1编译时出错行是 cout name name endl 错误信息提示name是私有成员不能访问 2在类A中 将private改写为protected 这样就可以通过编译 派生类可访问基类的保护部分 并把它作为派生类的公有部分 但程序其他部分把name作为私有成员 例如在main中 不能运行strcpy s1 bi name 3执行修改后的该程序输出如下结果 wangli classC publicB public voidf3 例 分析程序中的访问权限 includeclassA public voidf1 protected intj1 private inti1 classB publicA public voidf2 protected intj2 private inti2 公有继承public私有继承private保护继承rotected voidmain Bb1b1 f1 b1 f2 6 2 2构造函数和析构函数 1 构造函数派生类对象是由基类中说明的数据成员和派生类中说明的数据成员共同构成基类中说明的数据成员和操作所构成的封装体称为基类子对象派生类的构造函数必须通过调用基类的构造函数类初始化基类子对象在定义派生类的构造函数时除了对自己的数据成员进行初始化外 还必须负责调用基类构造函数使基类的数据成员得以初始化 如果派生类中还有子对象时 还应包含对子对象初始化的构造函数 在派生类中继承的基类成员的初始化 需要由派生类的构造函数调用基类的构造函数来完成 这和初始化对象成员有类似之处 定义派生类的构造函数的一般形式为 派生类名 派生类名 参数表0 基类名 参数表 函数体 派生类构造函数 派生类构造函数的格式如下 派生类构造函数的调用顺序如下 基类的构造函数子对象类的构造函数 如果有的话 派生类构造函数 例 includeclassA public A a 0 cout A sdefaultconstructorcalled n A inti a i cout A sconstructorcalled n A cout A sdestructorcalled n voidPrint const cout a intGeta returna private inta classB publicA public B b 0 cout B sdefaultconstructorcalled n B inti intj intk B cout B sdestrutorcalled n voidPrint private intb Aaa B B inti intj intk A i aa j b k cout B sconstructorcalled n voidB Print A Print cout b aa Geta endl voidmain Bbb 2 bb 0 B 1 2 5 bb 1 B 3 4 7 for inti 0 i 2 i bb i Print Ans A sdefaultconstructorcalled A sdefaultconstructorcalled 构造函数B sdefaultconstructorcalled A sdefaultconstructorcalled A sdefaultconstructorcalled 构造函数B sdefaultconstructorcalled A sconstructorcalled A sconstructorcalled B sconstructorcalled 赋值B sdestructorcalled A sdestructorcalled A sdestructorcalled A sconstructorcalled A sconstructorcalled B sconstructorcalled 赋值B sdestructorcalled A sdestructorcalled A sdestructorcalled 1 5 23 7 4B sdestructorcalled A sdestructorcalled A sdestructorcalled B sdestructorcalled A sdestructorcalled A sdestructorcalled 2 析构函数 当对象被删除时 派生类的析构函数被执行由于析构函数不能被继承 因此在执行派生类的析构函数时 基类的析构函数也将被调用先执行派生类的析构函数 再执行基类的析构函数 例 includeclassM public M m1 m2 0 M inti intj m1 i m2 j voidprint cout m1 m2 M cout M sdestructorcalled n private intm1 m2 classN publicM public N n 0 N inti intj intk voidprint M print cout n endl N cout N sdestructorcalled n private intn N N inti intj intk M i j n k voidmain Nn1 5 6 7 n2 2 3 4 n1 print n2 print Ans 5 6 7 2 3 4 N sdestructorcalled M sdestructorcalled N sdestructorcalled M sdestructorcalled 如何初始化派生类的对象呢 当然也应在派生类中声明一个与派生类同名的函数 假设从基类Point派生一个描述矩形的类Rectangle 类Rectangle继承Point类的两个数据成员作为矩形的一个顶点 再为Rectangle类增加两个数据成员H和W 分别描述它的高和宽 为类Point设计一个构造函数Point int int 和显示数据的函数Showxy 为Rectangle类也设计构造函数Rectangle int int int int 和显示函数Show 由此可见 派生类增加了两个新的数据成员以及相应的成员函数 同时继承Point的全部成员 例6 1 是它们的程序实现 例6 1 使用默认内联函数实现单一继承 includeusingnamespacestd 6 2 2派生类的构造函数和析构函数 classPoint private intx y public Point inta intb x a y b cout Point endl voidShowxy cout x x y y endl Point cout DeletePoint endl classRectangle publicPoint private intH W public Rectangle inta intb inth intw Point a b 构造函 数初始化列表 H h W w cout Rectangle endl voidShow cout H H W W endl Rectangle cout DeleteRectangle endl voidmain Rectangler1 3 4 5 6 r1 Showxy 派生类对象调用基类的成员函数r1 Show 派生类对象调用派生类的成员函数 程序输出如下 Point 调用基类构造函数Rectangle 调用派生类构造函数x 3 y 4 调用基类成员函数Showxy H 5 W 6 调用派生类成员函数Show DeleteRectangle 调用派生类析构函数DeletePoint 调用基类析构函数在派生类中继承的基类成员的初始化 需要由派生类的构造函数调用基类的构造函数来完成 这和初始化对象成员有类似之处 定义派生类的构造函数的一般形式为 派生类名 派生类名 参数表0 基类名 参数表 函数体 冒号后 基类名 参数表 称为成员初始化列表 参数表给出所调用的基类构造函数所需要的实参 实参的值可以来自 参数表0 或由表达式给出 可以像Rectangle那样 在类中直接定义为内联函数 下面是在类说明之外定义的示范 Rectangle Rectangle inta intb inth intw Point a b H h W w cout Rectangle endl 参数表0 有4个参数 基类Point的参数表是自己的2个数据成员 构造函数 包括析构函数 是不被继承的 所以一个派生类只能调用它的直接基类的构造函数 当定义派生类的一个对象时 首先调用基类的构造函数 对基类成员进行初始化 然后执行派生类的构造函数 如果某个基类仍是一个派生类 则这个过程递归进行 当该对象消失时 析构函数的执行顺序和执行构造函数时的顺序正好相反 输出结果也证实了这个结论 现在修改Rectangle的Show函数 使得它可以一次显示x y H和W 怎样修改成员函数Show呢 修改成下面的内容能实现这一目的吗 voidRectangle show cout x x y y H H W W endl 这段简单程序并不能通过编译 类Rectangle有4个私有成员x y H和W 这4个私有成员的来源是不一样的 H和W是Rectangle自己定义的 而x和y是从Point那里继承来的 换句话说 x和y是类Point的私有成员 类的私有成员是只能被它自己的成员函数 不讨论友元 访问的 而Show函数是在类Rectangle中定义的 它是类Point子类的成员函数 并不是类Point的成员函数 因而不能访问x和y 6 2 3类的保护成员 C 语言规定 公有派生类的成员函数可直接访问基类中定义的或基类 从另一个基类 继承来的公有成员 但不能访问基类的私有成员 这和私有成员的定义是一致的 符合数据封装思想 但这样也有问题 就拿上面的程序来说 在类Rectangle看来 x y H和W的地位是平等的 现在希望对它们 一视同仁 但C 语言关于私有成员继承的规定却妨碍这样做 为解决这一矛盾 C 引入了保护成员的概念 在类声明中 关键字protected之后声明的是类的保护成员 保护成员具有私有成员和公有成员的双重角色 对派生类的成员函数而言 它是公有成员 可以被访问 而对其他函数而言则仍是私有成员 不能被访问 因此 要想在类Rectangle中使用统一的Show函数 只要把x和y定义成类Point的保护成员就行了 例6 2 是修改过的程序 includeusingnamespacestd classPoint protected intx y public Point inta intb x a y b voidShow cout x x y y endl 基类的Show 函数 classRectangle publicPoint private intH W public Rectangle int int int int 构造函数原型voidShow cout x x y y H H W W endl 例6 2 演示使用protected成员 定义构造函数Rectangle Rectangle inta intb inth intw Point a b H h W w voidmain Pointa 3 4 Rectangler1 3 4 5 6 a Show 基类对象调用基类Show 函数r1 Show 派生类对象调用派生类Show 函数 程序还演示了在类体内声明Rectangle构造函数原型 类体外定义它 派生类虽然继承了基类的成员函数Show 但它改造了这个函数 使它能显示所有数据 这并不会影响基类函数原来的功能 程序有意定义了Point的对象a 执行a Show 验证之 程序输出如下 x 3 y 4x 3 y 4 H 5 W 6为了将来还可以派生这种新类 建议把H和W也定义成保护成员 然后就可以放心大胆地使用统一的Show函数了 1 公有派生和赋值兼容规则在前面的例子中 使用了公有派生 在公有派生的情况下 基类成员的访问权限在派生类中保持不变 这就意味着在程序中 基类的公有成员在派生类中仍然是公有的 基类的保护成员在派生类中仍然是保护的 基类的不可访问的和私有的成员在派生类中也仍然是不可访问的 所谓 不可访问 是说一个成员甚至对于其自身所在类的成员来说也是不可访问的 这似乎有点儿难以理解 但在类Rectangle中已经遇到过了 x和y就是不可访问的 在根类 不是从别的类派生出来的类 中 没有成员是不可访问的 对于根类来说 可能的访问级别是private public和protected 但是在派生类中 可以存在第4种访问级别 不可访问 inaccessible 6 2 4访问权限和赋值兼容规则 不可访问成员总是由基类继承来的 要么是基类的不可访问成员 要么是基类的私有成员 因此 在公有派生的情况下 可以通过定义派生类自己的成员函数来访问派生类对象继承来的公有和保护成员 但是不能访问继承来的私有成员 这一点很重要 当希望类的某些成员能够被子类所访问 而又不能被其他的外界函数访问的时候 就应当把它们定义为保护的 上一节就是这样做的 千万不能把它们定义成私有的 否则在子类中它们就会是不可访问的 事实上 当这样做了以后 每一只黑狗都是狗 每一个派生类的对象 都是基类的一个对象 于是可以得出赋值兼容规则 所谓赋值兼容规则是指在公有派生情况下 一个派生类的对象可以作为基类的对象来使用的情况 约定类derived是从类base公有派生而来的 则指如下3种情况 赋值兼容规则 所谓赋值兼容规则是指在公有派生情况下 一个派生类的对象可以作为基类的对象来使用的情况 约定类derived是从类base公有派生而来的 则指如下3种情况 派生的对象可以赋给基类的对象 例如 derivedd baseb b d 派生类的对象可以初始化基类的引用 例如 derivedd base但要注意 在后两种情况下 通过pb或br只能访问对象d中所继承的基类成员 下面使用 例6 2 定义的类 编写一个主程序来演示赋值兼容规则并说明这一问题 例6 3 使用Point和Rectangle类演示赋值兼容规则的例子 voidmain 表演公有继承的赋值兼容性规则Pointa 1 2 基类对象aRectangleb 3 4 5 6 派生类对象ba Show b Show Point 派生类对象的地址赋给指向基类的指针 p Show 实际调用的是基类的Show函数Rectangle pb 程序输出如下 x 1 y 2 用a的Show函数输出对象a的数据成员x 3 y 4 H 5 W 6 用b的Show函数输出对象b的 数据成员x 3 y 4 用b的基类Show函数输出其基类数据成员 x 3 y 4 用b的基类Show函数输出其基类数据成员x 3 y 4 H 5 W 6 b的指针调用Show函数输出对 象b的数据成员x 3 y 4 用a的Show函数输出对象a的数据成员以指针为例 为什么 p Show 不是使用b的Show函数输出 x 3 y 4 H 5 W 6 因为 base pb 从输出结果可以看出 它们的含义就是基类用派生类的属性值代替自己原来的属性值 这就是公有派生的 isa 原则 2 isa 和 has a 的区别类与类之间的关系有两大类 一是继承和派生问题 二是一个类使用另一个类的问题 后者的简单用途是把另一个类的对象作为自己的数据成员或者成员函数的参数 继承首先要掌握公有继承的赋值兼容规则 理解公有继承 就是一个 isa 的含义 如果写成类B 子类 公有继承于类A 父类 在可以使用类A 父类 的对象的任何地方 则类B的对象同样也能使用 因为每一个类B的对象 就是一个 类A的对象 另一方面 如果需要一个类B的对象 则类A的对象就不行 每个B都是A 但反之则不然 C 强制执行公有继承的这种规则 例如 classPerson classStudent PublicPerson 这两个类所断言的是 每个学生都是人 但并不是每个人都是学生 可以期望任何一件对于人来说是真实的事情 对学生也是真实的 例如他或她都有生日 对于学生同样也是真实的 但却不能期望每一件对于学生来说是真实的 事情 对所有的人都是真实的 譬如说他或她就读于一所指定的学校 这对于一般的人就不能都是真实的 人 的概念要比 学生 的概念来得更广泛些 而 学生 则是一种特殊类型的 人 在C 范围内 任何一个要求提供Person类型 或指向Person的指针及引用 参数的函数 也能够使用Student对象 或指向Student的指针及引用 作为参数 例如 voiddance constPerson 对 s是Student student也是 isa Person study s 对study p 错 p不都是Student这只是对公有继承才是正确的 仅当Student类是从Person类中公有地派生出来的时候 C 才具有像上面所描述的那种性质 公共继承和 isa 的等价性看起来很简单 但在实际应用中并不容易 有时自己的直觉会产生误导 譬如说 企鹅是鸟是一件事 鸟会飞是另一件事 企鹅是鸟 但不能从鸟会飞误导出企鹅会飞 如果已有一个address类 它描述地址这个概念 现在要建立一个worker类 它描述职工这个概念 每个职工都有一个住址 worker类可以有两种定义形式 使用继承或对象成员 如果采用继承 可以按如下形式定义 classworker publicaddress 如果在worker类中定义一个address类的类对象成员 可以按如下形式定义 classworker privateaddressworkerAddr 使用继承的方法是说职工是一个地址 使用对象的方法声称职工包含有一个地址属性 这两种说法中后者的说法是正确的 即地址只能作为职工的一个属性 在概念上 它们之间没有联系 所以上例中第2种的描述形式是合理的 这就是分层实现 由此可见 类用于描述一类对象的共同特性 不同种类的对象之间的联系使用继承来表示 对象所具有的属性使用类的成员来表示 分层就是一种处理过程 它通过让分层的类里包含被分层的类的对象作为其数据成员 以便把一个类建立在另一些类之上 例如 classString 字符串classAddress 某人居住的地方classPhoneNumber classPerson private Stringname 被分层的对象Addressaddress 被分层的对象 PhoneNumbervoiceNumber 被分层的对象PhoneNumberfaxNumber 被分层的对象public 在这个例子中 Person类要被分层到String Address和PhoneNumber这几个类的上面 这是因为它里面含有这些类型的数据成员 分层也可以叫做包含 嵌入或者聚合 公有继承的意思是 isa 与此相反 分层的意思是指 has a 有一个 或者 is implemented in terms of 是按 实现的 上面的Person类表示的是一种 has a 的关系 一个Person对象有一个名字 一个地址和用于语音及传真通信的两个电话号码 我们不会说一个人就是一个字符串 或者一个人就是一个地址 但会说一个人有一个字符串 并且有一个地址等 许多人做这种分辨时困难不大 搞不清 isa 和 has a 之间区别的人相对来说也很少见 稍微有些伤脑筋的是 isa 和is implemented in terms of之间的差异 这可通过例子来加深理解 在6 5节中 将给出分别使用这两种方法设计的例子 3 公有继承存取权限表派生类一般都使用公有继承 使用基类的有基类本身 派生类 对象和外部函数 对派生类而言 使用它的有派生类本身 对象和外部函数 类中可以使用三种成员 表6 1总结了它们之间的关系 由此可见 保护类型的成员 数据成员和成员函数 介于私有和公有之间 对派生类来讲 它的作用与public成员一样 对类的对象 外部函数以及不属于本类系之外的类来说 它与private成员一样 均是不可访问的 从而保持了类的封装性 访问控制 决定着基类各成员在派生类中的访问权限 上面讨论了最常用的公有继承方式 下面将讨论private和protected继承方式 4 私有派生通过私有派生 基类的私有和不可访问成员在派生类中是不可访问的 而公有和保护成员这时就成了派生类的私有成员 派生类的对象不能访问继承的基类成员 必须定义公有的成员函数作为接口 更重要的是 虽然派生类的成员函数可通过自定义的函数访问基类的成员 但将该派生类作为基类再继续派生时 这时即使使用公有派生 原基类公有成员在新的派生类中也将是不可访问的 下面就来看一个私有派生的例子 例6 4 私有派生的类继续派生的例子 includeusingnamespacestd classPoint 基类定义 private intx y public Point inta intb x a y b voidShow cout x x y y endl classRectangle privatePoint 派生类定义 private 新增私有数据intH W public 新增外部接口Rectangle inta intb inth intw Point a b H h W w voidShow Point Show cout H H W W endl classTest publicRectangle public Test inta intb inth intw Rectangle a b h w voidShow Rectangle Show 在这个例子中 基类的公有成员函数Show通过私有派生成了派生类的私有成员函数 Test虽然是公有派生 但它已经无法使用基类的Show函数 即不能通过 Point Show 方式使用Point类的Show函数 这就彻底切断了基类与外界的联系 私有派生的这一特点不利于进一步派生 因而实际中私有派生用得并不多 5 保护派生派生也可以使用protected 这种派生使原来的权限都降一级使用 private变为不可访问 protected变为private public变为protected 因为限制了数据成员和成员函数的访问权限 所以用得较少 它与private继承的区别主要在下一级的派生中 如果将上例Rectangle改为保护继承方式 则在Test类中可以使用基类的Show函数 则下面函数的定义是正确的 classTest publicRectangle public Test inta intb inth intw Rectangle a b h w voidShow Point Show Rectangle Show 可 以使用基类Point的成员 protected成员举例 intmain Aa a x 5 错误 Bb b x 5 错误 b Function classA protected intx classB publicA public voidFunction voidB Function x 5 一个类从多个基类派生的一般形式是 class类名1 访问控制类名2 访问控制类名3 访问控制类名n 定义派生类自己的成员 类名1继承了类名2到类名n的所有数据成员和成员函数 访问控制用于限制其后的类中的成员在类名1中的访问权限 其规则和单一继承情况一样 多重继承可以视为是单一继承的扩展 例6 5 演示多重继承的例子 6 3多重继承 includeusingnamespacestd classA private inta public voidsetA intx a x voidshowA cout a a endl classB private intb public voidsetB intx b x voidshowB cou b b endl classC publicA privateB private intc public voidsetC intx inty c x setB y voidshowC showB cout c c endl voidmain Cobj obj setA 53 obj showA 输出a 53obj setC 55 58 obj showC 输出b 58c 55 类C从类A公有派生 因此 类A的公有成员 保护成员 在类C中仍是公有的 保护的 类C从类B私有派生 类B的所有成员在类C中是私有的 这些成员在派生类中的可访问性和单一继承中讨论的一样 类B被私有继承 因此 类C负责维护类B的数据成员值和显示 所以在showC和setC中分别调用类B的成员函数showB和setB 使用obj setB 5 和obj showB 都是错误的 6 4 1二义性和作用域分辨符对基类成员的访问必须是无二义性的 如使用一个表达式的含义能解释为可以访问多个基类中的成员 则这种对基类成员的访问就是不确定的 称这种访问具有二义性 6 4二义性及其支配规则 C类的成员函数hunc访问func时 无法确定是访问基类A还是基类B 出现二义性 使用A func 或B func 可以解决这种二义性 例6 6 访问具有二义性的例子 includeusingnamespacestd classA public voidfunc cout a func endl classB public voidfunc cout b func endl Voidgunc cout b gunc endl classC publicA publicB public voidgunc cout c gunc endl voidhunc func 具有二义性 voidmain Cobj obj gunc 不具有二义性 子类覆盖父类 下面是正确的派生类C的实现方法 classC publicA publicB public voidgunc cout c gunc endl voidhun1 A func 使用基类A的funcvoidhun2 B func 使用基类B的func 不过 程序仍然含有二义性 如用C类的对象obj访问函数func 则具有二义性 obj func 不能确定是A的func还是B的func使用成员名限定可以消除二义性 例如 obj A func A的funcobj B func B的funcobj gunc C的guncobj B gunc B的gunc voidmain Cobjobj A func 输出 a func obj B func 输出 b func obj B gunc 输出 b gunc obj C gunc 输出 c gunc obj gunc 输出 c gunc obj hun1 输出 a func obj hun2 输出 b func 从类中派生其他类可能导致几个类使用同一个成员函数名或数据成员名 程序必须确切地告诉编译器使用哪个版本的数据成员或成员函数 类C有两个gunc函数 一个继承自B 另一个是自身定义的 编译器是自C开始沿继承树向上搜索的 所以obj gunc 没有二义性 它使用离C最近的gunc 版本 尽管C具有同一名字的两个不同函数 编译器能调用正确的成员函数 C 作用域规则保证了这一点 如果要使用B的gunc函数 则要使用成员名限定 下面是使用修改后的类C的例子 由上面的例子可知 如果基类中的名字在派生类中再次声明 则派生类中的名字就隐藏了基类中的相应名字 C 可以迫使编译器 看到 当前作用域的外层部分 存取那些被隐藏的名字 这是由作用域分辨符 实现的 这一过程叫做作用域分辨 作用域分辨操作的一般形式为 类名 类标识符 类名 可以是任一基类或派生类名 类标识符 是该类中声明的任一成员名 在上例中 调用函数B gunc 将调用B中的函数gunc 作用域分辨不仅可用于类中 而且可以用在函数调用时 下面的主程序使用上面定义的类 演示了使用作用域分辨的方法 voidmain Cc C c1 newC c C gunc 使用对象 输出c guncc1 C gunc 使用指针 输出c guncc B func 使用对象 输出b funcc1 A func 使用指针 输出a func 基类的成员和派生类的新增的成员都具有类作用域 基类在外层 派生类在内层 如果这时派生类定义了一个和基类成员函数同名的新成员函数 因为参数不同属于重载 所以这里是指具有相同的参数表的成员函数 派生类的新成员函数就覆盖了外层的同名成员函数 在这种情况下 直接使用成员名只能访问派生类的成员函数 只有使用作用域分辨 才能访问基类的同名成员函数 6 4 2派生类支配基类的同名函数 派生类D中的名字N支配基类B中同名的名字N 称为名字支配规则 例6 2 中的基类和派生类都有Show函数 正说明了名字支配规律 再如上面的程序中 C类中的名字支配B类的名字gunc 如果一个名字支配另一个名字 则二者之间不存在二义性 当选择该名字时 使用支配者的名字 例如 obj gunc 使用C的gunc如果要使用被支配者的名字 则应使用成员名限定 例如 6 4 2派生类支配基类的同名函数 obj B gunc 使用B的gunc由于二义性原因 一个类不能从同一个类中直接继承一次以上 例如 下面的方法就是错误的 classderived publicbase publicbase 类体 如果必须这样做 可以使用一个中间类 二义性检查是在访问权限检查之前进行的 因此 成员的访问权限是不能解决二义性问题的 如果涉及几层继承关系 对于任一基类中可以存取的成员 都可以通过作用域分辨进行存取 一般只有派生类中使用的标识符与基类中的标识符同名时 才有必要使用作用域分辨符进行存取 6 5 5典型问题分析初学者学习派生时 最容易产生的概念错误是忘记派生类已经继承了基类的数据成员 例如从Point类派生Line类 忘记Line类继承基类的一个点 反而为派生类设计4个数据成员 其实 Line类只应该再增加一个坐标点 同理 没有充分利用继承的基类的数据成员 又增加新的成员函数 这都是不正确的做法 当基类和派生类具有同名函数时 初学者常常不知道对象应该调用哪一个同名函数 记住 对象一定先调用自己的同名成员函数 如果自己没有同名函数 则调用直接基类的同名函数 以此类推 当然 使用作用域分辨符可以指定要调用的函数 程序设计过程中 一定要注意避免定义的二义性 可以使用作用域分辨符 解决二义性问题- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 程序设计 自考 4737
装配图网所有资源均是用户自行上传分享,仅供网友学习交流,未经上传用户书面授权,请勿作他用。
关于本文