当前位置:文档之家› CCP2009-TIAN-15

CCP2009-TIAN-15

程序设计实习
第十五讲 虚函数与多态
田永鸿
https://www.doczj.com/doc/672759867.html,/cpp2009 p // p / pp https://www.doczj.com/doc/672759867.html,/jiaoxue-CPP/2009/index.htm

上 讲内容回顾 上一讲内容回顾
基本概念:继承、基类、派生类
合理派生
派生类的成员组成、可见性
private/protected成员的继承性 成员的继承性 在派生类的各个成员函数中,不能访问基类中的private成员
派生类的构造、析构
构造顺序:基类、对象成员、派生 析构反之
派生类与基类的指针类型转换
f(派生类) y(基类对象) 基类指针 (强制指针类型转换)派生类指针
2
北京大学《程序设计实习》课程

课 课堂问题(1) 题
1. 判断下列说法是否正确?如果错误,应如何改正?
a) 派生类将继承基类的构造函数; b) 当销毁定义了成员对象的派生类时,先调用成员对象的析构函 数,再调用基类的析构函数,最后调用派生类的析构函数; 数 再调用基类的析构函数 最后调用派生类的析构函数 c) 在声明派生类时,派生类的首部要列出它的所有基类; d) 派生类对象的指针可以直接赋值给基类指针,但反之不行。
2.
如果有,下面的申明中哪个是正确的? class Base {…};
a) ) b) c) ) d) class Derived : public Dervied { } l D i d bli D i d {…}; class Derived : private Base {…}; class Derived : public Base; p class Derived inherits Base {…};
3
北京大学《程序设计实习》课程

课 课堂问题(2) 题
3. 下面的程序输出结果是:
8,8 4,4 9,6 96 请补足class B中缺失的部分(不能重载任何运算符)。 class A { public: int i; main(){ A(int n):i(n) { }; B b1,b2(4); b1 b2(4) }; A a(6); class B :public A{ cout << b1.nVal<< “,” << b1.i << endl; public: cout << b2.nVal<< “,” << b2.i << endl; b1 = a; int nVal; cout << b1.nVal<< "," << b1.i << endl; , ; ……………… } };
北京大学《程序设计实习》课程
B( int n = 8):A(n), nVal(n) { } //同时满足第一/二条件 B( A a ):A(a),nVal(9) { } //基类对象不能赋值给派生类 对象,除非显示定义”用基类 来初始化派生类的构造函数 来初始化派生类的构造函数”, B=A等价于 B(A)
4

课 课堂问题(3) 题
4. 4 对于下面的类定义,找出并解释哪些赋值语句 是非法的?
class base { }; } class derived : public base {}; base b; derived d; a) d = b; b) base & br = d; c) derived * pd = & b; d) b base * pb = & d b d;
a) Wrong b=d; Wrong. b) Right; c) Wrong. 基类对象不能 ) g 用于初始化派生类的指针 (即使使用强制类型转换 也不行) d) Right.
5
北京大学《程序设计实习》课程

内容提要 提要
虚函数和多态 多态的作用 多态的实现 虚函数的应用
虚函数的访问权限 纯虚函数和抽象类 成员函数中调用虚函数 虚析构函数
6
北京大学《程序设计实习》课程

虚函数
在类的定义中,前面有 virtual 关键字的成员函数就 是虚函数。 class base { virtual int get() ; } int base::get() { } virtual 关键字只用在类定义里的函数声明中,写函 数体时不用。
7
北京大学《程序设计实习》课程

回顾:基类与派生类的赋值兼容 类 类 兼
派生类的对象可以赋值给基类对象 类 对象可 给 类对象 派生类对象可以初始化基类引用 派生类对象的地址可以赋值给基类指针 类 象 可 类 派生类对象的指针可以直接赋值给基类指针
8
北京大学《程序设计实习》课程

多 多态
派生类的指针可以赋给基类指针。通过基类指 针调用基类和派生类中的同名虚函数时,
若该指针指向 个基类的对象,那么被调用是基类 若该指针指向一个基类的对象,那么被调用是基类 的虚函数 如果该指针指向一个派生类的对象,那么被调用的 是派生类的虚函数。 这种机制就叫做“多态”。例子
CBase * p = &ODerived; p -> SomeVirtualFunction(); >
9
北京大学《程序设计实习》课程

多 多态
派生类的对象可以赋给基类引用。 通过该基类 引用调用基类和派生类中的同名虚函数时,
若该引用调用一个基类的对象,那么被调用是基类的虚函数 如果该引用调用一个派生类的对象,那么被调用的是派生类的 虚函数。 这种机制也叫做 多态 这种机制也叫做“多态”。例子 例子
CBase & r = ODerived; r.SomeVirtualFunction(); r SomeVirtualFunction();
“多态”就是指上述这两种机制 多态 就是指上述这两种机制
10
北京大学《程序设计实习》课程

多态的例子
class A { public : bli virtual void Print( }; class B:public A { public : virtual void Print( }; class D: public A { p public: virtual void Print( }; class E: public B { virtual void Print( }; }
北京大学《程序设计实习》课程
) { cout << “A::Print”<) { cout << “B::Print” <) { cout << “D::Print” << endl ; } ) { cout << “E::Print” << endl ; }
11

main() { () A * pa; B * pb; D * pd ; E * pe; A a; B b; E e; D d; pb = & b; pd = & d; pe = & e; pa = & a; pa->Print(); // a.Print()被调用,输出:A::Print pa = pb; // 派生类指针赋值给基类指针 pa -> Print(); // b.Print()被调用,输出:B::Print pa = pd; pa -> Print(); // d. Print ()被调用,输出:D::Print pa = pe; pa -> Print() ; // e.Print () 被调用,输出:E::Print }
12

将 将上述例子稍加修改
class A { public : bl virtual void Print( ) { cout << “A::Print”<main函数不变,所得的结果一样 在多层继承的情况下,从定义virtual开始的派生类中同名函数均 为虚函数,无论在这些派生类的同名函数中是否显示加virtual。
北京大学《程序设计实习》课程

动态联编
一条函数调用语句在编译时无法确定调用哪个 条函数调用语句在编译时无法确定调用哪个 函数,运行到该语句时才确定调用哪个函数, 这种机制叫动态联编
14
北京大学《程序设计实习》课程

为什么需要动态联编 要
class A {public: virtual void Get(); }; class B : public A { public: virtual void Get(); }; void MyFunction( A * pa ) { pa->Get(); } pa->Get() 调用的是 A::Get()还是B::Get(),编译 时无法确定,因为不知道MyFunction被调用时 时无法确定 因为不知道M F ti 被调用时 ,形参会对应于一个 A 对象还是B对象。 所以只能等程序运行到 pa->Get()了,才能决定 ()了 才能决定 到底调用哪个Get()
15
北京大学《程序设计实习》课程

多态的又一例子
class Base { public: void fun1() { fun2(); } void virtual fun2() { cout << "B id i l f 2() "Base::fun2()" << endl; } f 2()" dl }; class Derived: public Base { virtual void fun2() { cout << "Derived:fun2()" << endl; } } }; main() { Derived d; Base * pBase = & d B B d; pBase -> fun1(); } // 输出 D i d f 2() 输出: Derived:fun2()
16
北京大学《程序设计实习》课程

调用的次序是:Base::fun1() -> Derived::fun2(); 因为 void fun1() { fun2(); f 2() } 相当于 void fun1() { this->fun2(); thi >f 2() } 编译这个函数的代码的时候,由于fun2()是虚函数 编译这个函数的代码的时候 由于f 2()是虚函数 ,this是基类指针,所以是动态联编 上面这个程序运行到fun1函数中时, this指针指向 上面这个程序运行到f 1函数中时 thi 指针指向 的是d ,所以经过动态联编,调用的是Derived::fun2()
17

多态的作用
多态的实质:父类定义共同接口,子类不同实 现,通过父类以相同的方式操作不同子类的行 为 在面向对象的程序设计中使用多态,能够增强 程序的可扩充性,即程序需要修改或增加功能 的时候,需要改动和增加的代码较少
18
北京大学《程序设计实习》课程

多 多态增强程序可扩充性的例子 强 序可扩充 例子
游戏《魔法门之英雄无敌》
19
北京大学《程序设计实习》课程

类:CSoldier 类:CAngel 类:Dragon
类CPhonex 游戏中有很多种怪物,每种怪物都有 个类与之 游戏中有很多种怪物,每种怪物都有一个类与之 对应,每个怪物就是一个对象
北京大学《程序设计实习》课程
20

相关主题
文本预览
相关文档 最新文档