第七章 多态性 主要内容 ●多态的概念:多态的定义、编译时的多态、运行时的多态、联编的概念、静态联编、动 态联编,以及多态的实现与联编的关系 ●虚函数:引入虚函数的原因、虚函数的定义及特性、运行时的多态需满足的条件、构造 函数与析构函数调用虚函数、虚函数的重载,以及析构函数的定义及调用 ● 纯虚函数和抽象类:纯虚函数的概念及定义、纯虚函数与函数体为空的虚函数的区别、 抽象类的概念及定义、抽象类的特性,以及抽象类的作用。 选择题 1下列关于动态联编的描述中,错误的是。 A动态联编是以虚函数为基础的 B动态联编是在运行时确定所调用的函数代码的 C动态联编调用函数操作时指向对象的指针或对象引用 D动态联编时在编译时确定操作函数的 答案:D 注释:动态联编一直要到程序运行时才能确定调用哪个函数 虚函数是实现动态联编的必要条件之一,没有虚函数一定不能实现动态联编,但有虚函数存 在时,必须同时满足下列条件,才能够实现动态联编: ●类之间满足子类型关系 ●调用虚函数操作的是指向对象的指针或对象引用:或者是由成员函数调用的虚函数 2在派生类中重新定义虚函数时,除了方面,其他方面都必须与基类中相应的虚函数保 持一致。 A参数个数 B参数类型 C函数名称 D函数体 答案:D 3下面关于构造函数和析构函数的描述,错误的是 A析构函数中调用虚函数采用静态联编 B对虚析构函数的调用可以采用动态联编 C当基类的析构函数是虚函数时,其派生类的析构函数也一定是虚函数 D构造函数可以声明为虚函数 答案:D 注释:构造函数不能声明为虚函数,但是在构造函数中可以调用虚函数。在构造函数或析构 函数中调用虚函数,将采用静态联编。 4关于纯虚函数和抽象类的描述中,错误的是 。 A纯虚函数是一种特殊的虚函数,它没有具体的实现 B抽象类是指具有纯虚函数的类 C一个基类中说明有纯虚函数,该基类的派生类一定不再是抽象类 D抽象类只能作为基类来使用,其纯虚函数的实现由派生类给出 答案:C 注释:带有纯虚函数的类称为抽象类。抽象类中的纯虚函数的实现由派生类给出,但派生类 仍可不给出纯虚函数的定义,继续作为抽象类存在。 5下面的描述中,正确的是一
第七章 多态性 主要内容 z 多态的概念:多态的定义、编译时的多态、运行时的多态、联编的概念、静态联编、动 态联编,以及多态的实现与联编的关系 z 虚函数:引入虚函数的原因、虚函数的定义及特性、运行时的多态需满足的条件、构造 函数与析构函数调用虚函数、虚函数的重载,以及析构函数的定义及调用 z 纯虚函数和抽象类:纯虚函数的概念及定义、纯虚函数与函数体为空的虚函数的区别、 抽象类的概念及定义、抽象类的特性,以及抽象类的作用。 选择题 1 下列关于动态联编的描述中,错误的是 。 A 动态联编是以虚函数为基础的 B 动态联编是在运行时确定所调用的函数代码的 C 动态联编调用函数操作时指向对象的指针或对象引用 D 动态联编时在编译时确定操作函数的 答案:D 注释:动态联编一直要到程序运行时才能确定调用哪个函数 虚函数是实现动态联编的必要条件之一,没有虚函数一定不能实现动态联编,但有虚函数存 在时,必须同时满足下列条件,才能够实现动态联编: z 类之间满足子类型关系 z 调用虚函数操作的是指向对象的指针或对象引用;或者是由成员函数调用的虚函数 2 在派生类中重新定义虚函数时,除了 方面,其他方面都必须与基类中相应的虚函数保 持一致。 A 参数个数 B 参数类型 C 函数名称 D 函数体 答案:D 3 下面关于构造函数和析构函数的描述,错误的是 。 A 析构函数中调用虚函数采用静态联编 B 对虚析构函数的调用可以采用动态联编 C 当基类的析构函数是虚函数时,其派生类的析构函数也一定是虚函数 D 构造函数可以声明为虚函数 答案:D 注释:构造函数不能声明为虚函数,但是在构造函数中可以调用虚函数。在构造函数或析构 函数中调用虚函数,将采用静态联编。 4 关于纯虚函数和抽象类的描述中,错误的是 。 A 纯虚函数是一种特殊的虚函数,它没有具体的实现 B 抽象类是指具有纯虚函数的类 C 一个基类中说明有纯虚函数,该基类的派生类一定不再是抽象类 D 抽象类只能作为基类来使用,其纯虚函数的实现由派生类给出 答案:C 注释:带有纯虚函数的类称为抽象类。抽象类中的纯虚函数的实现由派生类给出,但派生类 仍可不给出纯虚函数的定义,继续作为抽象类存在。 5 下面的描述中,正确的是 。 1
A virtual可以用来声明虚函数。 B含有纯虚函数的类是不可以用来创建对象的,因为它是虚基类 C即使基类的构造函数没有参数,派生类也必须建立构造函数 D静态数据成员可以通过成员初始化列表来初始化 答案:A 注释:virtual关键字既可以用来声明虚基类,也可以用来声明虚函数。 含有纯虚函数的类是抽象类,它不能用来定义对象。 静态数据成员的初始化必须在类体外进行 如果所有的基类和子对象构造函数都不需要参数,派生类也不需要参数时,派生类构造函数 可以不定义。 6下面程序中,A、B、C、D四句编译时出现错误的是。 class A MA { public: //B A(){func();) lic virtual void func() //D 答案:C 注释:构造函数中不能调用纯虚函数,但是可以调用虚函数。 填空题 1动态联编直到程序运行时才能确定调用哪个函数:而静态联编则是在程序编译时运行的。 静态联编所支持的多态性称为编译时的(静态)多态性,动态联编所支持的多态性则称为运 行时的(动态)多态性,动态多态性由虚函数来支持,它通过指针或引用来调用该函数操作。 2抽象类不能定义对象,但可以声明抽象类的指针或引用作为参数类型,函数返回类型或显 式转换类型。 阅读程序,写出运行结果 #include class base 1 { public: virtual void fun(){cout<<"base 1"<<endl;) class base 2 { public: void fun(){cout<<"base 2"<<endl;) class derived:public base 1,public base2 { public: {cout<<"derived"<<endl;) 2
A virtual 可以用来声明虚函数。 B 含有纯虚函数的类是不可以用来创建对象的,因为它是虚基类 C 即使基类的构造函数没有参数,派生类也必须建立构造函数 D 静态数据成员可以通过成员初始化列表来初始化 答案:A 注释:virtual 关键字既可以用来声明虚基类,也可以用来声明虚函数。 含有纯虚函数的类是抽象类,它不能用来定义对象。 静态数据成员的初始化必须在类体外进行 如果所有的基类和子对象构造函数都不需要参数,派生类也不需要参数时,派生类构造函数 可以不定义。 6 下面程序中,A、B、C、D 四句编译时出现错误的是 。 class A //A { public: //B A() {func();} //C virtual void func( ) //D }; 答案:C 注释:构造函数中不能调用纯虚函数,但是可以调用虚函数。 填空题 1 动态联编直到程序运行时才能确定调用哪个函数;而静态联编则是在程序编译时运行的。 静态联编所支持的多态性称为编译时的(静态)多态性,动态联编所支持的多态性则称为运 行时的(动态)多态性,动态多态性由虚函数来支持,它通过指针或引用来调用该函数操作。 2 抽象类不能定义对象,但可以声明抽象类的指针或引用作为参数类型,函数返回类型或显 式转换类型。 阅读程序,写出运行结果 #include class base 1 { public: virtual void fun() {cout<<”base 1”<<endl;} }; class base 2 { public: void fun(){cout<<”base 2”<<endl;} }; class derived:public base 1,public base2 { public: {cout<<”derived”<<endl;} }; 2
void main() { base 1 *pl; base 2 *p2; derived obj; pl=&obj; pl->funO; p2=&obj; p2->fun(); } 答案: derived base 2 注释:多继承式单继承的扩展。多继承情况下,每个基类之间是独立的,派生类与每个基类 之间的关系仍可看作是一个单继承。因此,派生类derived从两个不同的基类basel和base2 继承是独立运行的,两个基类中具有不同性质的函数对派生类中同名函数的影响也是独立 的。 因此,相对于基类basel的派生路径,由于base1中的fun函数是虚函数,所以派生类derived 中与其同名的具有相同参数和返回类型的fun也是虚函数。 相对于基类base2的派生路径,由于base2中的fun是一般成员函数,所以派生类derived中 的fun函数也是一般成员函数。 2 #include class B { int b: public: BO B(int i){b=i;) virtual void func() { cout<<"B:func()called"<<endl: } class D:public B { public: D0{} D(int i,int j):B(i){d=j;) Private: int d; void fun() { cout<<"D:func()called"<<endl: 3
void main() { base 1 *p1; base 2 *p2; derived obj; p1=&obj; p1->fun(); p2=&obj; p2->fun(); } 答案: derived base 2 注释:多继承式单继承的扩展。多继承情况下,每个基类之间是独立的,派生类与每个基类 之间的关系仍可看作是一个单继承。因此,派生类 derived 从两个不同的基类 base1 和 base2 继承是独立运行的,两个基类中具有不同性质的函数对派生类中同名函数的影响也是独立 的。 因此,相对于基类 base1 的派生路径,由于 base1 中的 fun 函数是虚函数,所以派生类 derived 中与其同名的具有相同参数和返回类型的 fun 也是虚函数。 相对于基类 base2 的派生路径,由于 base2 中的 fun 是一般成员函数,所以派生类 derived 中 的 fun 函数也是一般成员函数。 2 #include class B { int b; public: B() { } B(int i) {b=i;} virtual void func() { cout<<”B::func() called”<<endl; } }; class D: public B { public: D() { } D(int i, int j): B(i) {d=j;} Private: int d; void fun() { cout<<”D::func() called”<<endl; 3
void gfunc(B *obi) { obj->func()方 void main() D *pd=new D; gfunc(pd); } 答案:D:func()called 注释:pd指向D类的一个对象,动态联编只调用D类的fn()成员函数。 程序设计 编写一个程序,设计一个base基类,它有两个私有数据成员(x和y)以及一个个虚函数add (),由它派生出two类和three类,后者添加一个私有数据成员z,在这些派生类中实现add ()成员函数。并用数据进行测试。 参考答案 #include class base int x,y; public: base(int i,int j)x=i;y=j; virtual int add ()return x+y;} class two:public base { public: two (int i,int j):base(i,j) int add()return base:add(); class three:public base int Z; public: three(int i,int j,int k):base(ij) { z=k; } int add() { return(base::add()+z); 4
} }; void gfunc(B *obj) { obj->func( ); } void main() { D *pd=new D; gfunc(pd); } 答案:D::func() called 注释:pd 指向 D 类的一个对象,动态联编只调用 D 类的 fun()成员函数。 程序设计 编写一个程序,设计一个 base 基类,它有两个私有数据成员(x 和 y)以及一个个虚函数 add (),由它派生出 two 类和 three 类,后者添加一个私有数据成员 z,在这些派生类中实现 add ()成员函数。并用数据进行测试。 参考答案 #include class base { int x,y; public: base( int i, int j) { x=i; y=j;} virtual int add () { return x+y;} }; class two: public base { public: two (int i, int j): base(i,j) {} int add() { return base::add(); } }; class three: public base { int z; public: three(int i, int j, int k) :base(i,j) { z=k; } int add() { return (base::add()+z); 4
}; void diso(base*obj)/∥动态联编 { two *p=new teo(10,20); three *q=new three(10,20,30); cout<<输出结果:”<endl; disp(p): disp(q); } 执行结果: 输出结果: 参数相加:30 参数相加:60 5
} }; void diso(base *obj) //动态联编 { two *p=new teo (10,20); three *q=new three(10,20,30); cout<<”输出结果:”<<endl; disp(p): disp(q); } 执行结果: 输出结果: 参数相加:30 参数相加:60 5