第 6 章 多态性与虚函数

35
第6第 第第第第第第第

Upload: hastin

Post on 14-Jan-2016

86 views

Category:

Documents


0 download

DESCRIPTION

第 6 章 多态性与虚函数. 主要内容. 6.1 多态性概述 6.2 子类型 6.3 虚函数 6.4 纯虚函数和抽象类. 6.1 多态性概述. 所谓多态性就是不同对象收到相同的消息时,产生不同的动作。 直观地说,多态性是指用一个名字定义不同的函数,这些函数执行不同但又类似的操作,从而可以使用相同的调用方式来调用这些具有不同功能的同名函数。. 6.1.1 多态的分类. C++ 中的多态性可以分为四类 : 参数多态(函数模板、类模板) 包含多态(虚函数) 重载多态(函数重载、运算符重载) - PowerPoint PPT Presentation

TRANSCRIPT

Page 1: 第 6 章  多态性与虚函数

第 6 章 多态性与虚函数

Page 2: 第 6 章  多态性与虚函数

主要内容6.1 多态性概述6.2 子类型6.3 虚函数6.4 纯虚函数和抽象类

Page 3: 第 6 章  多态性与虚函数

6.1 多态性概述 所谓多态性就是不同对象收到相同的消息时,产生不同的动作。 直观地说,多态性是指用一个名字定义不同的函数,这些函数执行不同但又类似的操作,从而可以使用相同的调用方式来调用这些具有不同功能的同名函数。

Page 4: 第 6 章  多态性与虚函数

6.1.1 多态的分类 C++ 中的多态性可以分为四类 :

参数多态(函数模板、类模板) 包含多态(虚函数) 重载多态(函数重载、运算符重载) 强制多态(强制类型转换)。 前面两种统称为通用多态,而后面两种统称为专用多态。

Page 5: 第 6 章  多态性与虚函数

6.1.2 多态的实现 多态性的实现和联编这一概念有关。所谓联编就是把函数名与函数体的程序代码连接(联系)在一起的过程。 联编分成两大类:静态联编和动态联编。静态联编优点:调用速度快,效率高,但缺乏灵活性;动态联编优点:运行效率低,但增强了程序灵活性。 C++ 为了兼容 C 语言仍然是编译型的,采用静态联编。为了实现多态性,利用虚函数机制,可部分地采用动态联编。

Page 6: 第 6 章  多态性与虚函数

6.1.2 多态的实现 多态从实现的角度来讲可以划分为两类 :编译时的多态和运行时的多态。 编译时的多态是通过静态联编来实现的。静态联编就是在编译阶段完成的联编。编译时多态性主要是通过函数重载和运算符重载实现的。 运行时的多态是用动态联编实现的。动态联编是运行阶段完成的联编。运行时多态性主要是通过虚函数来实现的。

Page 7: 第 6 章  多态性与虚函数

6.2 子类型概念 子类型:有一个特定的类型 U ,当且仅当它至少提供了类型 X 的行为时,称类型 U

是类型 X 的子类型。 如果类 U 从类 X 公有继承,则类 U 将继承了 X 的行为,同时类 U 还可以根据需求增加新的行为。这意味着类 U 提供了类 X 的行为,根据定义,类 U 是类 X 的子类型。

Page 8: 第 6 章  多态性与虚函数

6.2.1 子类型作用 类型适应:类型 U 的对象能被用于类型 X 的对象所能使用的场合,称为类型 U 适应类型 X 。 类型适应的四种情况:在使用 X 类型对象的场合,可以使用 U 类型对象;在使用 X* 指针或 X& 引用的场合,可以使用 U*指针或 U& 引用;在使用使用 const X* 常指针或 const X& 常引用的场合,可以使用 const U* 常指针或 const U& 常引用;

Page 9: 第 6 章  多态性与虚函数

6.2.1 子类型作用 子类型作用:一个函数可被重用于处理 X 类型的对象和 X 的各个子类型的对象,而不必为处理这些子类型的对象去重载该函数,可以减轻程序人员编写程序代码的负担。

例 6.1 :分析下列程序的输出结果。

Page 10: 第 6 章  多态性与虚函数

例 6.1 子类型静态束定实例 #include <iostream.h>class A{public: A() {a=0;} A(int i) {a=i;} void Print() {cout<<a<<endl;} int Geta() {return a;}private: int a;};class B:public A{public: B() {b=0;} B(int i,int j):A(i),b(j) {} void Print() {A::Print();cout<<b

<<endl;}

private:

int b;

};

void fun(A& d)

{ cout<<d.Geta()*10<<endl; }

void main()

{ B bb(9,5);

A aa(5);

aa=bb;

aa.Print();

A *pa=new A(8);

B *pb=new B(1,2);

pa=pb;

pa->Print();

fun(bb); }

Page 11: 第 6 章  多态性与虚函数

6.3 虚函数 虚函数提供了一种更为灵活的多态性机制。虚函数允许函数调用与函数体之间的联系在运行时才建立,也就是在运行时才决定如何动作,即所谓的动态联编。

6.3.1 虚函数的引入

Page 12: 第 6 章  多态性与虚函数

例 6.2 没有引入虚函数的实例 1 。#include<iostream.h>class A{public: void show(){ cout<<"A"; }};class B:public A {public: void show(){ cout<<"B"; }};main(){ A a,*pc; B b; pc=&a; pc->show(); pc=&b; pc->show(); return 0;}

Page 13: 第 6 章  多态性与虚函数

例 6.3 没有引入虚函数的实例 2

#include<iostream.h>class Base{public:

Base(int x,int y){ a=x;

b=y; }void show(){cout<<"Base---------------\n"; cout<<a<<" "<<b<<endl; }

private:int a,b;

};class Derived:public Base{public: Derived(int x,int y,int z):Base(x,y) { c=z; }

void show()

{ cout<<"Derived-------------------\n";

cout<<"c="<<c<<endl; }

private:

int c;

};

void main()

{

Base mb(60,60),*pc;

Derived mc(10,20,30);

pc=&mb;

pc->show();

pc=&mc;

pc->show();

}

Page 14: 第 6 章  多态性与虚函数

6.3.2 虚函数的作用和定义 1. 虚函数的作用 虚函数同派生类的结合可使 C++ 支持运行时的多态性,实现了在基类定义派生类所拥有的通用接口,而在派生类定义具体的实现方法,即常说的“同一接口,多种方法”,它帮助程序员处理越来越复杂的程序。

Page 15: 第 6 章  多态性与虚函数

例 6.4 虚函数的作用。#include<iostream.h>class Base {public: Base(int x,int y) { a=x; b=y; } virtual void show() // 定义虚函数 show() { cout<<"Base----------\n"; cout<<a<<" "<<b<<endl;}

private: int a,b; };class Derived : public Base {public: Derived(int x,int y,int z):Base(x,y){c=z; } void show() // 重新定义虚函数 show() { cout<< "Derived---------\n"; cout<<c<<endl;}

private: int c;};

Page 16: 第 6 章  多态性与虚函数

void main(){

Base mb(60,60),*pc; Derived mc(10,20,30); pc=&mb; pc->show(); // 调用基类 Base 的 show() 版本 pc=&mc; pc->show(); // 调用派生类 Derived 的 show() 版本

}程序运行结果如下 :Base---------60 60Derived--------30

Page 17: 第 6 章  多态性与虚函数

2. 虚函数的定义定义虚函数的方法如下 :

virtual 函数类型 函数名 ( 形参表 )

{

// 函数体 }

Page 18: 第 6 章  多态性与虚函数

说明:1. 派生类应该从它的基类公有派生。2. 必须首先在基类中定义虚函数。3. 派生类对基类中声明虚函数重新定义时,关键字 virtual 可

以不写。4. 一般通过基类指针访问虚函数时才能体现多态性。5. 一个虚函数无论被继承多少次,保持其虚函数特性。6. 虚函数必须是其所在类的成员函数,而不能是友元函数,也

不能是静态函数。7. 内联函数不能是虚函数。8. 构造函数不能是虚函数。9. 析构函数可以使虚函数,通常说明为虚函数。

Page 19: 第 6 章  多态性与虚函数

例 6.5 虚函数的定义举例。#include<iostream.h>class Grandam { public: virtual void introduce_self() // 定义虚函数 introduce_self() { cout<<"I am grandam."<<endl; } };class Mother:public Grandam { public: void introduce_self() // 重新定义虚函数 introduce_self() { cout<<"I am mother."<<endl;}};class Daughter:public Mother { public: void introduce_self() // 重新定义虚函数 introduce_self() { cout<<"I am daughter."<<endl;}};

Page 20: 第 6 章  多态性与虚函数

void main(){ Grandam *ptr; Grandam g; Mother m; Daughter d; ptr=&g; ptr->introduce_self();// 调用基类 Grandam 的 introduce_self(

) ptr=&m; ptr->introduce_self();// 调用派生类 Mother 的 introduce_self

() ptr=&d; ptr->introduce_self(); // 调用派生类 // Daughter 的 introduce_self()}

Page 21: 第 6 章  多态性与虚函数

C ++规定,如果在派生类中,没有用 virtual 显式地给出虚函数声明,这时系统就会遵循以下规则来判断一个成员函数是不是虚函数:

( 1 )该函数与基类的虚函数有相同的名称;( 2 )该函数与基类的虚函数有相同的参数个数及相同

的对应参数类型;( 3 )该函数与基类的虚函数有相同的返回类型或者满

足赋值兼容性规则的指针、引用型的返回类型。

Page 22: 第 6 章  多态性与虚函数

3. 成员函数调用虚函数(例 6.6 )#include <iostream.h>

class A

{ public:

virtual double funA(double x)

{ cout<<"funA of class A called."<<endl;

return x*x; }

double funB(double x)

{ return funA(x)/2; }

};

class B:public A

{ public:

virtual double funA(double x)

{ cout<<"funA of class B called."<<endl;

return 2*x*x; }

};

class C:public B

{ public:

virtual double funA(double x)

{ cout<<"funA of class C called."<<endl;

return 3*x*x;

}

};

void main()

{

C objc;

cout<<objc.funB(3)<<endl;

B objb;

cout<<objb.funB(3)<<endl;

}

Page 23: 第 6 章  多态性与虚函数

4. 构造函数和析构函数内调用虚函数 构造函数中调用虚函数时,采用静态束定,即

构造函数调用的虚函数是自己类中实现的虚函数,如果自己类中没有实现这个虚函数,则调用基类中的虚函数,而不是任何派生类中实现的虚函数;

析构函数中调用虚函数同构造函数,即析构函数所调用的虚函数是自身类中的或者基类中实现的虚函数;

BA C

构造 (析构 )函数

一般成员函数

调用虚函数

Page 24: 第 6 章  多态性与虚函数

构造函数和析构函数内调用虚函数(例 6.7 )

#include <iostream.h>

class A

{ public:

A(){ cout<<"default constructor of class A is called! \n";}

~A(){ cout<<"destructor of class A is called!\n";}

virtual void funA()

{ cout<<"funA of class A called."<<endl; }

};

class B:public A

{ public:

B(){ cout<<"default constructor of class B is called! \n";funA();}

~B(){ cout<<"destructor of class B is called!\n";funA();}

void funB() { funA(); }

};

class C:public B

{ public:

C(){ cout<<"default constructor

of class C is called! \n";}

~C(){ cout<<"destructor of class C is called!\n";}

virtual void funA()

{cout<<"funA of class C called.“

<<endl; }

};

void main()

{

cout<<"creating object...\n";

C objc;

cout<<"executing objc.funB()...\n";

objc.funB();

cout<<"exiting...\n";

}

Page 25: 第 6 章  多态性与虚函数

5. 虚析构函数class B{ public: virtual ~B( ); // 虚析构函数}这样,用指针去释放对象时,基类指针指向哪个层次的对象,则调用的就是该类的析构函数,释放掉该对象。

注意:

1. 基类析构函数被声明为虚析构函数,则派生类中的析构函数会自动成为虚析构函数。2. 如果一个类拥有虚函数,则该类即使不需要虚析构函数,也应该为它提供一个。

注意:构造函数不能是虚函数

Page 26: 第 6 章  多态性与虚函数

5. 虚析构函数(例 6.8 )

#include <iostream.h>

class A

{ public:

virtual ~A() {

cout<<"A's ~A()"<<endl; }

};

class B:public A

{ char *buf;

public:

B(int i) {

buf=new char[i]; }

virtual ~B()

{ delete [] buf;

cout<<"B's ~B()"<<endl;

}

};

void fun(A *a)

{

delete a;

}

void main()

{

A *a=new B(10) ;

fun(a);

}

Page 27: 第 6 章  多态性与虚函数

上堂课总结1.普通成员函数调用虚函数(体现多态性)。 实质:基类指针指向普通成员函数(派生类自己或从基类

继承过来),而此成员函数又调用虚函数则调用派生类所指的虚函数。

2. 构造函数和析构函数调用虚函数(静态联编)。 实质:构造函数和析构函数是系统自动调用的,并不需要

使用基类指针调用,所以是静态的。3. 虚析构函数。 作用:保证使用基类类型的指针能够调用适当的析构函数

针对不同的对象进行清理。 实质:如果删除该指针,就会调用该指针指向的派生类的

析构函数。

Page 28: 第 6 章  多态性与虚函数

6.3.4 虚函数与重载函数的关系 在一个派生类中重新定义基类的虚函数是函数重载的另

一种形式,但它不同于一般的函数重载。 ◆ 普通的函数重载时,其函数的参数或参数类型必须有所

不同,函数的返回类型也可以不同。 ◆ 当重载一个虚函数时,也就是说在派生类中重新定义虚

函数时,要求函数名、返回类型、参数个数、参数的类型和顺序与基类中的虚函数原型完全相同。

◆ 如果仅仅返回类型不同,其余均相同,系统会给出错误信息 ;

◆若仅仅函数名相同,而参数的个数、类型或顺序不同,系统将它作为普通的函数重载,这时将丢失虚函数的特性。

Page 29: 第 6 章  多态性与虚函数

虚函数与重载函数关系(例 6_8 )

#include<iostream.h>

class Base

{

public:

virtual void func1();

virtual void func2();

virtual void func3();

void func4();

};

class Derived:public Base

{

public:

virtual void func1();

void func2(int x);

//char func3();

void func4();

};

void Base::func1()

{ cout<<"--Base func1--"<<endl;}

void Base::func2()

{ cout<<"--Base func2--"<<endl;}

void Base::func3()

{ cout<<"--Base func3--"<<endl;}

void Base::func4()

{ cout<<"--Base func4--"<<endl;}

void Derived::func1()

{ cout<<"--Dervied func1--"<<endl;}

void Derived::func2(int x)

{ cout<<"--Dervied func2--"<<endl;}

void Derived::func4()

{ cout<<"--Dervied func4--"<<endl;}

int main()

{ Base d1,*bp; Derived d2;bp=&d2;

bp->func1();bp->func2();bp->func4();

}

Page 30: 第 6 章  多态性与虚函数

6.4 纯虚函数和抽象类

6.4.1 纯虚函数 纯虚函数是一个在基类中说明的虚函数,它在该基类中没有定义,但要求在它的派生类中必须定义自己的版本,或重新说明为纯虚函数。 纯虚函数的定义形式如下 :

virtual 函数类型 函数名 ( 参数表 )=0;

Page 31: 第 6 章  多态性与虚函数

例 6.9 纯虚函数的使用。#include<iostream.h>class Circle {public: void setr(int x){ r=x; } virtual void show()=0; // 纯虚函数protected: int r;};class Area:public Circle{public: void show(){ cout<<"Area is "<<3.14*r*r<<endl;}}; // 重定义虚函数 show( )class Perimeter:public Circle{public: void show(){cout<<"Perimeter is "<<2*3.14*r<<endl;} }; // 重定义虚函数 show( )

Page 32: 第 6 章  多态性与虚函数

void main(){ Circle *ptr; Area ob1; Perimeter ob2; ob1.setr(10); ob2.setr(10); ptr=&ob1; ptr->show(); ptr=&ob2; ptr->show();}

Page 33: 第 6 章  多态性与虚函数

6.4.2 抽象类 如果一个类至少有一个纯虚函数,那么就称该类为抽象类。 抽象类只能作为其他类的基类来使用,不能建立抽象类对象,其纯虚函数的实现由派生类给出。

Page 34: 第 6 章  多态性与虚函数

6.4.2 抽象类 ( 1 )抽象类只能作为其他类的基类来使用,不能建立类对象; ( 2 )不允许从具体类派生出抽象类; ( 3 )抽象类不能用作参数类型、函数返回类型或显式转换的类型; ( 4 )可以声明指向抽象类的指针或引用; ( 5 )派生类没有重定义纯虚函数,它仍然是一个抽象类; ( 6 )在抽象类可以定义普通成员函数或虚函数。

Page 35: 第 6 章  多态性与虚函数

6.5 程序应用举例

例 6-10 应用抽象类,求圆、圆内接正方形和圆外切正方形的面积和周长 例 6-11 建立两种类型的表:队列与堆栈。虽然两个表的特性完全不同,但它们可用同一接口访问。