C++程序设计教程
第 12讲,虚函数( II)
多态性
? 多态性( polymorphism)
Shape
TwoDimShape ThreeDimShape
Circle
Square
Triangle
Sphere
Cube
Tetrahedron
多态性的特性
? 利用虚函数和多态性,程序员可以处理 普遍性 而让执行环
境处理 特殊性 。
? 多态性提高了可扩展性:处理多态性行为的软件可用与接
收消息的对象无关的方式编写。(利用动态联编的特性)
如:插件技术
? 抽象类为类层次结构中的各个成员定义接口。
? 纯虚函数没有实体;
? 纯虚函数必须在派生类中实现。
? 尽管不能实例化抽象基类的对象,但却可以声明抽象基类
的指针。例,如果 Shape 是抽象基类,
? 不能,Shape Ob;
? 但可以,Shape* pOb;
? 多态性特别适合于实现分层的软件系统。
实例研究:工资单系统
class Employee { virtual earnings() const = 0; }
class Boss, public Employee
{ virtual earnings() const; }
class CommissionWorker, public Employee
{ virtual earnings() const; }
class PieceWorker, public Employee
{ virtual earnings() const; }
class HourlyWorker, public Employee
{ virtual earnings() const; }
纯虚函数
必须重载并实现
重载函数可以各
不相同
见 FIG6_1
输出代码
? Employee* ptr; // 基类指针
? Boss b (,John”,”Smith”,800.00);
? ptr = &b; // 指向派生类对象的基类指针
? ptr->print(); // 动态绑定
? cout <<,earned $” << ptr->earnings();
// 动态绑定
? b.print(); // 静态绑定
? cout <<,earned $” << b.earnings();
// 静态绑定
? 实例研究 class Shape
virtual float area() {return 0.0;}
virtual float volume() {return 0.0;}
virtual void printShapeName()=0;
class Point, public Shape
virtual void printShapeName();
class Circle, public Point
virtual float area() ;
virtual void printShapeName();
class Cylinder, public Circle
virtual float area() ;
virtual float volume() ;
virtual void printShapeName();
虚函数
纯虚函数
实
例
研
究
? 动态绑定, 允许向系统中添加新类。编译时可以
不必知道对象的类型。在运行时,虚函数调用和
被调用对象的成员函数匹配。
? 屏幕管理程序 可以不经过重新编译 就可以处理新
的显示对象。
新类和动态绑定
Circle:Draw()
Square:Draw()
Triangle:Draw()
List[i]->Draw();
屏幕管理程序
不需重新编译! Rectangle:Draw() new!
? 在运行时,每一个包含虚函数的类都有一个虚函
数表 vtable,
? 利用这个 vtable实现动态联编,从而实现多态
性。
? 从运行效率来说,多态性和 switch语句编的程
序具有同样的效率。
C++是如何实现动态绑定的
Shape:Draw()
next
TwoDim:Draw()
next
Circle:Draw()
null
多态性在链表设计中的应用
? 多态性( polymorphism)
? 通过继承而相关的不同的类,他们的对象能
够对同一个函数调用作出不同的响应。
virtual Shape:Draw()
Circle:Draw()
Square:Draw()
Triangle:Draw()
for ( int i=0; i < n; i++ )
List[i]->Draw();
形式相
同而实
质不同
!
数组和链表
? 数组( Array)是固定大小的。
? 如,int array[10];
? 链表( List)的大小不固定,它是由一个个结点组
成。每个结点由 数据 和指向下一个结点的 指针 组成,
如,
? struct listnode
? { int data; listnode *next; }
data
*next
&
data
*next
&
data
*next
&
data
*next
& NULL
类型域和 switch语句
class Circle { int DrawCircle(); }
class Square { int DrawSquare(); }
class Triangle { int DrawTriangle(); }
Circle c; Square s; Triangle t;
switch ( paint )
{
case 0,c.DrawCircle(); break;
case 1,s.DrawSquare(); break;
case 2,t.DrawTriangle(); break;
}
用链表记录这些操作
? 我们希望将这些画图的动作记录下来。
? 由于各类的数据结构不同,所以只能记录各类对象
的指针。
? 将这些指针记录到一个数组( Array)或链表
( List)中。(下面着重讲链表,数组也一样)
? 注意,记录到链表中只能是同一个类的指针!
? 所以上面这些操作无法记录到一个链表中,于是我
们利用 派生类指针可转换成其基类指针 的功能,这
样设计这些类。
设计作图类
class Shape { int Draw(); } // base class
class Circle, public Shape // derived class
{ int Draw(); }
class Square, public Shape // derived class
{ int Draw(); }
class Triangle, public Shape // derived class
{ int Draw(); }
设链表为 List,里面记录着 Shape 的指针。
class List {
public,
void operator += (Shape*);
Shape* operator [] (int index);
};
添加结点
转成数组方式
多态性要达到的目标
Circle c;
List += (Shape*)&c;
Square s;
List += (Shape*)&s;
Triangle t;
List += (Shape*)&t;
我们设想,要恢复记录时,只需,
for ( int i=0; i < n; i++ )
List[i]->Draw();
派生类指针转
换成基类指针
只能调用基类的函数 但这是基类的指针
难题,基类的
指针如何访问派
生类的函数?
虚函数
? 虚函数的目的是告诉编译器,对于指向基类的指针
而言,如果它派生类中有同名的函数(被重载),
则优先执行派生类中的该函数。
Shape
Draw();
Circle
Draw();
基
类
指
针
Shape
virtual Draw();
Circle
virtual Draw();
基
类
指
针
基类指针只能访问基类的函数 基类指针可以访问派生类的函数
用虚函数实现目标
class Shape { virtual int Draw(); } // base class
class Circle, public Shape // derived class
{ virtual int Draw(); }
class Square, public Shape // derived class
{ virtual int Draw(); }
class Triangle, public Shape // derived class
{ virtual int Draw(); }
for ( int i=0; i < n; i++ )
List[i]->Draw();
虽然是基类指针,但可优先执
行派生类的有关函数
静态联编与动态联编
? 虚函数是为派生类重载基类的函数而设定的。
? 虚函数的设置导致重载函数指向的不确定性。(所
谓虚函数由此得名)
? 静态联编 static binding
? 在编译的时候就被确定的虚函数
? 动态联编 dynamic binding
? 在运行时才被确定的虚函数
? 如果 Circle c; c.Draw(); 这是静态联编!
? 如果 Circle c; Shape* ps = &c;
? ps->Draw(); 这是动态联编!
?
第 12讲,虚函数( II)
多态性
? 多态性( polymorphism)
Shape
TwoDimShape ThreeDimShape
Circle
Square
Triangle
Sphere
Cube
Tetrahedron
多态性的特性
? 利用虚函数和多态性,程序员可以处理 普遍性 而让执行环
境处理 特殊性 。
? 多态性提高了可扩展性:处理多态性行为的软件可用与接
收消息的对象无关的方式编写。(利用动态联编的特性)
如:插件技术
? 抽象类为类层次结构中的各个成员定义接口。
? 纯虚函数没有实体;
? 纯虚函数必须在派生类中实现。
? 尽管不能实例化抽象基类的对象,但却可以声明抽象基类
的指针。例,如果 Shape 是抽象基类,
? 不能,Shape Ob;
? 但可以,Shape* pOb;
? 多态性特别适合于实现分层的软件系统。
实例研究:工资单系统
class Employee { virtual earnings() const = 0; }
class Boss, public Employee
{ virtual earnings() const; }
class CommissionWorker, public Employee
{ virtual earnings() const; }
class PieceWorker, public Employee
{ virtual earnings() const; }
class HourlyWorker, public Employee
{ virtual earnings() const; }
纯虚函数
必须重载并实现
重载函数可以各
不相同
见 FIG6_1
输出代码
? Employee* ptr; // 基类指针
? Boss b (,John”,”Smith”,800.00);
? ptr = &b; // 指向派生类对象的基类指针
? ptr->print(); // 动态绑定
? cout <<,earned $” << ptr->earnings();
// 动态绑定
? b.print(); // 静态绑定
? cout <<,earned $” << b.earnings();
// 静态绑定
? 实例研究 class Shape
virtual float area() {return 0.0;}
virtual float volume() {return 0.0;}
virtual void printShapeName()=0;
class Point, public Shape
virtual void printShapeName();
class Circle, public Point
virtual float area() ;
virtual void printShapeName();
class Cylinder, public Circle
virtual float area() ;
virtual float volume() ;
virtual void printShapeName();
虚函数
纯虚函数
实
例
研
究
? 动态绑定, 允许向系统中添加新类。编译时可以
不必知道对象的类型。在运行时,虚函数调用和
被调用对象的成员函数匹配。
? 屏幕管理程序 可以不经过重新编译 就可以处理新
的显示对象。
新类和动态绑定
Circle:Draw()
Square:Draw()
Triangle:Draw()
List[i]->Draw();
屏幕管理程序
不需重新编译! Rectangle:Draw() new!
? 在运行时,每一个包含虚函数的类都有一个虚函
数表 vtable,
? 利用这个 vtable实现动态联编,从而实现多态
性。
? 从运行效率来说,多态性和 switch语句编的程
序具有同样的效率。
C++是如何实现动态绑定的
Shape:Draw()
next
TwoDim:Draw()
next
Circle:Draw()
null
多态性在链表设计中的应用
? 多态性( polymorphism)
? 通过继承而相关的不同的类,他们的对象能
够对同一个函数调用作出不同的响应。
virtual Shape:Draw()
Circle:Draw()
Square:Draw()
Triangle:Draw()
for ( int i=0; i < n; i++ )
List[i]->Draw();
形式相
同而实
质不同
!
数组和链表
? 数组( Array)是固定大小的。
? 如,int array[10];
? 链表( List)的大小不固定,它是由一个个结点组
成。每个结点由 数据 和指向下一个结点的 指针 组成,
如,
? struct listnode
? { int data; listnode *next; }
data
*next
&
data
*next
&
data
*next
&
data
*next
& NULL
类型域和 switch语句
class Circle { int DrawCircle(); }
class Square { int DrawSquare(); }
class Triangle { int DrawTriangle(); }
Circle c; Square s; Triangle t;
switch ( paint )
{
case 0,c.DrawCircle(); break;
case 1,s.DrawSquare(); break;
case 2,t.DrawTriangle(); break;
}
用链表记录这些操作
? 我们希望将这些画图的动作记录下来。
? 由于各类的数据结构不同,所以只能记录各类对象
的指针。
? 将这些指针记录到一个数组( Array)或链表
( List)中。(下面着重讲链表,数组也一样)
? 注意,记录到链表中只能是同一个类的指针!
? 所以上面这些操作无法记录到一个链表中,于是我
们利用 派生类指针可转换成其基类指针 的功能,这
样设计这些类。
设计作图类
class Shape { int Draw(); } // base class
class Circle, public Shape // derived class
{ int Draw(); }
class Square, public Shape // derived class
{ int Draw(); }
class Triangle, public Shape // derived class
{ int Draw(); }
设链表为 List,里面记录着 Shape 的指针。
class List {
public,
void operator += (Shape*);
Shape* operator [] (int index);
};
添加结点
转成数组方式
多态性要达到的目标
Circle c;
List += (Shape*)&c;
Square s;
List += (Shape*)&s;
Triangle t;
List += (Shape*)&t;
我们设想,要恢复记录时,只需,
for ( int i=0; i < n; i++ )
List[i]->Draw();
派生类指针转
换成基类指针
只能调用基类的函数 但这是基类的指针
难题,基类的
指针如何访问派
生类的函数?
虚函数
? 虚函数的目的是告诉编译器,对于指向基类的指针
而言,如果它派生类中有同名的函数(被重载),
则优先执行派生类中的该函数。
Shape
Draw();
Circle
Draw();
基
类
指
针
Shape
virtual Draw();
Circle
virtual Draw();
基
类
指
针
基类指针只能访问基类的函数 基类指针可以访问派生类的函数
用虚函数实现目标
class Shape { virtual int Draw(); } // base class
class Circle, public Shape // derived class
{ virtual int Draw(); }
class Square, public Shape // derived class
{ virtual int Draw(); }
class Triangle, public Shape // derived class
{ virtual int Draw(); }
for ( int i=0; i < n; i++ )
List[i]->Draw();
虽然是基类指针,但可优先执
行派生类的有关函数
静态联编与动态联编
? 虚函数是为派生类重载基类的函数而设定的。
? 虚函数的设置导致重载函数指向的不确定性。(所
谓虚函数由此得名)
? 静态联编 static binding
? 在编译的时候就被确定的虚函数
? 动态联编 dynamic binding
? 在运行时才被确定的虚函数
? 如果 Circle c; c.Draw(); 这是静态联编!
? 如果 Circle c; Shape* ps = &c;
? ps->Draw(); 这是动态联编!
?