一、前言
本部分为C++语言刷题系列中的第18节,主要讲解这几个知识点:多态性的概念、虚函数与覆盖函数、虚析构函数、纯虚函数与抽象类。欢迎大家提出意见、指出错误或提供更好的题目!
二、知识点讲解
知识点1:多态性的概念
· 在C++语言中,多态性是指对相同名称函数的调用,在不同情况下能调用不同的实现函数。
· 通过对重载函数的调用实现的多态性,称为静态多态性。在程序编译阶段,编译器通过实参匹配形参的方式确定具体调用哪个函数,故也称为编译时的多态性。
· 通过基类指针或引用调用虚函数实现的多态性,称为动态多态性。在程序运行阶段,基类指针指向哪个派生类对象,则调用哪个对象的覆盖函数,故也称为运行时的多态性。
知识点2:虚函数与覆盖函数(更深的理解可看后续的文章)
· 在基类中,被关键字virtual修饰的成员函数,都称为虚函数。
· 在派生类中,只要定义的成员函数与基类的虚函数原型相同,都称为覆盖函数。需要注意的是,派生类中覆盖函数的virtual关键字可以省略。
· 通过基类指针或引用调用虚函数时,当指针指向派生类对象,则调用派生类的成员函数;当指针指向基类对象时,则调用基类的成员函数。
知识点3:虚析构函数
通过基类指针释放派生类对象时,需要将基类的析构函数设置为虚函数。否则,仅清除基类的内容即仅执行基类的析构函数,而不执行派生类的析构函数。
知识点4:纯虚函数与抽象类
· 当基类无法(或没有必要)提供虚函数的实现的实现时,可以将虚函数设置成纯虚函数。
· 将虚函数声明为纯虚函数的格式:virtual void print() = 0;
· 包含纯虚函数的类为抽象类。抽象类不能被实例化,并且派生类中没有提供纯虚函数的实现时,该派生类仍然为抽象类。
三、试题解析
★☆☆☆☆
1.下列关于多态性的描述,错误的是( )。
A.C++语言中的多态性分为编译时的多态性和运行时的多态性
B.编译时的多态性可通过函数重载实现
C.运行时的多态性可通过模板和虚函数实现
D.实现运行时多态性的机制称为动态绑定
正确答案:C
解析:编译时的多态性是通过函数重载和模板体实现的,运行时的多态性是通过虚函数实现的。
★★☆☆☆
2.已知类XX中声明了如下的公有虚函数:
virtual void f()const;
XX的派生类YY覆盖了这个虚函数,XX和YY都有默认的构造函数,且有如下定义:
YY yy;
XX xx, *px=&xx, &rx=yy, *pp=&yy;
则下列对函数f的调用中,属于非多态调用的是( )。
A.px->f()
B.rx.f()
C.xx.f()
D.pp->f()
答案:C
解析:在C++语言中,多态调用是指通过基类的指针或引用调用虚函数。已知函数f()是虚函数,故关键是判断哪些选项是基类的指针或引用。因此,选项A、B、D都满足这一条件,所以本题正确答案为C。
★★★☆☆
3.有如下程序:
#include<iostream>
using namespace std;
class Vehicle
{
public:
virtual int wheels()const{ return 0; }
};
class Car: public Vehicle
{
public:
int wheels() const{ return 4; }
};
void f1(Vehicle v){ cout<< v.wheels()<<' '; }
void f2(Vehicle &v){ cout<<v.wheels()< <' '; }
void f3(Vehicle *pv){ cout<<pv->wheels()< <' '; }
void main()
{
Car c;
f1(c); f2(c); f3(&c);
}
运行后的输出结果是( )。
A.4 4 4
B.0 4 4
C.4 0 4
D.4 4 0
答案:B
解析:本题与第2题的知识点一样,但考查的形式不同。考查通过基类的指针或引用调用虚函数时,调用指针所指向派生类的成员函数。但如果是基类对象调用成员的话,则不管调用的是虚函数或非虚函数,都只能调用基类中的成员。函数f2的形参是基类指针,实参是派生类对象,则调用派生类中的成员,输出为4。函数f2的形参是基类引用,实参是派生类对象,则调用派生类中的成员,输出为4。而函数f1的形参按值传递,当以派生类对象为实参调用f1时,会创建一个基类对象副本,以基类对象调用时只能是基类中的成员,输出0。因此,最终输出为0 4 4,选项B正确。
★★★☆☆
4.有如下程序:
#include<iostream>
#include<string>
using namespace std;
class Instrument
{
public:
Instrument(){ }
virtual string GetType() const { return "AA"; }
virtual string GetName() const { return "BB"; }
};
class Piano: public Instrument
{
public:
Piano(){ }
string GetType() const { return "CC"; }
string GetName() const { return "DD"; }
};
void main()
{
Instrument *pi=new Piano();
cout<<pi->GetType()<<'-'<<pi->GetName();
delete pi;
}
运行时的输出结果是( )。
A. CC-DD
B. CC-BB
C. AA-BB
D. AA-DD
正确答案:A
解析:本题考查的是标准动态多态性的知识,即通过基类指针调用虚函数。指针pi是基类的指针,其调用的函数GetType()和GetName()都是虚函数,并且派生类中都提供了覆盖函数。因此,运行时调用哪个类中的函数要看指针pi具体指向哪个类的对象。本题指针pi指向派生类对象,因此输出结果为:CC-DD,选项A正确。
★★★★☆
5.有如下程序:
#include<iostream>
#include<string>
using namespace std;
class Instrument
{
public:
Instrument(){ }
string GetType() const { return "AA"; }
virtual string GetName() const { return "BB"; }
};
class Piano: public Instrument
{
public:
Piano(){ }
string GetType() const { return "CC"; }
string GetName() const { return "DD"; }
};
void main()
{
Instrument *pi=new Piano();
cout<<pi->GetType()<<'-'<<pi->GetName();
delete pi;
}
运行时的输出结果是( )。
A. CC-DD
B. CC-BB
C. AA-BB
D. AA-DD
正确答案:D
解析:本题与第4题看起来差不多,但更具迷惑性。因为同时考查了基类指针调用虚函数和非虚函数两种情况:其一通过基类指针调用非虚函数(pi->GetType()),只能调用基类中的成员,故先输出AA。其二通过基类指针调用虚函数(pi->GetName()),调用指针所指向的派生类中的成员,故后输出DD。因此,最终输出结果为:AA-DD,选项A正确。
★★★☆☆
6.下列程序执行后,输出结果是:
#include <iostream.h>
class A
{
public:
~A() { cout<<"A::destructor\n"; };
};
class B : public A
{
public:
~B() { cout<<"B::destructor\n"; };
};
void main()
{
A *pA=new B;
delete pA;
}
正确答案:cout<<"A::destructor\n";
解析:正常释放派生类对象时,都会先执行派生类的析构函数,后执行基类的析构函数。但是,当通过基类指针释放派生类对象时,如果基本的析构函数不是虚函数,则只会释放基类的内容,即仅执行基本的析构函数,不执行派生类的析构函数。特别提醒:在这种情况下,应该将基类析构函数设置为虚析构函数。
7.有如下程序:
#include <iostream>
using namespace std;
class Instrument
{
public:
virtual void Display()=0;
};
class Piano: public Instrument
{
public:
void Display() { /*函数体略*/ }
};
void main()
{
Instrument s;
Instrument *p=0;
//… ;
}
下列叙述中正确的是( )。
A.语句"Insturment *p=0;"编译时出错
B.语句"Instrument s;"编译时出错
C.类Piano中的Display函数不是虚函数
D.类Instrument是一个虚基类
正确答案:B
答案解析:本题考查纯虚函数与抽象类。纯虚函数在声明虚函数时被"初始化"为0,而包含纯虚函数的类为抽象类,抽象类不能被实例化,即不能由抽象类创建对象,可以用来定义指针或引用。所以语句"Instrument s;"在编译时出错,选项B正确。
四、试题测试
1.下列关于虚函数的说明中,正确的是( )。
A.从虚基类继承的函数都是虚函数
B.虚函数不得是静态成员函数
C.只能通过指针或引用调用虚函数
D.抽象类中的成员函数都是虚函数
2.有如下程序:
#include<iostream>
using namespace std;
class Base
{
public:
void fun1() {cout<<"Base\n"; }
virtual void fun2() { cout<<"Base\n"; }
};
class Derived : public Base
{
public:
void fun1() { cout<<"Derived\n"; }
void fun2() { cout<<"Derived\n"; }
};
void f(Base& b) { b.fun1(); b.fun2(); }
void main()
{
Derived obj;
f(obj);
}
执行这个程序的输出结果()
A.Base
Base
B.Base
Derived
C.Derived
Base
D.Derived
Derived
3.有如下程序段:
#include <iostream>
using namespace std;
class Base
{
public:
Base(int cnt){ resource=new int[cnt]; }
~Base(){ delete [] resource; }
virtual void show(){ cout<<"Base"<<endl; }
int *resource;
};
class Derived: public Base
{
public:
Derived(int cnt):Base(cnt){ resource=new float[cnt]; }
~Derived(){ delete [] resource; }
void show(){ cout<<"Derived"<<endl; }
float *resource;
};
上述程序使得derived对象中的动态内存空间有可能不被释放,有内存泄漏风险,原因是( )。
A.base类是抽象类
B.derived类的析构函数没有释放基类的动态内存
C.base类的析构函数不是虚函数
D.derived类的resource与公有继承得到的resource同名
4. 有如下类定义:
class Shape
{
public:
___________________ //纯虚函数Draw的声明
};
横线处缺失的纯虚函数 Draw 的声明是( )。
A. void Draw()=0;
B. virtual void Draw()=0;
C. virtual void Draw() { }
D. virtual void Draw(int=0);
------------------------------
欢迎大家提出意见、指出错误或提供更好的题目,只有大家的共同努力,才能帮助更多人掌握C++的基本概念,顺利通过考试!