第八章 多态性 (polymophism)
本章主要内容
1,多态的概念
2,虚函数 —— 重点
3,静态绑定、动态绑定与动态绑定的实现机制
4,纯虚函数与抽象类 —— 重点
21:53:08
§ 1 多态的概念
多态性指一个名字,多种语义,或接口相同,多种实现
C++支持两种形式的多态
1.静态多态(编译时多态)
2.动态多态(运行时多态)
21:53:08
静态多态
静态多态主要指函数和运算符的重载例:函数的重载
int abs(int x){ return x; }
float abs(float x){ return x; }
long abs(long x){ return x; }
void main()
{
cout << abs(-100) << endl;
cout << abs(-2.5f) << endl;
cout << abs(123456789L) << endl;
}
21:53:08
例:函数的重载(续)
class X{
public:
void Show(int,char);
void Show(char *,char);
};
void main()
{
X xObj;
xObj.Show(1,’a’);
xObj.Show(“张三,,’b’);
}
21:53:08
例:函数重载 (续 )
class X
{
public:
void show(){ }
};
class Y:public X{
public:
void show(int){ }
void show(){ }
};
void main(){
X xObj;
Y yObj;
xObj.Show();
yObj.Show(10);
yObj.Show();
yObj.X::Show();
}
结论:
重载函数,运算符重载对应的函数的调用和函数体之间对应关系的建立,
是由编译程序和连接程序根据函数的参数数目,参数类型的匹配关系,在编译连接阶段完成的,所以静态多态又称为编译 -连接时多态 ( 简称 编译时多态 )
21:53:08
动态多态
与继承机制有关,建立在继承机制之上
在继承机制中,能使用基类对象的地方就能用派生类对象 ( upcast — 向上转换 )
例,.upcast(向上转换 )
动态多态在继承机制之上靠 虚成员函数 实现
对虚函数的调用和函数体之间的对应关系不是在编译,
连接阶段确定,而是在程序运行过程中动态的确定该函数执行哪一个函数体,因此动态多态又称为 运行时多态基类的指针可以指向派生类的对象,基类的引用可以引用派生类的对象 。 导致编译时无法预知基类指针将来会指向哪个派生类的对象,也无法预知基类的引用将来会引用哪个派生类的对象
21:53:08
§ 2 虚函数
虚函数是在基类中使用关键字 virtual进行声明,在派生类中 重新定义 (override)的成员函数例:虚函数
class X
{
public:
virtual void f()
{
cout <<,X::f()”;
}
};
class Y:public X
{
public:
virtual void f()
{
cout <<,Y::f()”;
}
};
21:53:08
虚函数的作用
能使用基类对象的地方就能使用派生类对象,故基类指针可以指向派生类对象,基类引用也可以引用派生类对象
基类指针指向派生类对象,通过指针调用所指向对象中的虚函数,C++根据所指向对象的类型,在 运行时确定 调用哪一个类的虚函数
基类引用引用派生类对象,通过引用调用所引用对象中的虚函数,C++根据所引用对象的类型,在 运行时确定 调用哪一个类的虚函数
基类指针指向不同派生类的对象,基类引用引用不同派生类的对象时,可以执行虚函数的不同版本
21:53:08
例 1:非虚成员函数
class vehicle{
public:
void drive()
{ cout << "drive a generic vehicle" << endl;}
};
class car:public vehicle{
public:
void drive(){cout << "drive a car,<< endl;}
};
void f(vehicle & v){ v.drive(); }
void g(vehicle * pV){ pV->drive(); }
void main(){
vehicle vh; car c;
f(vh); f(c); //分别调用哪一个类中的 drive()?
g(&vh); g(&c); //分别调用哪一个类中的 drive()?
vehicle *pVh;
pVh=&vh; pVh->drive();
pVh=&c; pVh->drive();
}
引用基类对象,调用基类成员函数引用派生类对象,
调用基类成员函数指向基类对象,调用基类成员函数指向派生类对象,
调用基类成员函数
21:53:08
例 1:虚成员函数
class vehicle{
public:
virtual void drive()
{ cout << "drive a generic vehicle" << endl;}
};
class car:public vehicle{
public:
virtual void drive(){cout <<"drive a car"<< endl;}
};
void f(vehicle & v){ v.drive(); }
void g(vehicle * pV){ pV->drive(); }
void main(){
vehicle vh; car c;
f(vh); f(c); //分别调用哪一个类中的 drive()?
g(&vh); g(&c); //分别调用哪一个类中的 drive()?
}
引用基类对象,调用基类成员函数引用派生类对象,调用派生类成员函数指向基类对象,调用基类成员函数指向派生类对象,调用派生类成员函数结论:
基类指针指向不同派生类的对象,基类引用引用不同派生类的对象时,执行的是相应派生类中的虚函数
21:53:08
例 2:非虚成员函数
class CGraph{
int x,y;
public:
CGraph(int xx,int yy){ x = xx; y = yy;}
~ CGraph(){ }
void Draw(CDC &dc)
{ dc.TextOut(x,y,"This is a Graph Object"); }
int getX()const{return x;}
int getY()const{return y;}
};
21:53:08
例 2:非虚成员函数(续)
class CRectangle:public CGraph{
int left,top,right,bottom;
public:
CRectangle(int lft,int tp,int rgt,int btm)
:CGraph((rgt+lft)/2,(tp+btm)/2)
{
left = lft; top = tp; right = rgt; bottom = btm;
}
~ CRectangle(){ }
void Draw(CDC & dc)
{ dc.Rectangle(left,top,right,bottom); }
};
21:53:08
例 2,非虚成员函数 (续 )
#include "Graph.h"
class CCircle,public CGraph{
int r;
public:
CCircle(int xx,int yy,int rr):CGraph(xx,yy){r=rr;}
~ CCircle(){ }
void Draw(CDC & dc)
{dc.Ellipse(getX()-r,getY()-r,getX()+r,getY()+r);}
};
CGraph g(100,30),*pG=&g;
CCircle cir(100,100,50);
CRectangle rct(100,200,300,300);
pG->Draw(dc); //调用哪一个 Draw()?
pG=○
pG->Draw(dc); //调用哪一个 Draw()?
pG=&rct;
pG->Draw(dc); //调用哪一个 Draw()?
指向 CGraph类的对象,
调用 CGraph类的 Draw()
指向 CCircle类的对象,
调用 CGraph类的 Draw()
指向 CRectangle
类的对象,调用
CGraph类的 Draw()
21:53:08
例 2:虚成员函数
class CGraph{
int x,y;
public:
CGraph(int xx,int yy){ x = xx; y = yy;}
~ CGraph(){ }
virtual void Draw(CDC &dc)
{dc.TextOut(x,y,"This is a Graph Object");}
int getX()const{return x;}
int getY()const{return y;}
};
21:53:08
例 2:虚成员函数(续)
class CRectangle:public CGraph{
int left,top,right,bottom;
public:
CRectangle(int lft,int tp,int rgt,int btm)
:CGraph((rgt+lft)/2,(tp+btm)/2)
{
left = lft; top = tp; right = rgt; bottom = btm;
}
~ CRectangle(){ }
virtual void Draw(CDC & dc)
{ dc.Rectangle(left,top,right,bottom); }
};
21:53:08
例 2,虚成员函数 (续 )
#include "Graph.h"
class CCircle,public CGraph{
int r;
public:
CCircle(int xx,int yy,int rr):CGraph(xx,yy){r=rr;}
~ CCircle(){ }
virtual void Draw(CDC & dc)
{dc.Ellipse(getX()-r,getY()-r,getX()+r,getY()+r);}
};
CGraph g(100,30),*pG=&g;
CCircle cir(100,100,50);
CRectangle rct(100,200,300,300);
pG->Draw(dc); //调用哪一个 Draw()?
pG=○
pG->Draw(dc); //调用哪一个 Draw()?
pG=&rct;
pG->Draw(dc); //调用哪一个 Draw()?
指向 CGraph类的对象,
调用 CGraph类的 Draw()
指向 CCircle类的对象,
调用 CCircle类的 Draw()
指向 CRectangle类的对象,调用 CRectangle
类的 Draw()结论:
基类指针指向不同派生类的对象,基类引用引用不同派生类的对象时,执行的是相应派生类中的虚函数
21:53:08
关于虚函数的说明
虚函数只能是类的成员函数,而不能是普通函数或类的友元函数
关键字 virtual只在声明虚成员函数时使用,定义该成员函数时不需用 virtual进行修饰
无论继承层次有多深,基类中声明的虚函数,在派生类中自动为虚函数,在派生类中声明该虚函数时可不用关键字
virtual
21:53:08
例:继承层次
class Root{
public:
virtual void vf()
{cout << "Base::vf()" << endl; }
};
class FirstLevel:public Root{
public:
void vf()
{ cout << "FirstLevel::vf()" << endl; }
};
class SecondLevel:public FirstLevel{
public:
void vf()
{ cout << "SecondLevel::vf()" << endl; }
};
同一虚函数的不同版本
21:53:08
例:继承层次(续)
void main(){
Root * ptr;
ptr = new Root;
ptr->vf();
delete ptr;
ptr = new FirstLevel;
ptr->vf();
delete ptr;
ptr = new SecondLevel;
ptr->vf();
delete ptr;
}
21:53:08
关于虚函数的说明(续)
派生类中新添加了与基类中声明的虚函数同名的成员函数,
但两者的函数原型不同 (overload),则派生类中新添加的成员函数不是基类中同名函数的虚函数
class Base{
public:
virtual void f(int x)
{cout << "Base::vf(int)" << endl; }
};
class Derived:public Base{
public:
void f()
{ cout <<,Derived::vf()" << endl; }
};
派生类中的 f()不是基类中虚函数 f(int)的新版本派生类中的 f()丢失了虚的特性
21:53:08
关于虚函数的说明(续)
继承的类层次中,若某个派生类中没有显式重新定义最上层基类中声明的虚函数,则该派生类中的虚函数为从其直接基类中继承而来的虚函数
class Base{
public:
virtual void f(){ cout << "Base::f()" << endl;}
};
class Derived1:public Base{
public:
void f(){ cout << "Derived1::f()" << endl;}
};
class Derived2:public Derived1{
public:
void g(){ cout << "Derived2::g()" << endl;}
};
void main(){
Base *ptr;
ptr = new Derived1;
ptr->f();
delete ptr;
ptr = new Derived2;
ptr->f();
delete ptr;
}
21:53:08
关于虚函数的说明(续)
虚函数要发挥其作用,必须使用 基类指针指向派生类对象,
或者用基类引用引用派生类对象,再通过基类指针或基类引用调用对象中的虚成员函数
直接使用基类对象调用虚函数,则只能调用基类中的版本
class Base{
public:
virtual void f(){ cout << "Base::f()" << endl;}
};
class Derived:public Base{
public:
void f(){ cout << "Derived1::f()" << endl;}
};
Base b;
Derived d;
b.f(); //调用哪一个 f()?
b = d;
b.f(); //调用哪一个 f()?
直接通过基类对象调用虚函数,编译连接时就能确定对象 b的类型,所以只能调用基类中的 f()
21:53:08
关于虚函数的说明(续)
类的构造函数不能声明为虚函数
若类中至少包含一个虚函数,则该类的析构函数应该声明为虚析构函数例:非虚析构函数
class Base{
public:
~ Base()
{ cout << "Base destructor is called" << endl;}
virtual void f(){cout << "Base::f()" << endl;}
};
21:53:08
例:非虚析构函数(续)
class Derived:public Base{
char *v; int len;
public:
Derived(char *str){
len = strlen(str);v=NULL;
if (len!=0){
v = new char[len + 1];strcpy(v,str);
}
}
~ Derived(){
cout<<"Derived destructor is called"<<endl;
if(v) delete [] v;
}
void f(){cout << "Derived::f()" << endl; }
};
void main(){
Base * ptr;
ptr = new Derived(“张三,);
ptr->f();
delete ptr;
}
基类指针 ptr指向了派生类对象,因析构函数不是虚函数,所以编译连接时已经确定 delete时调用基类的析构函数而 不调用派生类的析构函数,故该派生类对象中的
v所指空间没有被释放
21:53:08
例:虚析构函数
class Base{
public:
virtual~ Base()
{ cout << "Base destructor is called" << endl;}
virtual void f(){cout << "Base::f()" << endl;}
};
class Derived:public Base{
char *v; int len;
public:
Derived(char *str){
len = strlen(str);v = NULL;
if(len!=0){v=new char[len + 1];strcpy(v,str);}
}
void f(){ cout << "Derived::f()" << endl;}
virtual ~ Derived(){
cout << "Derived destructor is called" << endl;
if(v)delete []v;
}
};
21:53:08
例:虚析构函数(续)
void main(){
Base * ptr;
ptr = new Derived("李四 ");
ptr->f();
delete ptr;
}
基类指针 ptr指向了派生类对象,此时析构函数是虚函数,
所以 编译连接时无法确定 delete调用哪一个类的析构函数
运行过程中 执行 delete运算符的操作时,系统先确定 ptr
所指对象的类型 ( 为 Derived),从而调用相应类的析构函数 ( 应为 Dervied类的析构函数,Derived类的析构函数又会自动调用基类 Base的析构函数 )
21:53:08
upcast(向上转换)与 downcast(向下转换)
在继承的层次中,可认把派生类对象为是一种基类对象,
这称为 upcast( 向上转换 )
向上转换的实质就是把派生类对象看做是基类对象,能 使用基类对象的地方可以使用派生类对象 代替,能发送给基类对象的消息,就能发送给派生类对象,基类对象能响应的消息,派生类对象也能响应
upcast是多态性的基础
与向上转换相反,有时需要把基类对象看做是派生类对象,
这称为 downcast( 向下转换 )
downcast不安全,一般先把派生类对象进行 upcast,向上转换成基类对象,再在需要时进行 downcast向下转换为原来的派生类的对象
向下转换需要使用强制类型转换关键字 dynamic_cast
21:53:08
例,downcast向下转换
class Date{
protected:
int year,month,day;
public:
Date(int y,int m,int d){ setDate(y,m,d);}
void setDate(int y,int m,int d){
year = y; month = m; day = d;
}
virtual void Print()
{
cout << year << "/" << month << "/" << day << ";";
}
};
21:53:08
例:向下转换(续)
class DateTime:public Date{
int hours,minutes,seconds;
public:
DateTime(int y,int m,int d,int h,int mi,int s)
:Date(y,m,d)
{setTime(h,mi,s);}
void setTime(int h,int mi,int s)
{hours = h; minutes = mi; seconds = s;}
void Print()
{
Date::Print();
cout<<hours<<":"<<minutes<<":"<<seconds<<endl;
}
};
21:53:08
例:向下转换(续)
void main(){
DateTime dt(2003,1,1,12,30,0);
DateTime *pdt = &dt;
pdt->Print();
Date d(2004,11,15);
//pdt = &d; //语法错误
pdt = (DateTime *)(&d);
pdt->Print(); //此处输出什么?-------不安全
//下面是合理、安全的使用方法
Date *pd = &dt;
//pdt = pd; //语法错误
pdt = dynamic_cast<DateTime *>(pd);
//pd指向的本身就是 DateTime类的对象,
//此时向下转换是安全的
pdt->Print();
}
dynamic_cast用于把基类对象的指针安全地转换为派生类指针
21:53:08
§ 3 静态绑定、动态绑定与动态绑定的实现机制
将函数调用与该调用将要执行的函数体之间的对应关系的确定过程称为 绑定 的过程
C++支持两种绑定方式
静态绑定,编译,连接时确定函数调用与该调用执行的函数体之间的对应关系 — 早期绑定对全局函数,重载的函数,重载运算符对应的函数,非虚的成员函数等都实施静态绑定
动态绑定,程序运行时根据指针所指对象或引用所引用对象的类型确定函数调用与该调用执行的函数体之间的对应关系 — 迟绑定,滞后绑定对类中的虚函数实施动态绑定动态绑定需要运行时类型识别 (RTTI)的支持,为此在包含虚函数的类中,除了类中定义的成员外,编译器还添加了相应的类型标识信息,以供运行时进行类型的识别
21:53:08
动态绑定的实现机制,VPTR + VTABLE
C++编译器为每个 包含了虚函数的类 产生一个 VTABLE( 虚表:可看作是一个数组 ),把类中每个虚函数的地址存入表中,并在该类中添加了一个成员变量 VPTR,该变量是一个指向 VTABLE起始地址的指针
编译器同时还在包含虚函数的类中生成了适当的代码,以便调用成员函数时通过 VPTR访问 VTABLE,从 VTABLE中查找有无该成员函数的表项,有则该函数为虚函数,从 VTABLE中取出对应的地址,就可以调用虚函数的相应版本
21:53:08
动态绑定的实现
VTABLEObject
CGraph
类的对象
Draw
…
vptr
…
CGraph::Draw
…
CCircle
类的对象 vptr… Draw… CCircle::Draw…
CRectangle
类的对象 vptr… Draw… CRectagle::Draw…
21:53:08
§ 4 纯虚函数和抽象类
在继承的层次中,基类往往用于表达一些抽象,泛指的概念,因此基类的某些虚成员函数无法实现或其实现没有意义,只有在具体的某个特定派生类中该函数才有实际意义例,CGraph中的 Draw()
例:简单图形类
class figure{
protected:
double x,y;
public:
void set_dim(double i,double j=0){ x=i;y=j; }
virtual double area(); //应该如何实现该函数?
};
21:53:08
例:简单图形类(续)
class triangle:public figure{
public:
double area(){ return 0.5 * x * y; }
};
class square:public figure{
public:
double area(){return x * y; }
};
class circle:public figure{
public:
double area(){return 3.1415926 * x * x; }
};
void main(){
triangle t;square s;circle c;
t.set_dim(10.0,5.0);cout << t.area() << endl;
s.set_dim(10.0,5.0);cout << s.area() << endl;
c.set_dim(9.0);cout << c.area() << endl;
}
21:53:08
纯虚函数
纯虚函数是在基类中声明的虚函数,在基类中它没有实现定义,一般要求在派生类中对该函数进行定义
纯虚函数的一般形式
class 类名 {
…
virtual 返回值类型 函数名 (形参列表 )=0;
…
};
例纯虚函数
class figure{
…
virtual double area() = 0;
};
21:53:08
抽象类
包含了纯虚函数的类称为抽象类
抽象类中声明的纯虚函数必须在某个派生类中对其进行定义,若派生类中没有定义抽象基类中声明的纯虚函数,则该派生类也将成为抽象类
抽象类因没有对纯虚函数进行实现,所以是不完整的类,
因此 不能创建抽象类的对象,但可定义抽象类的指针,也可以声明抽象类的引用
抽象类一般用作基类,提供接口,用该抽象基类的指针指向派生类对象或用该抽象基类的引用引用派生类的对象,
实现 一个接口,多种实现方法,提供了对从该抽象类继承而得到的派生类的统一使用方法
21:53:08
例:纯虚函数和抽象类
1.使用抽象类指针
void main(){
figure *p;
triangle t;
square s;
circle c;
p = &t;
p->set_dim(10.0,5.0);
cout << p->area() << endl;
p = &s;
p->set_dim(10.0,5.0);
cout << p->area() << endl;
p = &c;
p->set_dim(9.0);
cout << p->area() << endl;
}
21:53:08
2.使用抽象类引用,以不同数制形式输出整数
class Number{
protected:
int val;
public:
Number(int i){val = i; }
virtual void Show() = 0;
};
class Hex_type:public Number{
public:
Hex_type(int i):Number(i){ }
void Show(){cout<<"Hexadecimal,"<<hex<<val<<endl;}
};
21:53:08
使用抽象类引用,以不同数制形式输出整数(续)
class Dec_type:public Number{
public:
Dec_type(int i):Number(i){}
void Show(){cout<<"Decimal,"<<dec<<val<<endl;
}
};class Oct_type:public Number{
public:
Oct_type(int i):Number(i){}
void Show(){cout<<"Octal,"<<oct<<val<<endl;}
};
void fun(Number & n){n.Show();}
void main(){
Dec_type n1(50); fun(n1);
Hex_type n2(50); fun(n2);
Oct_type n3(50); fun(n3);
}
21:53:08
例:利用虚函数和多态性计算雇员工资一个工厂的雇员 (Employee)包括管理人员 (Manager),计时工人 (HourWorker)和计件工人 (PieceWorker)。 所有雇员的基本信息有编号,姓名 。 管理人员领取固定月薪,计时工的月薪为基本工作时间薪金加上加班费,计件工的月薪取决于他所生产的工件数
Employee
Manager HourWorker PieceWorker
21:53:08
例:抽象基类的指针数组创建一个长为 6的抽象基类的指针 ( Employee*) 数组,让该数组的每一个元素指向一个不同派生类的对象 。 然后用循环语句遍历数组的方式,调用不同派生类的虚函数输出名对象的数据例:动态异质链表链表中的每个结点可是不同派生类的对象,每个结点中指向下一结点的指针域为抽象基类的指针
21:53:08
本章主要内容
1,多态的概念
2,虚函数 —— 重点
3,静态绑定、动态绑定与动态绑定的实现机制
4,纯虚函数与抽象类 —— 重点
21:53:08
§ 1 多态的概念
多态性指一个名字,多种语义,或接口相同,多种实现
C++支持两种形式的多态
1.静态多态(编译时多态)
2.动态多态(运行时多态)
21:53:08
静态多态
静态多态主要指函数和运算符的重载例:函数的重载
int abs(int x){ return x; }
float abs(float x){ return x; }
long abs(long x){ return x; }
void main()
{
cout << abs(-100) << endl;
cout << abs(-2.5f) << endl;
cout << abs(123456789L) << endl;
}
21:53:08
例:函数的重载(续)
class X{
public:
void Show(int,char);
void Show(char *,char);
};
void main()
{
X xObj;
xObj.Show(1,’a’);
xObj.Show(“张三,,’b’);
}
21:53:08
例:函数重载 (续 )
class X
{
public:
void show(){ }
};
class Y:public X{
public:
void show(int){ }
void show(){ }
};
void main(){
X xObj;
Y yObj;
xObj.Show();
yObj.Show(10);
yObj.Show();
yObj.X::Show();
}
结论:
重载函数,运算符重载对应的函数的调用和函数体之间对应关系的建立,
是由编译程序和连接程序根据函数的参数数目,参数类型的匹配关系,在编译连接阶段完成的,所以静态多态又称为编译 -连接时多态 ( 简称 编译时多态 )
21:53:08
动态多态
与继承机制有关,建立在继承机制之上
在继承机制中,能使用基类对象的地方就能用派生类对象 ( upcast — 向上转换 )
例,.upcast(向上转换 )
动态多态在继承机制之上靠 虚成员函数 实现
对虚函数的调用和函数体之间的对应关系不是在编译,
连接阶段确定,而是在程序运行过程中动态的确定该函数执行哪一个函数体,因此动态多态又称为 运行时多态基类的指针可以指向派生类的对象,基类的引用可以引用派生类的对象 。 导致编译时无法预知基类指针将来会指向哪个派生类的对象,也无法预知基类的引用将来会引用哪个派生类的对象
21:53:08
§ 2 虚函数
虚函数是在基类中使用关键字 virtual进行声明,在派生类中 重新定义 (override)的成员函数例:虚函数
class X
{
public:
virtual void f()
{
cout <<,X::f()”;
}
};
class Y:public X
{
public:
virtual void f()
{
cout <<,Y::f()”;
}
};
21:53:08
虚函数的作用
能使用基类对象的地方就能使用派生类对象,故基类指针可以指向派生类对象,基类引用也可以引用派生类对象
基类指针指向派生类对象,通过指针调用所指向对象中的虚函数,C++根据所指向对象的类型,在 运行时确定 调用哪一个类的虚函数
基类引用引用派生类对象,通过引用调用所引用对象中的虚函数,C++根据所引用对象的类型,在 运行时确定 调用哪一个类的虚函数
基类指针指向不同派生类的对象,基类引用引用不同派生类的对象时,可以执行虚函数的不同版本
21:53:08
例 1:非虚成员函数
class vehicle{
public:
void drive()
{ cout << "drive a generic vehicle" << endl;}
};
class car:public vehicle{
public:
void drive(){cout << "drive a car,<< endl;}
};
void f(vehicle & v){ v.drive(); }
void g(vehicle * pV){ pV->drive(); }
void main(){
vehicle vh; car c;
f(vh); f(c); //分别调用哪一个类中的 drive()?
g(&vh); g(&c); //分别调用哪一个类中的 drive()?
vehicle *pVh;
pVh=&vh; pVh->drive();
pVh=&c; pVh->drive();
}
引用基类对象,调用基类成员函数引用派生类对象,
调用基类成员函数指向基类对象,调用基类成员函数指向派生类对象,
调用基类成员函数
21:53:08
例 1:虚成员函数
class vehicle{
public:
virtual void drive()
{ cout << "drive a generic vehicle" << endl;}
};
class car:public vehicle{
public:
virtual void drive(){cout <<"drive a car"<< endl;}
};
void f(vehicle & v){ v.drive(); }
void g(vehicle * pV){ pV->drive(); }
void main(){
vehicle vh; car c;
f(vh); f(c); //分别调用哪一个类中的 drive()?
g(&vh); g(&c); //分别调用哪一个类中的 drive()?
}
引用基类对象,调用基类成员函数引用派生类对象,调用派生类成员函数指向基类对象,调用基类成员函数指向派生类对象,调用派生类成员函数结论:
基类指针指向不同派生类的对象,基类引用引用不同派生类的对象时,执行的是相应派生类中的虚函数
21:53:08
例 2:非虚成员函数
class CGraph{
int x,y;
public:
CGraph(int xx,int yy){ x = xx; y = yy;}
~ CGraph(){ }
void Draw(CDC &dc)
{ dc.TextOut(x,y,"This is a Graph Object"); }
int getX()const{return x;}
int getY()const{return y;}
};
21:53:08
例 2:非虚成员函数(续)
class CRectangle:public CGraph{
int left,top,right,bottom;
public:
CRectangle(int lft,int tp,int rgt,int btm)
:CGraph((rgt+lft)/2,(tp+btm)/2)
{
left = lft; top = tp; right = rgt; bottom = btm;
}
~ CRectangle(){ }
void Draw(CDC & dc)
{ dc.Rectangle(left,top,right,bottom); }
};
21:53:08
例 2,非虚成员函数 (续 )
#include "Graph.h"
class CCircle,public CGraph{
int r;
public:
CCircle(int xx,int yy,int rr):CGraph(xx,yy){r=rr;}
~ CCircle(){ }
void Draw(CDC & dc)
{dc.Ellipse(getX()-r,getY()-r,getX()+r,getY()+r);}
};
CGraph g(100,30),*pG=&g;
CCircle cir(100,100,50);
CRectangle rct(100,200,300,300);
pG->Draw(dc); //调用哪一个 Draw()?
pG=○
pG->Draw(dc); //调用哪一个 Draw()?
pG=&rct;
pG->Draw(dc); //调用哪一个 Draw()?
指向 CGraph类的对象,
调用 CGraph类的 Draw()
指向 CCircle类的对象,
调用 CGraph类的 Draw()
指向 CRectangle
类的对象,调用
CGraph类的 Draw()
21:53:08
例 2:虚成员函数
class CGraph{
int x,y;
public:
CGraph(int xx,int yy){ x = xx; y = yy;}
~ CGraph(){ }
virtual void Draw(CDC &dc)
{dc.TextOut(x,y,"This is a Graph Object");}
int getX()const{return x;}
int getY()const{return y;}
};
21:53:08
例 2:虚成员函数(续)
class CRectangle:public CGraph{
int left,top,right,bottom;
public:
CRectangle(int lft,int tp,int rgt,int btm)
:CGraph((rgt+lft)/2,(tp+btm)/2)
{
left = lft; top = tp; right = rgt; bottom = btm;
}
~ CRectangle(){ }
virtual void Draw(CDC & dc)
{ dc.Rectangle(left,top,right,bottom); }
};
21:53:08
例 2,虚成员函数 (续 )
#include "Graph.h"
class CCircle,public CGraph{
int r;
public:
CCircle(int xx,int yy,int rr):CGraph(xx,yy){r=rr;}
~ CCircle(){ }
virtual void Draw(CDC & dc)
{dc.Ellipse(getX()-r,getY()-r,getX()+r,getY()+r);}
};
CGraph g(100,30),*pG=&g;
CCircle cir(100,100,50);
CRectangle rct(100,200,300,300);
pG->Draw(dc); //调用哪一个 Draw()?
pG=○
pG->Draw(dc); //调用哪一个 Draw()?
pG=&rct;
pG->Draw(dc); //调用哪一个 Draw()?
指向 CGraph类的对象,
调用 CGraph类的 Draw()
指向 CCircle类的对象,
调用 CCircle类的 Draw()
指向 CRectangle类的对象,调用 CRectangle
类的 Draw()结论:
基类指针指向不同派生类的对象,基类引用引用不同派生类的对象时,执行的是相应派生类中的虚函数
21:53:08
关于虚函数的说明
虚函数只能是类的成员函数,而不能是普通函数或类的友元函数
关键字 virtual只在声明虚成员函数时使用,定义该成员函数时不需用 virtual进行修饰
无论继承层次有多深,基类中声明的虚函数,在派生类中自动为虚函数,在派生类中声明该虚函数时可不用关键字
virtual
21:53:08
例:继承层次
class Root{
public:
virtual void vf()
{cout << "Base::vf()" << endl; }
};
class FirstLevel:public Root{
public:
void vf()
{ cout << "FirstLevel::vf()" << endl; }
};
class SecondLevel:public FirstLevel{
public:
void vf()
{ cout << "SecondLevel::vf()" << endl; }
};
同一虚函数的不同版本
21:53:08
例:继承层次(续)
void main(){
Root * ptr;
ptr = new Root;
ptr->vf();
delete ptr;
ptr = new FirstLevel;
ptr->vf();
delete ptr;
ptr = new SecondLevel;
ptr->vf();
delete ptr;
}
21:53:08
关于虚函数的说明(续)
派生类中新添加了与基类中声明的虚函数同名的成员函数,
但两者的函数原型不同 (overload),则派生类中新添加的成员函数不是基类中同名函数的虚函数
class Base{
public:
virtual void f(int x)
{cout << "Base::vf(int)" << endl; }
};
class Derived:public Base{
public:
void f()
{ cout <<,Derived::vf()" << endl; }
};
派生类中的 f()不是基类中虚函数 f(int)的新版本派生类中的 f()丢失了虚的特性
21:53:08
关于虚函数的说明(续)
继承的类层次中,若某个派生类中没有显式重新定义最上层基类中声明的虚函数,则该派生类中的虚函数为从其直接基类中继承而来的虚函数
class Base{
public:
virtual void f(){ cout << "Base::f()" << endl;}
};
class Derived1:public Base{
public:
void f(){ cout << "Derived1::f()" << endl;}
};
class Derived2:public Derived1{
public:
void g(){ cout << "Derived2::g()" << endl;}
};
void main(){
Base *ptr;
ptr = new Derived1;
ptr->f();
delete ptr;
ptr = new Derived2;
ptr->f();
delete ptr;
}
21:53:08
关于虚函数的说明(续)
虚函数要发挥其作用,必须使用 基类指针指向派生类对象,
或者用基类引用引用派生类对象,再通过基类指针或基类引用调用对象中的虚成员函数
直接使用基类对象调用虚函数,则只能调用基类中的版本
class Base{
public:
virtual void f(){ cout << "Base::f()" << endl;}
};
class Derived:public Base{
public:
void f(){ cout << "Derived1::f()" << endl;}
};
Base b;
Derived d;
b.f(); //调用哪一个 f()?
b = d;
b.f(); //调用哪一个 f()?
直接通过基类对象调用虚函数,编译连接时就能确定对象 b的类型,所以只能调用基类中的 f()
21:53:08
关于虚函数的说明(续)
类的构造函数不能声明为虚函数
若类中至少包含一个虚函数,则该类的析构函数应该声明为虚析构函数例:非虚析构函数
class Base{
public:
~ Base()
{ cout << "Base destructor is called" << endl;}
virtual void f(){cout << "Base::f()" << endl;}
};
21:53:08
例:非虚析构函数(续)
class Derived:public Base{
char *v; int len;
public:
Derived(char *str){
len = strlen(str);v=NULL;
if (len!=0){
v = new char[len + 1];strcpy(v,str);
}
}
~ Derived(){
cout<<"Derived destructor is called"<<endl;
if(v) delete [] v;
}
void f(){cout << "Derived::f()" << endl; }
};
void main(){
Base * ptr;
ptr = new Derived(“张三,);
ptr->f();
delete ptr;
}
基类指针 ptr指向了派生类对象,因析构函数不是虚函数,所以编译连接时已经确定 delete时调用基类的析构函数而 不调用派生类的析构函数,故该派生类对象中的
v所指空间没有被释放
21:53:08
例:虚析构函数
class Base{
public:
virtual~ Base()
{ cout << "Base destructor is called" << endl;}
virtual void f(){cout << "Base::f()" << endl;}
};
class Derived:public Base{
char *v; int len;
public:
Derived(char *str){
len = strlen(str);v = NULL;
if(len!=0){v=new char[len + 1];strcpy(v,str);}
}
void f(){ cout << "Derived::f()" << endl;}
virtual ~ Derived(){
cout << "Derived destructor is called" << endl;
if(v)delete []v;
}
};
21:53:08
例:虚析构函数(续)
void main(){
Base * ptr;
ptr = new Derived("李四 ");
ptr->f();
delete ptr;
}
基类指针 ptr指向了派生类对象,此时析构函数是虚函数,
所以 编译连接时无法确定 delete调用哪一个类的析构函数
运行过程中 执行 delete运算符的操作时,系统先确定 ptr
所指对象的类型 ( 为 Derived),从而调用相应类的析构函数 ( 应为 Dervied类的析构函数,Derived类的析构函数又会自动调用基类 Base的析构函数 )
21:53:08
upcast(向上转换)与 downcast(向下转换)
在继承的层次中,可认把派生类对象为是一种基类对象,
这称为 upcast( 向上转换 )
向上转换的实质就是把派生类对象看做是基类对象,能 使用基类对象的地方可以使用派生类对象 代替,能发送给基类对象的消息,就能发送给派生类对象,基类对象能响应的消息,派生类对象也能响应
upcast是多态性的基础
与向上转换相反,有时需要把基类对象看做是派生类对象,
这称为 downcast( 向下转换 )
downcast不安全,一般先把派生类对象进行 upcast,向上转换成基类对象,再在需要时进行 downcast向下转换为原来的派生类的对象
向下转换需要使用强制类型转换关键字 dynamic_cast
21:53:08
例,downcast向下转换
class Date{
protected:
int year,month,day;
public:
Date(int y,int m,int d){ setDate(y,m,d);}
void setDate(int y,int m,int d){
year = y; month = m; day = d;
}
virtual void Print()
{
cout << year << "/" << month << "/" << day << ";";
}
};
21:53:08
例:向下转换(续)
class DateTime:public Date{
int hours,minutes,seconds;
public:
DateTime(int y,int m,int d,int h,int mi,int s)
:Date(y,m,d)
{setTime(h,mi,s);}
void setTime(int h,int mi,int s)
{hours = h; minutes = mi; seconds = s;}
void Print()
{
Date::Print();
cout<<hours<<":"<<minutes<<":"<<seconds<<endl;
}
};
21:53:08
例:向下转换(续)
void main(){
DateTime dt(2003,1,1,12,30,0);
DateTime *pdt = &dt;
pdt->Print();
Date d(2004,11,15);
//pdt = &d; //语法错误
pdt = (DateTime *)(&d);
pdt->Print(); //此处输出什么?-------不安全
//下面是合理、安全的使用方法
Date *pd = &dt;
//pdt = pd; //语法错误
pdt = dynamic_cast<DateTime *>(pd);
//pd指向的本身就是 DateTime类的对象,
//此时向下转换是安全的
pdt->Print();
}
dynamic_cast用于把基类对象的指针安全地转换为派生类指针
21:53:08
§ 3 静态绑定、动态绑定与动态绑定的实现机制
将函数调用与该调用将要执行的函数体之间的对应关系的确定过程称为 绑定 的过程
C++支持两种绑定方式
静态绑定,编译,连接时确定函数调用与该调用执行的函数体之间的对应关系 — 早期绑定对全局函数,重载的函数,重载运算符对应的函数,非虚的成员函数等都实施静态绑定
动态绑定,程序运行时根据指针所指对象或引用所引用对象的类型确定函数调用与该调用执行的函数体之间的对应关系 — 迟绑定,滞后绑定对类中的虚函数实施动态绑定动态绑定需要运行时类型识别 (RTTI)的支持,为此在包含虚函数的类中,除了类中定义的成员外,编译器还添加了相应的类型标识信息,以供运行时进行类型的识别
21:53:08
动态绑定的实现机制,VPTR + VTABLE
C++编译器为每个 包含了虚函数的类 产生一个 VTABLE( 虚表:可看作是一个数组 ),把类中每个虚函数的地址存入表中,并在该类中添加了一个成员变量 VPTR,该变量是一个指向 VTABLE起始地址的指针
编译器同时还在包含虚函数的类中生成了适当的代码,以便调用成员函数时通过 VPTR访问 VTABLE,从 VTABLE中查找有无该成员函数的表项,有则该函数为虚函数,从 VTABLE中取出对应的地址,就可以调用虚函数的相应版本
21:53:08
动态绑定的实现
VTABLEObject
CGraph
类的对象
Draw
…
vptr
…
CGraph::Draw
…
CCircle
类的对象 vptr… Draw… CCircle::Draw…
CRectangle
类的对象 vptr… Draw… CRectagle::Draw…
21:53:08
§ 4 纯虚函数和抽象类
在继承的层次中,基类往往用于表达一些抽象,泛指的概念,因此基类的某些虚成员函数无法实现或其实现没有意义,只有在具体的某个特定派生类中该函数才有实际意义例,CGraph中的 Draw()
例:简单图形类
class figure{
protected:
double x,y;
public:
void set_dim(double i,double j=0){ x=i;y=j; }
virtual double area(); //应该如何实现该函数?
};
21:53:08
例:简单图形类(续)
class triangle:public figure{
public:
double area(){ return 0.5 * x * y; }
};
class square:public figure{
public:
double area(){return x * y; }
};
class circle:public figure{
public:
double area(){return 3.1415926 * x * x; }
};
void main(){
triangle t;square s;circle c;
t.set_dim(10.0,5.0);cout << t.area() << endl;
s.set_dim(10.0,5.0);cout << s.area() << endl;
c.set_dim(9.0);cout << c.area() << endl;
}
21:53:08
纯虚函数
纯虚函数是在基类中声明的虚函数,在基类中它没有实现定义,一般要求在派生类中对该函数进行定义
纯虚函数的一般形式
class 类名 {
…
virtual 返回值类型 函数名 (形参列表 )=0;
…
};
例纯虚函数
class figure{
…
virtual double area() = 0;
};
21:53:08
抽象类
包含了纯虚函数的类称为抽象类
抽象类中声明的纯虚函数必须在某个派生类中对其进行定义,若派生类中没有定义抽象基类中声明的纯虚函数,则该派生类也将成为抽象类
抽象类因没有对纯虚函数进行实现,所以是不完整的类,
因此 不能创建抽象类的对象,但可定义抽象类的指针,也可以声明抽象类的引用
抽象类一般用作基类,提供接口,用该抽象基类的指针指向派生类对象或用该抽象基类的引用引用派生类的对象,
实现 一个接口,多种实现方法,提供了对从该抽象类继承而得到的派生类的统一使用方法
21:53:08
例:纯虚函数和抽象类
1.使用抽象类指针
void main(){
figure *p;
triangle t;
square s;
circle c;
p = &t;
p->set_dim(10.0,5.0);
cout << p->area() << endl;
p = &s;
p->set_dim(10.0,5.0);
cout << p->area() << endl;
p = &c;
p->set_dim(9.0);
cout << p->area() << endl;
}
21:53:08
2.使用抽象类引用,以不同数制形式输出整数
class Number{
protected:
int val;
public:
Number(int i){val = i; }
virtual void Show() = 0;
};
class Hex_type:public Number{
public:
Hex_type(int i):Number(i){ }
void Show(){cout<<"Hexadecimal,"<<hex<<val<<endl;}
};
21:53:08
使用抽象类引用,以不同数制形式输出整数(续)
class Dec_type:public Number{
public:
Dec_type(int i):Number(i){}
void Show(){cout<<"Decimal,"<<dec<<val<<endl;
}
};class Oct_type:public Number{
public:
Oct_type(int i):Number(i){}
void Show(){cout<<"Octal,"<<oct<<val<<endl;}
};
void fun(Number & n){n.Show();}
void main(){
Dec_type n1(50); fun(n1);
Hex_type n2(50); fun(n2);
Oct_type n3(50); fun(n3);
}
21:53:08
例:利用虚函数和多态性计算雇员工资一个工厂的雇员 (Employee)包括管理人员 (Manager),计时工人 (HourWorker)和计件工人 (PieceWorker)。 所有雇员的基本信息有编号,姓名 。 管理人员领取固定月薪,计时工的月薪为基本工作时间薪金加上加班费,计件工的月薪取决于他所生产的工件数
Employee
Manager HourWorker PieceWorker
21:53:08
例:抽象基类的指针数组创建一个长为 6的抽象基类的指针 ( Employee*) 数组,让该数组的每一个元素指向一个不同派生类的对象 。 然后用循环语句遍历数组的方式,调用不同派生类的虚函数输出名对象的数据例:动态异质链表链表中的每个结点可是不同派生类的对象,每个结点中指向下一结点的指针域为抽象基类的指针
21:53:08