12_Derive

主要讲解继承多态 继承是面向对象技术的一个重要概念。如果一个类 B 继承自另一个类 A,就把这个 B 称为 A 的派生类,而把 A 称为 B的父类。继承可以使得派生类具有父类的各种属性和功能,而不需要再次编写相同的代码。派生类在继承父类的同时,还可以通过重新定义某些属性或改写某些方法,来更新父类的原有属性和功能,或增加新的属性和功能。 多态性是面向对象程序设计的关键技术之一,指的是接口的多种不同的实现方式。比如调用同一个函数名的函数,但实现完全不同的功能。若程序设计语言不支持多态性,不能称为面向对象的语言。
展开查看详情

1.第十二讲 继承 与 多态

2.2 继承与 多态 继承 是面向对象技术的一个重要概念。如果一个类 B 继承自另一个类 A ,就把这个 B 称为 A 的派生类,而把 A 称为 B 的父类。继承可以使得派生类具有父类的各种属性和功能,而不需要再次编写相同的代码。派生类在继承父类的同时,还可以通过重新定义某些属性或改写某些方法,来 更新 父类的原有属性和功能,或 增加 新的属性和功能。 多态性 是面向对象程序设计的关键技术之一,指的是接口的多种不同的实现方式。比如调用同一个函数名的函数,但实现完全不同的功能。若程序设计语言不支持多态性,不能称为面向对象的语言。

3.3 怎么定义派生类 如何继承父类的成员 怎样定义派生类的构造函数 派生 类的析构函数 类型兼容规则:派生类对象可以代替父类对象 如果解决多重继承时重复继承问题 — 虚父类 继承 / 派生 在 已有的类的基础上定义新的 类 —— 类的继承或派生

4.4 在已 有类的基础上产生新类的过程就是类的 派生 原有类称为 基类 或 父类 ,新类称为 派生类 或 子类 类的 继承 :派生类继承了父类 的 已有 特 性(数据和函数) 派生类可以加入新的特性 派生类也可以作 为父类,派 生新 的子类 --- 继承层次结构 继承和派 生提高了代 码 的可重用性, 有利于软件开发 例: 交通工具 火车 汽车 飞机 大卡车 小轿车 面包车 什么是继承与 派生

5.5 class 派生类名 : 继承方式 父类名 1, 继承方式 父类名 2, ... { 派生类成员声明 ; }; 一个派生类可以有多个父类( 多重继承 ) 单继承 :一个派生类只有一个父类 一个父类可以派生出多个子类-- 类族 继承 是可传递 的:从父类继承的特性可以传递给新的子类 继承方式:规定了如何访问从父类继承的成员 继承方式有三种: public 、 protected 、 private 派生类成员:从父类继承的成员 + 新增加的成员 怎么定义派生 类

6.6 注:构造函数和 析构函 数不能被继承 ! 吸收父类成员 派生类包含父类中 除构造和析构函数外的所有非静态成员 改造父类成员 - 父类成员的访问控制(通过继承方式实现) - 对父类成员的覆盖或隐藏(如同名隐藏,即新成员与父类成员同 名(若是函数,则形参也要一样), 则只能访问新成员) 添加新成员 根据实际需要,添加新的数据成员或函数成员 派生过程 :吸收父类成员,改造父类成员,添加新成员 类的派生 过程

7.7 访问控制: 能否访问 / 怎样访问从父类继承得来的成员 这 里主要强调派 生类中 新增成员 和 派生类外部函数 访问 派生类中从父类继承的成 员 公有继承( public ) - 父类的公有和保护成员的访问属性保持不变 - 父类的私有成员不可直接访问 从父类继承的成员函数对父类成员的访问不受影响 继承方式不同,访问控制不同 派生类成员的访问 控制

8.8 例:公有继承 class Point { public: void initPoint(float x=0, float y=0); void move(float offx, float offy); float getx() const {return x;} float gety() const {return y;} private: float x, y; } class Rectangle: public Point { public: void initRect(float x,float y,float w,float h) { initPoint(x,y) ; this->w=w; this->h=h; } float geth() const {return h;} float getw() const {return w;} private: float h, w; // 新增私有成员 }

9.9 私有继承( private ) - 父类的公有和保护成员都成为派生类的私有成员 - 父类的私有成员不可直接访问 私有继承后,父类成员(特别是公有函数)无法在以后的派生类中直接发挥作用,相当于终止了父类功能的继续派生。因此,私有继承较少使用。 保护继承( protected ) - 父类的公有和保护成员都成为派生类的保护成员 - 父类的私有成员不可直接访问 与私有继承的区别:父类成员(特别是公有函数)可以在以后的派生中作为保护成员继承下去。 私有继承和保护 继承

10.10 父类成员 函数 访问父类 成 员: 正常访问 派 生 类成员 函数访问派生 类新增成员: 正常访问 父类成员 函数访问派生 类新增成员: 不能访问 派生类成员函数访问父类成员: 继承方式 + 成员本身访问属性 非成员函数访问派生类所有成员: 只能访问公有 成员 访问控制 小结 派生类成员按 访问属性 可划分为下面四类: - 不可访问成员:父类的私有成员 - 私有成员:父类继承的部分成员 + 新增的私有成员 - 保护成员:父类继承的部分成员 + 新增的保护成员 - 公有成员:父类继承的部分成员 + 新增的公有成员 如果没有指定继承方式,则缺省为 private

11.11 派生 类的构造函数 派生 类不能继承父类的构造和析构 函数 派生类的构造函数只负责 新增成员 的初始化 从 父类继承的 成员需通过调用 父 类的构造 函数 进行初始化

12.12 - 派生类对象的(数据)成员:父类成员 + 新增成员 - 初始化:父类成员初始化 + 新增成员初始化 - 数据成员: 基本类型数据成员 + 其它类 的对象 - 由于父类成员需要调用父类构造函数,因此在派生类构造函数的参数中,有一些参数是传递给父类的构造函数的 派生类对象的 初始化 派生 类名 ( 总参数列表 ): 父 类 1 ( 参数 ),..., 父类 n ( 参数 ), 成员 对象 1( 参数 ), ..., 成员对象 m( 参数 ) { 新增数据成员的初始化( 不 包括继承的父类成员) ; } 总参数列 表 中 的 参数 需要带数据类型(形参),其他不需要

13.13 若父类 使用 缺省 (即不 带形参)构造函数,则可以省略 若成员对象 使用 缺省(即不带形参)构造 函数来初始化, 也可以 省略 派生类构造函数执行的一般次序 - 调用父类的构造函数,按 被继承时声明的顺序 执行 - 对派生类新增成员对象初始化,按它们在 类中声明的顺序 - 执行派生类的构造函数体的内容 派生类构造 函数

14.14 例 1 :派生类构造函数 class B1 // 类 B1 ,构造函数有参数 { public: B1( int i ) { cout <<"constructing B1 "<< i << endl ;} }; class B2 // 类 B2 ,构造函数有参数 { public: B2( int j) { cout <<"constructing B2 "<<j<< endl ;} }; class B3 // 类 B3 ,构造函数无参数 { public: B3() { cout <<"constructing B3 *"<< endl ;} };

15.15 class C: public B2, public B1, public B3 // 派生新类C , 注意 父 类名的顺序 { public : // 派生类的公有成员 C( int a, int b, int c, int d) : B1(a), memberB2(d), memberB1(c), B2(b ) { x=a; } // 注意 父 类名的个数与顺序 // 注意成员对象名的个数与顺序 private: // 派生类的私有对象成员 B1 memberB1; B2 memberB2; B3 memberB3; int x; }; int main() { C obj (1,2,3,4 ); } 屏幕输出结果: constructing B2 2 constructing B1 1 constructing B3 * constructing B1 3 constructing B2 4 constructing B3 * ex12_derive01.cpp 例 1 :派生类构造函数(续)

16.16 class Person // 父类 { public : Person(string & str , int age ) { name= str ; this- >age = age; } void show() { cout << "Name: " << name << endl ; cout << "Age: " << age << endl ; } private: string name; // 姓名 int age; // 年龄 }; 例 2 :派生类构造函数

17.17 class Student : public Person // 派生类 { public: Student(string & str , int age, int stuid ) : Person( str , age ) // 父类数据成员初始化 { this-> stuid = stuid ; } void showStu () { this- >show(); // 不能直接访问 name 和 age cout << " Stuid : " << stuid << endl ; } private: int stuid ; // 学号 }; 例 2 :派生类构造函数(续)

18.18 int main() { string str ="Xi Jiajia "; Student stu1( str , 18, 20150108); stu1.showStu(); return 0; } ex12_derive02.cpp 思考:如果 Student 中的成员函数 showStu 也取名为 show ,该如何处理? 作用域分辨符 :: 例 2 :派生类构造函数(续)

19.19 派生类成员的标识与访问 派生类成员的标识问题: 如何处理成员同名问题? 派生类成员:所有 父类的成员 + 新增成员 解决方法:作用域 分辨 符,即连续的 两个冒号,即 “ :: ” —— 用来限定要访问的成员所在的类 类名 :: 成员名 // 数据成员 类名 :: 成员名 ( 参数 ) // 函数成员

20.20 如果 存在两个或多个具有包含关系的作用域,外层作用域声明的标识符在内层作用域可见,但如果在内层作用域声明了同名标识符,则外层标识符在内层不可见。 父类是外层,派生类是内层 若在派生类中声明了与父类同名的新函数, 即使函数参数表不同 ,从父类继承的同名函数的所有重载形式都会 被屏蔽 如何访问被屏蔽的成员: 类名 + 作用域 分辨符 若派生类有多个父类,且这些父类中有同名标识符,则必须使用作用域分辨符来指定使用哪个父类的标识符! 通过 作用域分辨符 就明确地唯一标识了派生类中从父类继承的成员,从而解决了成员同名问题。 屏蔽规则

21.21 派生类复制构造函数的作用: 调用父类 的复制构造函数 完成父类 部分的复制,然后再复制派生类的部分 。 在 定义派生类的复制构造函数时,需要为父类相应的复制构造函数传递参数 class C: public B { C(const C &v) : B(v); ... ... }; C::C(const C &v) : B(v) { ... ... } 例: 派生 类 的复制构造 函数

22.22 派生类的析构函数只负责新增 非对象成员 的清理工作 派生类析构函数的定义与没有继承关系的类的析构函数一样 父类和新增对象成员的清理工作由父类和对象成员的析构函数负责 析构函数的执行顺序与构造函数相反: - 执行派生类析构函数体 - 执行派生类对象成员的析构函数 - 执行父类的析构函数 派生类析构函数

23.23 在 需要父类对象出现的地方,可以使用派生类(以公有方式继承)的对象来替代。 通俗解释:公有派生类实际具备了父类的所有功能,凡是父类能解决的问题,公有派生类都可以解决。 类型兼容规则中的替代包括以下情况: - 派生类的对象可以隐式转化为父类对象 - 派生类的对象可以初始化父类的引用 - 派生类的指针可以隐式转化为父类的指针 用派生类对象替代父类对象后, 只能使用从父类继承的 成员,即派生 类只能发挥父类的作用 类型兼容 规则 / 多态

24.24 在 多重继承 时,如果派生类的部分或全部父类是从另一个 共同父类 派生而来,则在最终的派生类中会保留该间接 共 同 父 类 数据成员的多份同名成员。 这时不仅会 存在标识符同名 问题,还会占用额外的 存储空间 ,同时也增加 了访问这些成员时的困难 ,且容易 出错 。而事实上,在 很多情况下,我们只需要一个这样 的成员副本 (特别是函数成员 )。 虚父类: 当某个类的部分或全部父类是从另一个共同父类派生而来时,可以将这个共同父类设置成虚父类,这时从不同路径继承来的同名数据成员在内存中只存放一个副本,同一个函数名也只有一个映射。 虚 父 类

25.25 class 派生类名 : virtual 继承方式 父类名 { ... ... } ; class A { ... }; class B : virtual public A { ... }; class C : virtual public A { ... }; class D : public B, public C { ... }; 例: 虚 父 类的声明

26.26 在直接或间接继承虚父类的所有派生 类中, 都必须在构造函数 的 初始化列表 中列出对虚父类的初始化。 class A { public: A( int x); ... }; class B : virtual public A { public: B( int x) : A(x); ... }; class C : public B { public: C( int x) : A(x), B(x); ... }; 例: 虚 父 类 及其派生类的构造 函数

27.27 虚父类 并不是在 声明父类时 声明的,而是 在声明派生类时,指定继承方式时声明 的 。 一个父类 可以在 生成某个 派生类时作为 虚父类 ,而在生成另一个派生类时不作为 虚父类。 为了保证 虚父类成员在 派生类中只继承一次,应当在 该父类 的所有 直接派生类 中 声明其为虚父类 。否则 仍然可能会 出现 对该父类 的多次继承。 虚 父 类几点注记

28.28 同样 的消息被不同类型的对象接收 时会导致 不同的行为 多态性是面向对象程序设计的重要特征之一 多态 的实现: - 函数重载,运算符重载 - 虚 函数 - 模板 消息:对类的成员函数的调用 不同行为:不同的实现(功能),即调用不同函数 类的多态性

29.29 课后练习 课后练习(自己练习)